数据结构与算法-链表题-总结

本文将总结Leetcode和剑指offer已有的链表相关的题目和解答,有简入难。

1、删除链表的节点

(1)、O(1)时间删除链表节点 

题目:给定一个链表和一个节点指针,在O(1)时间删除该节点。
方法:狸猫换太子,用后一个节点数据覆盖要删除的节点,然后删除下一个节点。

leetcode 237. Delete Node in a Linked List(删除链表中的特定节点)

//java

public class leetcode237删除链表中的特定节点 {
	
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
    
    public class ListNode {
    	int val;
    	ListNode next;
    	ListNode(int x) { val = x; }
    }
}



# python
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def deleteNode(self, node):
        """
        :type node: ListNode
        :rtype: void Do not return anything, modify node in-place instead.
        """
        node.val = node.next.val
        node.next = node.next.next

(2)、删除链表中的重复节点

剑指offer 面试题18 删除链表中的重复节点
leetcode82. Remove Duplicates from Sorted List(删除有序链表中的重复项) 

leetcode83. Remove Duplicates from Sorted List II(删除有序链表中的重复项,重复项只保留第一次出现的节点如果链表是无序的,可以利用set())

题目:输入的单链表中有一些重复节点,请对他们进行删除
分析:根据问题1,可以进行狸猫换太子删除法如leetcode83,也可以进行传统的删除方法可以参见剑指offer18,对比理解。

# python
class Solution:
    def deleteDuplication(self, pHead):
        # write code here
        if pHead is None or pHead.next is None:
            return pHead
        head1 = pHead.next
        if head1.val != pHead.val:
            pHead.next = self.deleteDuplication(pHead.next)
        else:
            while pHead.val == head1.val and head1.next is not None:
                head1 = head1.next
            if head1.val != pHead.val:
                pHead = self.deleteDuplication(head1)
            else:
                return None
        return pHead


// java
public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if (pHead == null || pHead.next == null) { // 只有0个或1个结点,则返回
            return pHead;
        }
        if (pHead.val == pHead.next.val) { // 当前结点是重复结点
            ListNode pNode = pHead.next;
            while (pNode != null && pNode.val == pHead.val) {
                // 跳过值与当前结点相同的全部结点,找到第一个与当前结点不同的结点
                pNode = pNode.next;
            }
            return deleteDuplication(pNode); // 从第一个与当前结点不同的结点开始递归
        } else { // 当前结点不是重复结点
            pHead.next = deleteDuplication(pHead.next); // 保留当前结点,从下一个结点开始递归
            return pHead;
        }
    }
}

(3) leetcode19 Remove Nth Node From End of List

题目:给定一个链表,删除链表的倒数第 个节点,并且返回链表的头结点。你能尝试使用一趟扫描实现吗?

解法:采用双指针法:一个指针在前一个指针在后间隔n个节点,遍历一次即可。 删除链表中倒数第 n 个元素。原来的头节点可能被删除掉,所以需要使用辅助头节点。

(4). 找出链表中间节点:Leecode 876. Middle of the Linked List

解法:双指针法:

# python
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def middleNode(self, head: ListNode) -> ListNode:
        if head is None:
            return None

        slow = head
        fast = head

        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        return slow

(5) leetcode 203 Remove Linked List Elements 删除元素值为val 的节点 ,注意可能删除的是头结点

解法:https://leetcode-cn.com/problems/remove-linked-list-elements/solution/di-gui-he-die-dai-by-powcai-6/

2、链表反转   

(1). 单链表反转

题目:输入一个单项链表,输出逆序反转后的链表。
方法:pre, cur, next 三指针循环一遍即可。 或者递归方法(不容易想到)

leetcode 206. Reverse Linked List (翻转一个链表)
剑指offer 面试题24 反转链表

# python
# 循环的方法
class Solution:
    # 返回ListNode
    def ReverseList(self, pHead):
        if not pHead or not pHead.next:
            return pHead
        left = None
        while pHead:
            right = pHead.next
            pHead.next = left
            left = pHead
            pHead = right
        return left

# 递归的方法
class Solution:
    # 返回ListNode
    def ReverseList(self, pHead):
        # write code here
        if pHead==None or pHead.next == None:
            return pHead
        p = self.ReverseList(pHead.next)
        pHead.next.next = pHead
        pHead.next = None
        return p

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None


//java

