本篇将介绍关于链表算法的基本解体思路与经典问题,本篇不仅仅追求的是写出优秀的链表代码,更在意的是在有限时间内,如何写出bug free
的链表代码。
链表是一种利用不连续的内存块,通过在每块内存中存储下一块内存的指针而构造的线性存储结构,所以链表是线性表的一种形式。
链表问题是一种考察基本编码能力的问题,这类问题的特点是解法并不复杂,难点在于证明解法的正确性,以及如何编码。即,考察是否能编写出 bug free
的代码,少数会考察算法的数学证明问题。
那么什么是一般性的思路呢?
这是一种模式,链表问题的算法思想就是那么几种,掌握后套用即可,数学证明看几道典型案例即可,真正的难点在于编码,链表问题是涉及指针操作,极易出错,写出 bug free
是很不容的事情,所以最重要的就是要多加练习。
class MyLinkedList:
def __init__(self):
"""
初始化一个链表,使用一个永不存数据的节点作为哨兵,
这样可以简化,在插入删除操作中对头节点与尾节点插入时的条件判读,
进而提高速度。
"""
self.sb = ListNode(-1)
self.le = 0
def get(self, index):
"""
通过索引获取链表中的节点。
"""
head = self.sb.next
if index < 0 or index >= self.le or head == None:
return -1
for i in range(index):
head = head.next
return head.val
def addAtHead(self, val):
"""
在头节点的位置插入节点,哨兵思想使其不用判读非空情况。
"""
head = self.sb.next
self.sb.next = ListNode(val)
self.sb.next.next = head
self.le += 1
def addAtTail(self, val):
"""
在尾部插入节点
"""
tmp = self.sb
for i in range(self.le):
tmp = tmp.next
tmp.next = ListNode(val)
self.le += 1
def addAtIndex(self, index, val):
"""
在指定索引的位置插入节点
"""
if index < 0 or index > self.le :
return -1
pre = self.sb
for i in range(index):
pre = pre.next
tmp = pre.next
pre.next = ListNode(val)
pre.next.next = tmp
self.le += 1
def deleteAtIndex(self, index):
"""
指定index的位置删除节点
"""
if index < 0 or index >= self.le:
return
pre = self.sb
for i in range(index) :
pre = pre.next
pre.next = pre.next.next
self.le -= 1
双指针思想经典三问:判读链表是否有环,链表与环的交点,求环的长度。
定义两个指针,从头开始,一个每次走2步,
一个走一步,二者相交则有环,
不相交走两步的指针先到尾部,则判读无环。
class Solution(object):
def hasCycle(self, head):
if head is None or head.next is None:
return False
fast ,slow = head,head
while fast is not None and fast.next is not None:
fast = fast.next.next
slow = slow.next
if slow == fast:
return True
return False
快慢指针相遇后,将快指针重新指向头节点,
两个指针开始同时走,每次走一步。当再次相遇时,
即是环的入口点。
class Solution(object):
def detectCycle(self, head):
if head == None or head.next == None:
return None
fast = head.next.next
slow = head.next
while fast != None and fast.next != None:
fast = fast.next.next
slow = slow.next
if fast == slow:
break
if fast == None or fast.next == None:
return None
fast = head
while fast != slow:
fast = fast.next
slow = slow.next
return fast
pass
那么问题来了,上述前两个算法正确吗,如何用数学证明呢?
链表有环算法证明:
证明:
1. 设慢指针有走到环入口点位置时,L1代表head到入口点的距离,也就是慢指针走的距离,这是快指针一定到了环内,距离入口点设为L2.
2. i代表慢指针追上快指针要走的步数。
3. S1代码慢指针的总步数,S2代码快指针的总步数。
4. C代表环的周长。
若快指针与慢指针会相遇,那么一定满足(S1+i-L1)mod C = (S2+2i-L1)mod C
,减去L1是为了减去入环之前的步数,慢指针追击了i步那么快指针实际上走了2i步。因此,若想一定相遇就要满足这个条件。
刨除入环之前的步数影响,仅看入环后相对于环入口点位置的距离,有:S1 = L1
,S2 - L1 = NC + L2
,其中N是已经环绕环多少圈的数量。带入上式得i mod C = (NC+L2+2i) mod C
进一步整理得:(L2 + i) mod C = 0
,N取0时,可知,L2 < C
,i在整数范围内必有解,故快慢指针一定相遇。
环的入口点算法证明:
根据式子(4)=> (4)=>(m-n-1)L2+L2=s => (m-n-1)L2+ P1+P2=L1+P1 <=> (m-n-1)L2+P2=L1
(P1为定义第一个相交点到追击相遇点的长度,P2为相遇点到第一个相交点的长度,即P1+P2=L2)
这个式子表明链表中不包括环的长度 等于 相遇点到第一个相交点的长度加上环的长度的整数倍。
class Solution(object):
def getIntersectionNode(self, headA, headB):
p1 ,p2 = headA,headB
while p1 != p2:
p1 = headB if p1 == None else p1.next
p2 = headA if p2 == None else p2.next
return p1
class Solution(object):
def removeNthFromEnd(self, head, n):
pre ,last = head,head
for i in range(n):
pre = pre.next
if pre == None:
return head.next
while pre.next != None:
pre = pre.next
last = last.next
last.next = last.next.next
return head
指针操作
这类问题就是考察对指针操作的边界条件的检查,对编码能力有所要求。
class Solution(object):
def reverseList(self, head):
"""
使用三个指针,完成单链表操作。
"""
if head == None:
return None
pre = None
next = head.next
while head != None:
head.next = pre
pre = head
head = next
if next != None:
next = next.next
return pre
class Solution(object):
def removeElements(self, head, val):
if head == None:
return None
while head != None and head.val == val:
if head.next == None:
return None
else:
head = head.next
pre = head
while pre.next != None:
if pre.next.val == val:
pre.next = pre.next.next
else:
pre = pre.next
return head
class Solution(object):
def oddEvenList(self, head):
if head is None:
return head
odd = head
even = head.next
if even is None:
return head
odd_prev = odd
even_prev = even
even_head = even
while even is not None and even.next is not None:
odd = even.next
even = odd.next
odd_prev.next = odd
even_prev.next = even
odd_prev = odd
even_prev = even
odd.next = even_head
return head
class Solution(object):
def isPalindrome(self, head):
if head is None or head.next is None:
return True
mid = self.getMid(head)
mid.next = self.getFanZhuan(mid.next)
p,q = head,mid.next
while q is not None and p.val == q.val:
q = q.next
p = p.next
self.getFanZhuan(mid.next)
return q == None
def getMid(self,head):
fast,slow = head.next,head
while fast is not None and fast.next is not None:
fast = fast.next.next
slow = slow.next
return slow
def getFanZhuan(self,head):
if head is None or head.next is None:
return head
pre,curr,next = None,head,head.next
while curr is not None:
curr.next = pre
pre = curr
curr = next
next = None if next is None else next.next
return pre
class MyLinkedList(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.head = ListNode(-1)
self.tail = ListNode(-1)
self.head.next = self.tail
self.tail.prev = self.head
self.length = 0
def get(self, index):
"""
Get the value of the index-th node in the linked list. If the index is invalid, return -1.
:type index: int
:rtype: int
"""
if index < 0 or index >= self.length:
return -1
return self.getNode(index).val
def getNode(self,index):
frist = self.head.next
for i in range(index):
frist = frist.next
return frist
def addAtHead(self, val):
"""
Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
:type val: int
:rtype: void
"""
frist = self.head.next
node = ListNode(val)
self.head.next = node
node.prev = self.head
node.next = frist
frist.prev = node
self.length += 1
def addAtTail(self, val):
"""
Append a node of value val to the last element of the linked list.
:type val: int
:rtype: void
"""
last = self.tail.prev
node = ListNode(val)
self.tail.prev = node
node.next = self.tail
last.next = node
node.prev = last
self.length += 1
def addAtIndex(self, index, val):
"""
Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
:type index: int
:type val: int
:rtype: void
"""
if index < 0 or index > self.length:
return
if index == self.length:
self.addAtTail(val)
return
old = self.getNode(index)
node = ListNode(val)
pre = old.prev
pre.next = node
node.prev = pre
node.next = old
old.prev = node
self.length += 1
def deleteAtIndex(self, index):
"""
Delete the index-th node in the linked list, if the index is valid.
:type index: int
:rtype: void
"""
if index < 0 or index >= self.length:
return
node = self.getNode(index)
pre = node.prev
next = node.next
pre.next = next
next.prev = pre
node.next = None
node.prev = None
self.length -= 1
class Solution(object):
def mergeTwoLists(self, l1, l2):
if l1 is None and l2 is None:
return None
elif l1 is None:
return l2
elif l2 is None:
return l1
sb ,headA,headB = ListNode(-1),l1,l2
headSB = sb
while headA and headB :
if headA.val > headB.val:
sb.next = headB
headB = headB.next
elif headA.val < headB.val:
sb.next = headA
headA = headA.next
else:
sb.next = headA
headA = headA.next
sb = sb.next
sb.next = headB
headB = headB.next
sb = sb.next
if headA:
sb.next = headA
elif headB:
sb.next = headB
return headSB.next
class Solution(object):
def addTwoNumbers(self, l1, l2):
rem = 0
dummy = ListNode(0)
p = dummy
while l1 or l2 or rem:
s = (l1.val if l1 else 0) + (l2.val if l2 else 0) + rem
rem = s/10
p.next = ListNode(s%10)
p = p.next
if l1:
l1 = l1.next
if l2:
l2 = l2.next
return dummy.next
bug free
的关键。class Solution(object):
if not head:
return None
p = head
while p:
if not p.child:
p = p.next
continue
p1 = p.child
p2 = p.child
while p2.next:
p2 = p2.next
p2.next = p.next
if p.next:
p.next.prev = p2
p.next = p1
p1.prev = p
p.child = None
p = p1
return head
class Solution(object):
def copyRandomList(self, head):
if not head:
return None
p = head
while p:
tmp = RandomListNode(p.label)
tmp.next = p.next
p.next = tmp
p = tmp.next
p = head
while p:
if p.random:
p.next.random = p.random.next
p = p.next.next
n,o = head.next,head
new = n
while n:
o.next = n.next
o = o.next
if not o:
break
n.next = o.next
n = n.next
return new
class Solution(object):
def rotateRight(self, head, k):
if head is None:
return None
if k == 0:
return head
count = 0
end = None
tmp = head
while tmp:
tmp = tmp.next
count += 1
head = self.nx(head,end)
k = k % count
tmp = head
while k != 0:
tmp = tmp.next
k -= 1
end = tmp
new_head = self.nx(head,end)
head.next = self.nx(end,None)
return new_head
def nx(self,head,end):
if head is None:
return None
pre,curr,next = None,head,head.next
while curr != end:
curr.next = pre
pre = curr
curr = next
next = next.next if next else None
return pre
class Solution(object):
def sortedListToBST(self, head):
return self.insterC(head,None)
def insterC(self,head,tail):
if head is tail:
return None
if head.next is tail:
return TreeNode(head.val)
fast = mid = head
while fast is not tail and fast.next is not tail:
mid = mid.next
fast = fast.next.next
tree = TreeNode(mid.val)
tree.left = self.insterC(head,mid)
tree.right = self.insterC(mid.next,tail)
return tree
bug free
,重点在于耐心,要有沉得住气。
class Solution(object):
def swapPairs(self, head):
if head is None:
return head
sb = ListNode(-1)
sb.next = head
a = sb
b = head
c = head.next
while c:
a.next = c
b.next = c.next
c.next = b
a = b
b = b.next
if b is None:
break
c = b.next
return sb.next
class Solution(object):
def sortList(self, head):
if head is None or head.next is None :
return head
mid = self.getMid(head)
left = self.sortList(head)
right = self.sortList(mid)
return self.merge(left,right)
def getMid(self,head):
m,k = head,head
sb = ListNode(-1)
sb.next = head
while k and k.next:
k = k.next.next
m = m.next
sb = sb.next
sb.next = None
return m
def merge(self,a,b):
sb = ListNode(-1)
curr = sb
while a and b:
if a.val >= b.val:
curr.next = b
b = b.next
else:
curr.next = a
a = a.next
curr = curr.next
if a:
curr.next = a
elif b:
curr.next = b
return sb.next
class Solution(object):
def reorderList(self, head):
if not head or not head.next:
return
if not head.next.next:
return
mid = self.get_mid(head)
head1 = self.nx(mid)
head = self.marge(head,head1)
def get_mid(self,head):
pre = ListNode(-1)
pre.next = head
fast,slow = head,head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
pre = pre.next
pre.next = None
return slow
def nx(self,head):
pre, cur, next = None,head,head.next
while cur:
cur.next = pre
pre = cur
cur = next
if next:
next = next.next
return pre
def marge(self,head1,head2):
dummy = ListNode(-1)
d = dummy
p, q = head1,head2
while p and q:
d.next = p
p = p.next
d = d.next
d.next = q
q = q.next
d = d.next
if q:
d.next = q
elif p:
d.next = p
return dummy.next
class Solution:
def numComponents(self, head, G):
if head is None or len(G) == 0:
return 0
count = 0
cur = head
GMap = {}
for v in G:
GMap[v] = 0
while cur:
if cur.val in GMap and (cur.next is None or cur.next.val not in GMap):
count += 1
cur = cur.next
return count
class Solution:
def splitListToParts(self, root, k):
p = root
size = 1
while p and p.next:
size += 1
p = p.next
mod = size % k
num = int(size / k)
res = []
tmp = root
while k != 0:
if mod != 0:
root = self.splitListNodeByLen(tmp,num+1)
mod -= 1
else:
root = self.splitListNodeByLen(tmp,num)
res.append(tmp)
tmp = root
k -= 1
return res
def splitListNodeByLen(self,root,l):
if not root or l <= 0:
return root
pre = None
while l > 0:
pre = root
root = root.next
l -= 1
pre.next = None
return root
class Solution:
def addTwoNumbers(self, l1, l2):
s1, s2 = [], []
tmp1 ,tmp2 = l1, l2
while tmp1:
s1.append(tmp1.val)
tmp1 = tmp1.next
while tmp2:
s2.append(tmp2.val)
tmp2 = tmp2.next
c = 0
node = ListNode(-1)
root = node
while len(s1) != 0 and len(s2) != 0:
num = s1[-1] + s2[-1] + c
s1 = s1[:len(s1)-1]
s2 = s2[:len(s2)-1]
if num >= 10:
node.val = num - 10
c = 1
else:
node.val = num
c = 0
if len(s1) == 0 and len(s2) == 0:
if c == 1:
node.next = ListNode(-1)
node = node.next
node.val = c
break
node.next = ListNode(-1)
node = node.next
while len(s1) != 0:
num = s1[-1] + c
if num >= 10:
node.val = num - 10
c = 1
else:
node.val = num
c = 0
s1 = s1[:len(s1)-1]
if len(s1) == 0:
if c == 1:
node.next = ListNode(-1)
node = node.next
node.val = c
break
node.next = ListNode(-1)
node = node.next
while len(s2) != 0:
num = s2[-1] + c
if num >= 10:
node.val = num - 10
c = 1
else:
node.val = num
c = 0
s2 = s2[:len(s2)-1]
if len(s2) == 0:
if c == 1:
node.next = ListNode(-1)
node = node.next
node.val = c
break
node.next = ListNode(-1)
node = node.next
return self.nx(root)
def nx(self,root):
if root is None:
return None
pre = None
next = root.next
while root:
root.next = pre
pre = root
root = next
if next:
next = next.next
return pre
class Solution:
def partition(self, head, x):
h1,h2 = ListNode(-1),ListNode(-1)
dummy = ListNode(-1)
dummy.next = head
pre = dummy
tmp1,tmp2 = h1,h2
while pre and pre.next:
tmp = pre.next
pre.next = None
pre = tmp
if tmp.val >= x:
tmp1.next = tmp
tmp1 = tmp1.next
else:
tmp2.next = tmp
tmp2 = tmp2.next
tmp2.next = h1.next
return h2.next
class Solution:
def reverseBetween(self, head, m, n):
count = 0
dummy = ListNode(-1)
pre = dummy
pre.next = head
while count < m-1:
pre = pre.next
count += 1
last = pre
while count < n:
last = last.next
count += 1
cur = pre.next
next = last.next
self.reverse(cur, last)
pre.next = last
head = dummy.next
cur.next = next
return head
def reverse(self, cur,last):
last.next = None;
pre = None
next = cur.next
while cur:
cur.next = pre
pre = cur
cur = next
if next:
next = next.next
class Solution(object):
def addTwoNumbers(self, l1, l2):
"""
"""
rem = 0
dummy = ListNode(0)
p = dummy
while l1 or l2 or rem:
s = (l1.val if l1 else 0) + (l2.val if l2 else 0) + rem
rem = s/10
p.next = ListNode(s%10)
p = p.next
if l1:
l1 = l1.next
if l2:
l2 = l2.next
return dummy.next
比如1->2->3,那么第一个不为9的数字为3,对3进行加1,变成4,右边没有节点了,所以不做处理,返回1->2->4。
再比如说8->9->9,找第一个不为9的数字为8,进行加1处理变成了9,然后把后面的数字都置0,得到结果9->0->0。
再来看9->9->9的情况,找不到不为9的数字,那么再前面新建一个值为0的节点,进行加1处理变成了1,把后面的数字都置0,得到1->0->0->0。
class Solution {
public:
ListNode* plusOne(ListNode* head) {
ListNode *cur = head, *right = NULL;
while (cur) {
if (cur->val != 9) right = cur;
cur = cur->next;
}
if (!right) {
right = new ListNode(0);
right->next = head;
head = right;
}
++right->val;
cur = right->next;
while (cur) {
cur->val = 0;
cur = cur->next;
}
return head;
}
};
Design Phone Directory(设计电话字典)
这里,讲的很清楚,不用我再说了,主要考察数据结构的设计,事实上,这种问题先根据经验想最常见的结构是否可以满足要求,不足则尝试组合数据结构,关键是定义你要知道哪些信息才能使你离正确答案更近一步,这需要练习与总结。
Convert Binary Search Tree to Sorted Doubly Linked List
将一颗二叉搜索树,转换为一个循环双连表。
非常经典的一道题,LeetCode收费,没办法OJ做,本地只有go的环境。所以用go写了个解。
此题利用分治思想,递归实现。原问题的模式可以看成:左子树成环 与 根成环合并,再与右子树成环合并。如此,
子问题就是: 1. "左子树 根 右子树"成环,2.合并。
递归基显然为根节点为空,直接返回。
递归的部分就是:左,右成环.
每次递归完成的事情就是: 将根成环,合并左根右三个环,合并动作就是将两个循环链表合并成一个连表的函数。
type TreeNode struct {
Left *TreeNode
Right *TreeNode
Val int
}
func BST2DLL(root *TreeNode) *TreeNode {
if root == nil{
return nil
}
aLast := BST2DLL(root.Left)
bLast := BST2DLL(root.Right)
root.Left = root
root.Right = root
aLast = Append(aLast,root)
aLast = Append(aLast,bLast)
return aLast
}
func Append(a,b *TreeNode) *TreeNode {
if a == nil{
return b
}
if b == nil{
return a
}
aLast := a.Left
bLast := b.Left
Join(aLast, b)
Join(bLast, a)
return a
}
func Join(a, b *TreeNode) {
a.Right = b
b.Left = a
}
30.Insert into a Cyclic Sorted List 在循环有序的链表中插入结点
这道题,就是考察多种情况的分析,第一种若是空链表时,插入值在最大与最小之间,小于最小或者大于最大时。将不同的情况考虑清楚即可。按照数轴。
这里很详细
31.k个一组翻转链表
利用哨兵减少指针操作,利用k作为计数器控制pre,left,right边界指针进行操作。根据计数器移动指针到正确位置,翻转链表。方法比较笨拙但是简单有效哈哈,以后有时间在优化,现在我还是尽量多涮题,见识更多的类型,收集更多数据先训练个基本模型。
class Solution:
def reverseKGroup(self, head, k):
"""
:type head: ListNode
:type k: int
:rtype: ListNode
"""
if k <= 0 or not head or not head.next:
return head
dummy = ListNode(-1)
dummy.next = head
cur = head
pre = dummy
while cur:
left,right = cur,cur
for i in range(k-1):
cur = cur.next
if cur is None:
return dummy.next
right = cur
self.nx(pre,left,right)
cur = left
pre = left
cur = cur.next
return dummy.next
def nx(self,pre,l,r):
if not pre or not l or not r or l == r:
return l
p ,cur,next = pre,l,l.next
tmp = r.next
while cur != tmp:
cur.next = p
p = cur
cur = next
if next:
next = next.next
pre.next = p
l.next = tmp
32.合并K个排序链表
此题可以做的很精妙,但是我这里先给出一个直觉式的暴力解法,以后有机会在不断优化。此题可以理解为,每次从list中选择最大的节点,从中剔除利用哨兵组成新的节点,然后在将链表更新,不断跌代,最后当list为空结束。
class Solution:
def mergeKLists(self, lists):
"""
:type lists: List[ListNode]
:rtype: ListNode
"""
dummy = ListNode(-1)
cur = dummy
while len(lists) != 0:
index = self.min(lists)
if not lists[index]:
del lists[index]
continue
cur.next = lists[index]
if lists[index] and lists[index].next:
lists[index] = lists[index].next
else:
del lists[index]
cur = cur.next
return dummy.next
def min(self,lists):
m = lists[0]
index = 0
i = 1
while i<len(lists):
if lists[i] and m and m.val > lists[i].val:
m = lists[i]
index = i
i += 1
return index
33.删除链表中的节点
思想比较巧妙,你只有给定节点,没有前驱节点的指针.巧妙的利用赋值的思想解决。
class Solution:
def deleteNode(self, node):
"""
:type node: ListNode
:rtype: void Do not return anything, modify node in-place instead.
"""
tmp = node.next
node.next = node.next.next
tmp.next = None
node.val = tmp.val
34.链表的中间结点
本来按照顺序此题放在前面,后来发现居然没写,在这里加上吧。前面多次提到,中间节点,快慢指针呀
class Solution(object):
def middleNode(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head and not head.next:
return head
slow,fast = head,head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
return slow