1. 数据结构---链表
一、链表
双指针”法解决链表问题
背景:单链表问题由于顺序遍历的特性,有时候执行一些操作的时候会出现问题看似需要多次遍历才能获取数据。
使用双指针法能在一次遍历中获取更多的数据,也可以节约更多的额外控件。“双指针”就是用一个快指针一个慢指针同时进行单链表的顺序扫描。
如此就可以使用快指针的时间差给慢指针提供更多的操作信息。
下面是两个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实现单向链表
浙公网安备 33010602011771号