public class offer16递归反转链表 {
	//用三个指针分别指向前一个,后一个和目前这个
	public ListNode ReverseList(ListNode head) {
		 if(head==null||head.next==null) return head;
		 ListNode i=null;
		 ListNode j=head;
		 ListNode k;
		 while(j!=null){//比较简单
			 k=j.next;
             j.next=i;
			 i=j;
			 j=k;
		 }
		 return i;
	 }

    //用递归方法实现反转链表
	public ListNode reverse(ListNode current)  {  
	     if (current == null || current.next == null) return current;  
	     ListNode nextNode = current.next;  
	     current.next = null;  
	     ListNode reverseRest = reverse(nextNode);  
	     nextNode.next = current;  
	     return reverseRest;  
	 }
}


(2). leetcode 24 两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

(3). leetcode 92. 反转链表 II

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

示例:

输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL

3、求链表倒数第k个节点

【剑指offer】 面试题22 链表中倒数第k个结点

题目:输入一个单向链表,输出该链表中倒数第k个节点。
分析: 双指针法,设置p1,p2两个指针,首先p1移动k-1个点,然后p2和p1再一起移动,直到p1到达链表尾部,p2刚好是链表的倒数第k个节点。

# python

class Solution:
    def FindKthToTail(self, head, k):
        # write code here
        p1 = p2 = head
        for i in range(k):
            if p1==None:
                return None
            p1 = p1.next
        while(p1):
            p2 = p2.next
            p1 = p1.next
        return p2


// java
public class offer15链表中倒数第K个结点 {
	//要分别考虑head和k的边界条件  本方法只需要一次遍历链表
	public ListNode FindKthToTail (ListNode head,int k) {
		if(head==null) return head;
		if(k<=0) return null;
		ListNode first=head;
		ListNode second=head;
		for(int i=0;i<k;i++){
			if(first==null) return null;
			first=first.next;
		}
		while(first!=null){
			first=first.next;
			second=second.next;
		}
		return second;
    }	
}

4、 判断单链表是否有环

leetcode141 Linked List Cycle(判断链表是否有环)

题目:输入一个单链表判断是否有环存在

方案一:用一个set集合取遍历每一个节点,如果出现重复就证明有环(注意:两个节点的值相同 != 两个节点相同)

方案二:快慢指针法,从头出发,快指针一次走两步,慢指针一次走一步,如果存在环,那么快慢指针一定在某一时刻相遇。

# python
# 方案一
class Solution1:
    def hasCycle(self, head):
        mapping = set()
        flag = False
        p = head
        while p:
            if p not in mapping:
                mapping.add(p)
            else:
                flag = True
                break
            p = p.next
        return flag

# 方案二
class Solution1:
    def hasCycle(self, head):
        if head is None or head.next is None:
            return False
        slow = head
        fast = head
        while fast is not None and fast.next is not None:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                return True
        return False




//java 
public class leetcode141判断链表是否有环 {

    public static boolean hasLoop(ListNode n){
        //定义两个指针tmp1,tmp2
    	ListNode tmp1 = n;
    	ListNode tmp2 = n.next;

        while(tmp2!=null){

            int d1 = tmp1.val;
            int d2 = tmp2.val;
            if(d1 == d2)return true;//当两个指针重逢时,说明存在环,否则不存在。
            tmp1 = tmp1.next;  //每次迭代时,指针1走一步,指针2走两步
            tmp2 = tmp2.next.next;
            if(tmp2 == null)return false;//不存在环时,退出

        }
        return true; //如果tmp2为null,说明元素只有一个,也可以说明是存在环
    }
    
    //方法2:将每次走过的节点保存到hash表中,如果节点在hash表中,则表示存在环
    public static boolean hasLoop2(ListNode n){
    	ListNode temp1 = n;
        HashMap<ListNode,ListNode> map = new HashMap<ListNode,ListNode>();
        while(n!=null){
            if(map.get(temp1)!=null) {
            	return true;
            }else {
            	map.put(temp1, temp1);
            }
            temp1 = temp1.next;
            if(temp1 == null)return false;
        }
        return true;
    }
    
    public class ListNode {
    	int val;
    	ListNode next;
    	ListNode(int x) { 
    		val = x; 
    	}
    }
    
}

5、找到环的入口节点

【剑指offer】 面试题23 链表中环的入口节点

