一、链表
双指针”法解决链表问题
背景:单链表问题由于顺序遍历的特性,有时候执行一些操作的时候会出现问题看似需要多次遍历才能获取数据。
使用双指针法能在一次遍历中获取更多的数据,也可以节约更多的额外控件。“双指针”就是用一个快指针一个慢指针同时进行单链表的顺序扫描。
如此就可以使用快指针的时间差给慢指针提供更多的操作信息。
下面是两个LeetCode下的习题:
(1)给定一个链表,删除链表的倒数第 n 个节点并返回头结点。
思路:构建先导指针,快于后续指针n-1步,先导指针指向链表尾部时候,慢指针就指向倒数第n个节点
(2)给定一个链表,判断链表中否有环。
传统思路:找个容器把出现的点存起来,出现重复点就判断成环(缺点很明显:额外内存占用)
双指针思路:快指针比慢指针先走,如果快指针被慢指针追上,说明进入闭环
二、算法
1.链表反转
思路:
1.头插法
2.递归
# -*- coding:utf-8 -*- class ListNode: def __init__(self, x): self.val = x self.next = None class Solution: # 带头结点返回ListNode def ReverseList(self, pHead): if pHead == None or pHead.next == None: return pHead cur = pHead.next.next pHead.next.next = None while cur: post = cur.next cur.next = pHead.next pHead.next = cur cur = post return pHead # 不带头结点,要先创建头结点 def ReverseList1(self, pHead): if pHead == None or pHead.next == None: return pHead p = ListNode(0) p.next = pHead cur = p.next.next p.next.next = None while cur: post = cur.next cur.next = p.next p.next = cur cur = post return p.next def ReverseList2(self,pHead): ''' 递归,时间复杂度O(N),空间复杂度O(1) 从后向前反转 :param pHead: :return: ''' if pHead == None or pHead.next == None: return pHead rhead = self.ReverseList2(pHead.next) # pHead.next.next = pHead # 把pHead节点放在尾部 pHead.next = None return rhead
2.删除排序链表中重复的节点
题目:一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
# -*- coding:utf-8 -*- class ListNode: def __init__(self, x): self.val = x self.next = None class Solution: def deleteDuplication1(self, pHead): ''' 删除重复的,并且保留1个 :param pHead: :return: ''' if pHead == None or pHead.next == None: # 空或者只有一个节点 return first,end = pHead,pHead.next while end != None: while first.val == end.val: end = end.next first.next = end first = first.next end = end.next return pHead def deleteDuplication2(self, pHead): ''' 删除重复的,不保留 :param pHead: :return: ''' if pHead == None or pHead.next == None: # 空或者只有一个节点 return pHead dummy = ListNode(None) dummy.next = pHead first,end = dummy,dummy.next flag = 0 while end != None: while end.next and (end.val == end.next.val): end = end.next flag = 1 if flag == 1: # 有重复 first.next = end.next # 删除重复的所有,end指向重复的最后一个 flag = 0 else: # 无重复 first = first.next end = end.next return dummy.next # 1,2,3,3,4,4,5 listnode1 = ListNode(1) listnode2 = ListNode(2) listnode3 = ListNode(3) listnode4 = ListNode(3) listnode5 = ListNode(4) listnode6 = ListNode(4) listnode7 = ListNode(5) listnode1.next = listnode2 listnode2.next = listnode3 listnode3.next = listnode4 listnode4.next = listnode5 listnode5.next = listnode6 listnode6.next = listnode7 solution = Solution() res = solution.deleteDuplication2(listnode1) while res != None: print(res.val) res = res.next
3.两个链表的第一个公共节点
两个单链表相交分为三种情况:
(1)一个有环,一个没环,两个链表不可能相交
(2)两个都没有环,求相交点或者不相交
(3)两个都有环,求相交点或者不相交
思路1:利用两个栈分布记录两个链表,然后一起弹出栈顶,比较是否相等,如果相等,就继续一起弹出,直到不相等为止,返回最后一次弹出的节点,即为第一个公共节点,时间复杂度O(m+n),空间复杂度O(m+n)
思路2:headA的长度为A+C(公共长度),headB的长度为B+C,所以当headA走完A+C时,再转过来走B;headB走完B+C时,再转过来走A; 当两个节点都走了A+B+C时,两个节点相遇,返回该节点 时间复杂度O(m+n),空间复杂度O(1)
class Solution: def FindFirstCommonNode(self, pHead1, pHead2): p1 = pHead1 p2 = pHead2 while (p1 != p2): if p1 == None: p1 = pHead2 else: p1 = p1.next if p2 == None: p2 = pHead1 else: p2 = p2.next return p1
4.从尾到头打印链表
题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
# -*- coding:utf-8 -*- class ListNode: def __init__(self, x): self.val = x self.next = None class Solution: def __init__(self): self.arr = [] def printListFromTailToHead(self, listNode): ''' 思路: 用栈存储遍历过的节点 缺点:当链表非常长的时候,就会导致栈溢出 :param listNode: :return: ''' result = [] if listNode is None: return [] while listNode: result.append(listNode.val) listNode = listNode.next return result[::-1] def printListFromTailToHead2(self,listNode): ''' 思路:递归的本质是栈 :param listNode: :return: ''' if listNode == None: return [] if listNode.next != None: self.printListFromTailToHead2(listNode.next) # print(listNode.val,end=' ') self.arr.append(listNode.val) return self.arr L1 = ListNode(1) L2 = ListNode(2) L3 = ListNode(3) L4 = ListNode(4) L5 = ListNode(5) L6 = ListNode(6) # head.next = L1 L1.next = L2 L2.next = L3 L3.next = L4 L4.next = L5 L5.next = L6 S = Solution() res = S.printListFromTailToHead2(L1) print(res)
5.合并两个有序链表
时间复杂度O(N),空间复杂度O(1)
思路:
1)首先将两个链表第一个值比较小的链表作为头结点,
2)然后依次遍历两个链表的值,比较大小
3)再把次链表的直接接到主链表上
class ListNode: def __init__(self, x): self.val = x self.next = None class Solution: def mergeTwoLists(self, l1, l2): if l1 is None and l2 is None: return None if l1 is None: return l2 if l2 is None: return l1 head = None ptr1 = None ptr2 = None if l1.val < l2.val: head = l1 ptr1 = l1 ptr2 = l2 else: head = l2 ptr1 = l2 ptr2 = l1 while ptr1.next is not None and ptr2 is not None: if ptr1.val <= ptr2.val and ptr1.next.val >= ptr2.val: temp = ptr1.next ptr1.next = ptr2 ptr2 = ptr2.next ptr1.next.next = temp ptr1 = ptr1.next if ptr1.next is None: ptr1.next = ptr2 return head # 递归 def merge2LinkList(self,l1,l2): if l1 == None: return l2 if l2 == None: return l1 if l1.val < l2.val: l1.next = self.merge2LinkList(l1.next,l2) return l1 else: l2.next = self.merge2LinkList(l1,l2.next) return l2
6.复杂链表的复制
题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路1:第一步复制主链节点;第二步复制设置每个节点的m_pSibling节点 时间复杂度O(N2)
思路2:第一步复制主链节点,把<N,N'>配对信息放到哈希表中;第二步设置复制链表上的每个节点的m_pSibling,时间复杂度O(N),空间复杂度O(N)
思路3:第一步复制主链节点,把N'链接到N的后面;第二步设置复制出来的节点的m_pSibling;第三步长链表拆分成两个两个链表,空间复杂度O(1),时间复杂度O(N)
class Solution: def Clone(self, pHead): ''' 思路3 :param pHead: :return: ''' self.CloneNodes(pHead) self.ConnectSiblingNodes(pHead) return self.ReconnectNodes(pHead) def CloneNodes(self,pHead): ''' 复制每个节点,并插在被复制节点的后面 :param pHead: :return: ''' p = pHead while p: newNode = RandomListNode(None) newNode.label = p.label newNode.random = None # 不保存随机下一跳 newNode.next = p.next p.next = newNode p = newNode.next return pHead def ConnectSiblingNodes(self,pHead): ''' 设置复制出来的节点的m_pSibling :param pHead: :return: ''' p = pHead while p: newNode = p.next if p.random != None: newNode.random = p.random.next p = newNode.next return pHead def ReconnectNodes(self,pHead): ''' 拆分两个链表,把奇数位置的节点用next链接起来就是原始链表,把偶数位置的节点用next链接起来就是复制链表 :param pHead: :return: ''' p = pHead pClonedHead = None pClonedNode = None if p: pClonedHead = pClonedNode = p.next p.next = pClonedNode.next p = p.next while p: pClonedNode.next = p.next pClonedNode = pClonedNode.next p.next = pClonedNode.next p = p.next return pClonedHead L1 = RandomListNode(1) L2 = RandomListNode(2) L3 = RandomListNode(3) L4 = RandomListNode(4) L1.next = L2 L2.next = L3 L3.next = L4 L1.random = L3 L2.random = L1 s = Solution() res = s.Clone(L1) while res: print(res.label) if res.random != None: print(res.random) res = res.next
7.链表中倒数第k个节点
思路:快慢指针,第一个指针先走k步,然后第二个指针开始走,直到第一个指针下一跳为空,第二个指针所在的位置就是倒数第k个节点。相当于制造了一个k长度的尺子,把尺子从头往后移动,当尺子的右端与链表的末尾对齐的时候,尺子左端所在的结点就是倒数第k个结点
class Solution: def FindKthToTail(self, head, k): if head == None or k <= 0: return None slow = fast = head while k > 1: if fast.next != None: fast = fast.next k -= 1 else: return None while fast.next != None: slow = slow.next fast = fast.next return slow
8.链表中环的入口节点
思路:
1)先找快慢指针相遇的节点,这个节点肯定在环中;
2)然后通过这个节点,找到环的长度;
3)环的长度已知,可以设置快指针从头结点出发,先走环的长度的距离,再用慢指针,从头结点开始走,同时,快指针也开始走,两个指针相遇的地方就是环的入口位置
def MeetingNode(self, head):#有头结点 ''' :param head: :return: 返回快慢指针相遇的节点 ''' if head == None or head.next == None: return None slow = fast = head try: while slow.next and fast.next.next: slow = slow.next fast = fast.next.next if slow == fast: return slow except Exception as e: pass return None def EntryNodeOfLoop(self,pHead):#没有头结点 ''' :param pHead: :return: ''' meetingNode = self.MeetingNode(pHead) # print('meetingNode',meetingNode.val) if meetingNode == None: return None # 得到环中节点的数目 pNode1 = meetingNode count = 0 while pNode1.next != meetingNode: count += 1 pNode1 = pNode1.next # print('count',count) pNode2,pNode3 = pHead,pHead for i in range(count+1): # 先走count步 pNode2 = pNode2.next # print('pNode2',pNode2.val) while pNode2 != pNode3: pNode2 = pNode2.next pNode3 = pNode3.next return pNode2 s = Solution() # head = ListNode(None) L1 = ListNode(1) L2 = ListNode(2) L3 = ListNode(3) L4 = ListNode(4) L5 = ListNode(5) L6 = ListNode(6) # head.next = L1 L1.next = L2 L2.next = L3 L3.next = L4 L4.next = L5 L5.next = L6 L6.next = L3 res = s.EntryNodeOfLoop(L1) if res != None: print(res.val)
参考:
【1】Java实现单向链表