本文基于Leetcode上Top Interview Questions、Top 100 Liked Questions中的链表部分和剑指offer上的链表算法题进行总结,同时大家也可以参考这篇博客,整理的很不错。
问题描述:删除链表中的结点(已经定位到该结点),该结点不在尾结点,要求in-place操作。
思路:一般的删除链表结点是先定位到该结点的前一个结点,通过指针删除,这里既然已经定位到该结点,且不是尾结点,就可以将下一结点的信息赋值给当前结点,然后删除下一个结点。
node.val = node.next.val
node.next = node.next.next
问题描述:判断链表是否有环
思路:方法1.直接利用python中的list,将结点循环添加到list中,如果存在重复则有环,不过这种方法需要额外的O(n)空间,且容易出现超时;方法2. 利用快慢指针,一个在前一个在后,如果两者相遇则链表有环。
def hasCycle(head):
if head is None:
return False
fast = head
slow = head
while fast and fast.next: # 利用前面的指针判断 fast.next 和 fast.next.next
fast = fast.next.next
slow = slow.next
if fast == slow:
return True
return False
问题描述:判断链表是否有环,有环则找出环的起点
思路:方法1.直接利用python中的list,循环将结点添加到list中,如果存在重复则有环,第一个重复的元素则为环的起点,需要额外的O(n)空间,且容易出现超时;方法2.快慢指针,具体分析如下:
假设链表head到环的起点距离为L1,环的长度为C,快慢指针在环中M点相遇,M点距离环起点L2距离。
利用快慢指针,一个在前一个在后,快指针此时前进了L1 + L2 + n * C,慢指针前进了L1 + L2,根据相遇条件,2 * (L1 + L2) = L1 + L2 + n * C,解得 L1 = (n - 1) * C + (C - L2),如果忽略(n-1)* C这一项,则L1 = C- L2,即L1的长度为环的长度减去环的起点到M点的距离。再利用一个慢指针从头结点开始出发,相遇点处的指针也继续向前走,则一定会在环的起点相遇。
def detectCycle(head):
if head is None:
return None
slow1 = head
fast = head
slow2 = head
while fast and fast.next: # 利用快慢指针向前
fast = fast.next.next
slow1 = slow1.next
if fast == slow1:
break
else: # 注意这里的写法,当不相等的时候,直接返回None
return None
while fast != slow2: # 从头开始的慢指针,与之前的指针相遇时,就到达环开始的地方
slow2 = slow2.next
fast = fast.next
return fast
问题描述:将两个排好序的列表进行合并
思路:利用一个新链表和两个指针,根据已排好序的链表中首结点的值大小将链表头结点结点添加到新的链表中,并向后移动指针,注意存在两个链表长度不同的情况。
def mergeTwoLists(l1, l2):
p = ListNode(0)
dummy = p
while l1 and l2:
if l1.val > l2.val:
p.next = l2
l2= l2.next
else:
p.next = l1
l1 = l1.next
p = p.next
if l1: # 当第一个链表较长时
p.next = l1
if l2:
p.next = l2
return dummy.next
问题描述:链表排序,要求时间复杂度O(n log n)和O(1)空间复杂度
思路:归并排序,1.先找到列表的中间节点;2.再对左右节点进行分治;3.将左右节点进行合并排序,其中第三步的方法就是上一个题目。
def sortList(head):
if head is None or head.next is None:
return head
# split the Linklist at point p
p = None
slow = head
fast = head
while fast and fast.next:
p = slow
slow = slow.next
fast = fast.next.next
left = head
right = p.next
p.next = None
# iteration
left = self.sortList(left)
right = self.sortList(right)
# MergeSort
return self.merge(left,right)
问题描述:将K个链表进行合并
思路:方法1. 利用第4个题目中的方式,与K个链表进行循环合并。
def mergeKLists(lists):
if len(lists) == 0:
return None
if len(lists) == 1:
return lists[0]
mergelist = None
for i in range(len(lists)):
mergelist = mergeTwoLists(mergelist, lists[i])
return mergelist
问题描述:将链表中偶数打印到奇数后面
思路:方法1. 直接利用python中的list,将结点加入list并排序,然后再根据排序的结果新建链表;方法2. 利用两个指针,不断向前移动,时间复杂度为O(n)。
def oddEvenList(head):
if head is None:
return None
# 借助了两个变量,其中 oddhead 和 evenhead 是分别作为奇数和偶数结点的起点一直保持不动,在最后的时候append
oddhead = odd = head
evenhead = even = head.next # 这里 head.next is None 的情况会在下面的循环语句判断
while even and even.next:
odd.next = even.next
odd = odd.next
even.next = odd.next
even = even.next
odd.next = evenhead # 将偶数结点添加到奇数结点后面
return oddhead
问题描述:判断链表是否对称(回文链表)
思路:1. 利用快慢指针,找到中间节点;2. 将后面的链表进行链表反转;3. 将反转的链表与链表的前半部分进行比较,如果相同则是对称链表。
def isPalindrome(head):
if head is None:
return True
fast = head
slow = head
while fast and fast.next: # 找到中间点位置
fast = fast.next.next
slow = slow.next
p_head = slow # 偶数个节点时,中间位置就是下一段的开始节点,并进行链表反转
p = None
q = None
while p_head:
q = p_head.next
p_head.next = p
p = p_head
p_head = q
while p: # 将反转得到的新链表和原来的链表前半部分进行比较
if head.val != p.val:
return False
head = head.next
p = p.next
return True
问题描述:移出链表中倒数第N个结点
思路:利用两个指针,一个快一个慢,快指针向前移动到倒数第K个结点的位置,然后再和慢指针一起往前移动,直到找到倒数第(K+1)个结点。
def removeNthFromEnd(head, n):
if head is None or n <= 0: # 边界条件的判断
return None
fast = head
slow = head
for _ in range(n):
fast = fast.next
if fast is None:
return head.next
while fast.next:
fast = fast.next
slow = slow.next
slow.next = slow.next.next # 移出结点
return head
问题描述:列表反转
思路:创建新的链表,利用指针不断将链表的首节点添加到新的链表中。
def ReverseList(pHead):
preNode = None
nextNode = None
while pHead:
nextNode = pHead.next # 链表的下一个结点
pHead.next = preNode # 将首节点逐渐添加到新的链表中
preNode = pHead
pHead = nextNode
return preNode
问题描述:K个节点一组翻转链表
思路:与上述题目不同,此题要求不能使用额外的空间,因此不能重建新的链表。一种思路是在遍历一次链表的时候,每K个节点做反转操作。具体的,如果得到链表反转的头结点位置和尾结点位置,则可以在这个区间进行类似“插入”的操作进行翻转。
def ReverseKnodes(phead, k):
h = ListNode(-1)
h.next = phead
pre = h
cur = phead
while cur is not None:
t = cur
count = 1
# find the start position and end position
while count < k and t is not None:
t = t.next
count += 1
if count == k and t is not None:
for _ in range(k-1):
lst = cur.next
cur.next = lst.next
lst.next = pre.next
pre.next = lst
# 借助于pre结点,进行类似“插入”的操作,完成翻转
# 如 pre->1->2->3
# 第一步: pre->2->1->3(将2 “插入” 到 pre 和 1 之间)
# 第二步: pre->3->2->1(将3 “插入” 到 pre 和 2 之间)
# 更新 pre 和 标记位 cur
pre = cur
cur = pre.next
else:
break
return h.next