题目:输入一个单链表判断是否有环存在,如果存在环,如何找到环的入口节点。
分析:快慢指针法,从头出发,快指针faster一次走两步,慢指针slower一次走一步,如果存在环,那么快慢指针一定在某一时刻相遇。接下来,让faster回到链表的头部重新走,每次走一步,当faster,slower再次相遇的时候,则就是入口节点。

这类问题通常使用双指针的方法,即一个快指针一个慢指针。

faster = faster.next.next;

slower = slower.next;

“公理”:两指针相遇时,快指针走过的路程为慢指针的2倍。

链表有环时,有以下3种情况,右边和下边都是第一种的特例,下文以第一种为讨论对象。
 

1.判断是否有环

两个指针开始时均指向头节点,快指针每次跨2个节点,慢指针每次跨1个节点,如果两个指针能够相遇即存在环。

2.求环的长度

假设两个指针在b点相遇。则点b必在环中,以b点为出发点,两个指针再来一次“追逐”,慢指针走一圈,快指针走2圈后他们又在b点相遇。此时慢指针走过的路程即为环周长。

3.求环入口

根据前文的“公理”可得:a+nr+b=2(a+b),  其中r为圆周长,n>=1

化简得:a=nr-b, 即: a=(n-1)r+r-b

这个式子的意义就是,一个慢指针slower1从链表头出发,1个慢指针slower2从b点出发,slower1走到环入口时(路程为a),slower也刚好走到环入口(路程为(n-1)r+r-b:n-1个整圈加上r-b的路程)

# python   #这个方案更容易理解
class Solution:
    def EntryNodeOfLoop(self, pHead):
        if pHead==None or pHead.next==None or pHead.next.next==None:
            return None
        slow=pHead.next
        fast=pHead.next.next
        while slow!=fast:
            if fast.next==None or fast.next.next==None:
                return None
            slow=slow.next
            fast=fast.next.next
        fast=pHead
        while slow!=fast:
            slow=slow.next
            fast=fast.next
        return fast



# java
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        ListNode fast=pHead;
        ListNode slow=pHead;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow)
                break;
        }
        if(fast==null||fast.next==null)
            return null;
        slow=pHead;
        while(fast!=slow){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }
}

6、两个链表相交的第一个公共节点

leetcode160. Intersection of Two Linked Lists(寻找两个链表的交叉点)
剑指offer 面试题52 链表的公共节点

题目:如果两个没有环的链表相交,怎么求出他们相交的第一个节点?

Ref: https://www.cnblogs.com/yqpy/p/9551004.html

方案一:将两个链表先后压入同一个栈中,遇到第一个重复的就是他们的第一个公共节点。

方案二:就是把全部结点分别压入两个栈,利用栈的特性LIFO,然后同时pop出栈,一开始两边的元素肯定是相同的,当遇到不同的元素时,肯定已经遇到了最后一个节点,那就break

方案三:就是分别从链表的头结点开始遍历,当两条链表有长度差时,先让长链表走他们的差值,当走到还剩下和短链表一样长时,两个链表同时遍历,这样就能找到第一个公共结点了

方案四:采用对齐的思想,把两个单链表互相拼接成同样长度的链表即l1+l2 和 l2+l1,然后从头用指针p1,p2分别遍历,p1=p2时找到。

#python 

# 方案一:
class Solution:
    def FindFirstCommonNode(self, pHead1, pHead2):
        # write code here
        result_set = set()
        while pHead1:
            result_set.add(pHead1)
            pHead1 = pHead1.next
        while pHead2:
            if pHead2 in result_set:
                return pHead2
            pHead2 = pHead2.next
        return None

# 方案二:
class Solution(object):
    def getIntersectionNode(self, headA, headB):
        stack1 = []
        stack2 = []
        while headA:
            stack1.append(headA)
            headA = headA.next
        while headB:
            stack2.append(headB)
            headB = headB.next
        node1 = None
        while stack1 and stack2 and stack1[-1] is stack2[-1]:
            node1 = stack1.pop()
            stack2.pop()
        return node1
#is比较的是两个对象的地址值,判断两个对象是否为同一个实例对象;而==比较的是对象的值是否相等,其调用了对象的__eq__()方法。

# 方案三:
def FindFirstCommonNode(self, pHead1, pHead2):
    len1 = self.getChainLen(pHead1)
    len2 = self.getChainLen(pHead2)
    if len2 > len1:
        pHead1, pHead2 = pHead2, pHead1
    diff = abs(len1-len2)
    while diff > 0:
        pHead1 = pHead1.next
        diff -= 1
    while pHead1 != pHead2:
        pHead1 = pHead1.next
        pHead2 = pHead2.next
    return pHead1

def getChainLen(self, Head):
    chainLen = 0
    while Head:
        chainLen += 1
        Head = Head.next
    return chainLen



方案四:
class Solution(object):
    def getIntersectionNode(self, headA, headB):
        ha, hb = headA, headB
        while ha != hb:
            ha = ha.next if ha else headB
            hb = hb.next if hb else headA
        return ha



//java

# 方案一:
import java.util.HashSet;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        HashSet<ListNode> set = new HashSet<>();
        if(pHead1==null || pHead2==null) return null;
        ListNode p1 = pHead1,p2 = pHead2;
        while(p1!=null){
            set.add(p1);
            p1 = p1.next;
        }
        while(p2!=null){
            if(set.contains(p2)){
                return p2;
            }
            p2 = p2.next;
        }
        return null;
    }
}



#方案四:
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1!=p2){
            p1 = (p1==null) ? pHead2 : p1.next;
            p2 = (p2==null) ? pHead1 : p2.next;
        }
        return p1;
    }
}


7、合并两个有序链表

(1). 合并两个有序链表

leetcode 21 Merge Two Sorted Lists (合并两个有序链表迭代)
剑指offer 面试题25 合并两个排好序的链表–递归

题目:给定两个排好序的链表,对两个链表进行合并
分析:递归和迭代两种方法分别给出。可以用两个指针分别指向两个链表,比较每一次节点值得大小。

# python
# 方案一:非递归
class Solution:
    # 返回合并后列表
    def Merge(self, pHead1, pHead2):
        # write code here
        mergeHead = ListNode(-1)
        p = mergeHead
        while pHead1 and pHead2:
            if pHead1.val >= pHead2.val:
                mergeHead.next = pHead2
                pHead2 = pHead2.next
            else:
                mergeHead.next = pHead1
                pHead1 = pHead1.next
                  
            mergeHead = mergeHead.next
        if pHead1:
            mergeHead.next = pHead1
        elif pHead2:
            mergeHead.next = pHead2
        return p.next

#方案二:递归
class Solution:
    # 返回合并后列表
    def Merge(self, pHead1, pHead2):
        if pHead1==None and pHead2==None:
            return None
        if pHead1==None:
            return pHead2
        if pHead2==None:
            return pHead1
        if pHead1.val<=pHead2.val:
            pHead1.next=self.Merge(pHead1.next,pHead2)
            return pHead1
        else:
            pHead2.next=self.Merge(pHead1,pHead2.next)
            return pHead2




//java 
// 方案一:非递归
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        //新建一个头节点,用来存合并的链表。
        ListNode head=new ListNode(-1);
        head.next=null;
        ListNode root=head;
        while(list1!=null&&list2!=null){
            if(list1.val<list2.val){
                head.next=list1;
                list1=list1.next;
            }else{
                head.next=list2;
                list2=list2.next;
            }
            head=head.next
        }
        //把未结束的链表连接到合并后的链表尾部
        if(list1!=null){
            head.next=list1;
        }
        if(list2!=null){
            head.next=list2;
        }
        return root.next;
    }
}

# 方案二:递归
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if (list1 == null) return list2;
        if (list2 == null) return list1;
 
        ListNode head = null;
        if (list1.val <= list2.val){
            head = list1;
            head.next = Merge(list1.next, list2);
        } else {
            head = list2;
            head.next = Merge(list1, list2.next);
        }
 
        return head;
    }
    public class ListNode {
        int val;
        ListNode next = null;
        ListNode(int val) {
            this.val = val;
        }
    }
}

(2). Leetcode 23 Merge k Sorted Lists  

解法:https://leetcode-cn.com/problems/merge-k-sorted-lists/solution/he-bing-kge-pai-xu-lian-biao-by-leetcode/

8.旋转链表

(1) Leetcode 61. 旋转链表

给定一个链表,旋转链表,将链表每个节点向右移动 个位置,其中 是非负数。

示例 1:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL

方法一:先将链表闭合成环,然后找到相应的位置断开这个环,确定新的链表头和链表尾

https://leetcode-cn.com/problems/rotate-list/solution/xuan-zhuan-lian-biao-by-leetcode/

方法二:双指针法找到倒数第k个节点,断开成两个链表,然后将后一个链表拼接在前一个链表前面。

https://leetcode-cn.com/problems/rotate-list/solution/ji-bai-liao-91de-javayong-hu-qing-xi-yi-dong-by-re/

9. 复制链表:

(1). leetcode 138. Copy List with Random Pointer     offer35 复杂链表的复制

解答:https://leetcode-cn.com/problems/copy-list-with-random-pointer/solution/duo-chong-si-lu-by-powcai/

 https://www.cnblogs.com/yanmk/p/9220525.html

方案一:递归法  通过了
# -*- coding:utf-8 -*-
# class RandomListNode:
#     def __init__(self, x):
#         self.label = x
#         self.next = None
#         self.random = None
class Solution:    
    # 返回 RandomListNode
    def Clone(self, pHead):
        # write code here
        if  pHead==None:
            return None
        newNode=RandomListNode(pHead.label)
        newNode.next=self.Clone(pHead.next)
        newNode.random=pHead.random
        
        return newNode

其他链表题目:

430. Flatten a Multilevel Doubly Linked List
遍历一个有子链表的双向链表。尽管此题被贴上了链表的标签,但是题目中主要考察的是深度优先搜索,链表只是一种外在的形式。

817. Linked List Components
给出一个链表和一个数组G。数组G中的元素是链表所有节点 val 值的子集。如果某个节点或者某一段节点的 val 值在数组G中,那么就被看作一个“组件”。求最终求整个链表一共有多少个“组件”?这个题需要用到集合,可以借此熟悉集合的用法。

2. Add Two Numbers
给出两个非空的链表,代表着2个非负整数。求这2个非负整数的和,也使用链表表示出来。

445. Add Two Numbers II
本题和 2. Add Two Numbers 非常像,唯一不同的地方在于链表表示整数是从高位到低位表示的。计算的过程中需要使用堆栈。可以借此熟悉堆栈的用法。

1019. Next Greater Node In Linked List
给出一个链表,求每个节点后面第一个val值更大的节点。为了降低时间复杂度,这个题使用了堆栈、还将链表的遍历顺序改变了。有点像动态规划的思路了,也就是利用之前计算存储下来的结果,计算当前的问题。

725. Split Linked List in Parts
把一个链表分割为 k 个尽量相等的部分。这个题目练习了如何从一个大链表中截取一小部分的链表。

328. Odd Even Linked List
把链表的奇数节点都排在偶数节点的前面,使用双指针法。不过本题的双指针法严格意义上来说,是用了三个指针的。

147. Insertion Sort List
使用插入排序对一个链表进行排序。需要学会如何在一个有序链表中插入元素,并且保证插入元素后链表还是有序的。

141. Linked List Cycle
给定一个链表,判断该链表中是否有环。该题可以用hash集合做,也可以用双指针法做。这两种方法都很常见,建议把它们都熟练掌握。

160. Intersection of Two Linked Lists
写一个程序去寻找两个单向链表相交的节点。本题同样有两种方法,一种是集合,一种是双指针法。可以当作双指针法的题目练习。

142. Linked List Cycle II
和141. Linked List Cycle非常类似,同样是给出一个链表,判断该链表中是否有环。如果有环,就返回环形开始的那个节点。如果这个链表中没有环,那么就返回null。方法仍然是集合、双指针法。

109. Convert Sorted List to Binary Search Tree
给出一个按照递增排序的单向链表,将其转换成一个高度平衡的二叉树。这个题目也利用了快慢双指针法找到链表的中点。该方法在 141. Linked List Cycle 和 142. Linked List Cycle II中都有使用过。

234. Palindrome Linked List
判断一个链表是不是回文串,主要用到了从一个链表中间一分为二的方法,以及反转链表的方法。

148. Sort List
对链表进行归并排序。需要掌握归并排序的方法、将链表从中间一分为二的方法和合并两个有序链表的方法。

143. Reorder List
这个题目是一道综合题,涉及三种针对链表的操作:
1 . 如何从中间将链表一分为二?使用了 109. Convert Sorted List to Binary Search Tree 中介绍的快慢双指针法
2. 如何反转一个链表?使用了 206. Reverse Linked List 中反转链表的方法,即节点的next指向前一个节点
3. 如何拼接两个链表?使用了类似于 21. Merge Two Sorted Lists 和 2. Add Two Numbers 中的合并链表的方法
 

发布了118 篇原创文章 · 获赞 132 · 访问量 60万+

猜你喜欢

转载自blog.csdn.net/qfikh/article/details/103624074