本篇将介绍关于链表算法的基本解体思路与经典问题,本篇不仅仅追求的是写出优秀的链表代码,更在意的是在有限时间内,如何写出bug free的链表代码。

链表问题的一般解题思路:

链表是一种利用不连续的内存块,通过在每块内存中存储下一块内存的指针而构造的线性存储结构,所以链表是线性表的一种形式。
链表问题是一种考察基本编码能力的问题,这类问题的特点是解法并不复杂,难点在于证明解法的正确性,以及如何编码。即,考察是否能编写出 bug free 的代码,少数会考察算法的数学证明问题。

  • 使用画图的技巧,释放一部分大脑空间。通过画出几个小规模的实例来思考问题,如果问题需要三个以上指针才能解决,那么画图的时间成本,是可以被接受的。
  • 通过画出一般情况下的案例,思考算法的主体解体思路。一般是3-5个节点的情况下。
  • 这时,更重要的是不要马上编码,而是在举出几个特殊实例,来验证一般思路在特殊场景下是否健壮,一般是0,1,2,3,4个数的链表节点,以及链表指针在头节点,与尾节点是否能正常工作。
  • 一定要有耐心,不要图快而提交代码,链表问题就是在锻炼你的代码review能力,思路很简单,难点在于耐下心来,冷静的分析。相信我,你总是能找到第一次编码中的问题。

那么什么是一般性的思路呢?
这是一种模式,链表问题的算法思想就是那么几种,掌握后套用即可,数学证明看几道典型案例即可,真正的难点在于编码,链表问题是涉及指针操作,极易出错,写出 bug free 是很不容的事情,所以最重要的就是要多加练习。

经典链表问题:

  1. 设计单链表:
    这里利用了哨兵的思想
class MyLinkedList:def __init__(self):"""初始化一个链表,使用一个永不存数据的节点作为哨兵,这样可以简化,在插入删除操作中对头节点与尾节点插入时的条件判读,进而提高速度。"""self.sb = ListNode(-1)self.le = 0def get(self, index):"""通过索引获取链表中的节点。"""head = self.sb.nextif  index < 0 or index >= self.le or head == None:return -1for i in range(index):head = head.nextreturn head.valdef addAtHead(self, val):"""在头节点的位置插入节点,哨兵思想使其不用判读非空情况。"""head = self.sb.nextself.sb.next = ListNode(val)self.sb.next.next = headself.le += 1def addAtTail(self, val):"""在尾部插入节点"""tmp = self.sbfor i in range(self.le):tmp = tmp.nexttmp.next = ListNode(val)self.le += 1def addAtIndex(self, index, val):"""在指定索引的位置插入节点"""if  index < 0 or index > self.le :return -1pre = self.sbfor i in range(index):pre  = pre.nexttmp = pre.nextpre.next = ListNode(val)pre.next.next = tmpself.le += 1def deleteAtIndex(self, index):"""指定index的位置删除节点"""if index < 0 or index >= self.le:returnpre = self.sbfor  i in range(index) :pre = pre.nextpre.next = pre.next.nextself.le -= 1
  1. 判断链表是否有环

双指针思想经典三问:判读链表是否有环,链表与环的交点,求环的长度。
定义两个指针,从头开始,一个每次走2步,
一个走一步,二者相交则有环,
不相交走两步的指针先到尾部,则判读无环。

class Solution(object):def hasCycle(self, head):if head is None or head.next is None:return Falsefast ,slow = head,headwhile fast is not  None and fast.next is not None:fast = fast.next.nextslow = slow.nextif slow == fast:return Truereturn False
  1. 判断环的入口点

快慢指针相遇后,将快指针重新指向头节点,
两个指针开始同时走,每次走一步。当再次相遇时,
即是环的入口点。


class Solution(object):def detectCycle(self, head):if head == None or head.next == None:return Nonefast = head.next.nextslow = head.nextwhile fast != None and fast.next != None:fast = fast.next.nextslow = slow.nextif fast == slow:breakif fast == None or fast.next == None:return Nonefast = headwhile fast != slow:fast = fast.nextslow = slow.nextreturn fast
  1. 求解环的长度
    求出相遇点,一个指针继续走,每次一步,记录走的次数,
    另一个指针原地不动,再次相遇次数就是长度。
    很简单,脑补吧。
  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)
这个式子表明链表中不包括环的长度 等于 相遇点到第一个相交点的长度加上环的长度的整数倍。

  1. 求两个链表相交点
    两个指针,分别指向两个链表的头,当不断的往前走,
    当谁到了尾部,则回到另一个链表的头部。相交的链表,
    先到尾部的一定是短的链表,回到长链表的头部,
    当长链表出发的指针也到尾时,
    其已经走过了正好是差一步到长短链表相差的节点数,
    此时长链表指针回到短链表的头部,
    二者位置对齐,在此向前走,最终相遇,即为交点。
class Solution(object):def getIntersectionNode(self, headA, headB):p1 ,p2 = headA,headBwhile p1 != p2:p1 = headB if p1 == None else p1.nextp2 = headA if p2 == None else p2.nextreturn p1
  1. 删除单链表的第n个倒数节点
    两个指针,一个先走n步,然后两个指针再一起走,
    当先走的指针到达尾部,则后走的指针为待删除节点前驱节点。
    倒数第n,其实就是正数第L-n+1,L为链表长度,
    所以L-n正好是,要删除节点的前驱。
    先走n步,之后再走的就是L-n了,
    这样第二指针正好就是在第L-n的节点处。
class Solution(object):def removeNthFromEnd(self, head, n):pre ,last = head,headfor i in range(n):pre = pre.nextif pre == None:return head.nextwhile pre.next != None:pre = pre.nextlast = last.nextlast.next = last.next.nextreturn head

指针操作
这类问题就是考察对指针操作的边界条件的检查,对编码能力有所要求。

  1. 单链表反转
    两个指针实现c版:

class Solution(object):def reverseList(self, head):"""使用三个指针,完成单链表操作。"""if head == None:return Nonepre = Nonenext = head.nextwhile head != None:head.next = prepre = headhead = nextif next != None:next = next.nextreturn pre
  1. 移除链表中的所有值为val的元素
    考虑边界条件,例外情况,第一次循环是关键,
    通过考察0,1,2,3节点数的链表得到遍历前驱删除Val的方法,
    无法适应头节点以及头节点是连续需要删除的Val的情况。

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 Noneelse:head = head.nextpre = headwhile pre.next != None:if pre.next.val == val:pre.next = pre.next.nextelse:pre = pre.nextreturn head
  1. 奇偶链表
    通过两个指针相互next,交错前进,巧妙的实现奇偶节点的独立遍历,然后将其独自串联,并合并。
    难点在于指针关系不要搞错,可以使用画图的技巧辅助思考,链表问题考验编程实现能力,要沉得住气,耐心检查这才是关键。
class Solution(object):def oddEvenList(self, head):if head is None:return headodd = headeven = head.nextif even is None:return headodd_prev = oddeven_prev = eveneven_head = evenwhile even is not None and even.next is not None:odd = even.nexteven = odd.nextodd_prev.next = oddeven_prev.next = evenodd_prev = oddeven_prev = evenodd.next = even_headreturn head
  1. 回文链表
    快慢指针找到中点,将后半部分链表逆序,再次从头和中点位置遍历链表,逐一比较其值来判断回文。
    这里的重点是在编码过程中适当的抽象,可以简化代码逻辑,大大提高编写代码的正确性和可读性。
class Solution(object):def isPalindrome(self, head):if head is None or head.next is None:return Truemid = self.getMid(head)mid.next = self.getFanZhuan(mid.next)p,q = head,mid.nextwhile q is not None and p.val == q.val:q = q.nextp = p.nextself.getFanZhuan(mid.next)return q == Nonedef getMid(self,head):fast,slow = head.next,headwhile fast is not None and fast.next is not None:fast = fast.next.nextslow = slow.nextreturn slowdef getFanZhuan(self,head):if head is None or head.next is None:return headpre,curr,next = None,head,head.nextwhile curr is not None:curr.next = prepre = currcurr = nextnext = None if next is None else next.nextreturn pre
  1. 双链表设计
    双链表的设计要比单链表还要复杂一些,但是如果领会哨兵在链表中的运用的话,那么还是很轻松的,不过这道题我居然做了3天,好吧我每天早上七点半会做一道题.总共应该花了40分钟,还是我太菜了,还有就是当设计数据结构这种题,要编写多个方法,我们在review代码时,还要考虑多个函数相互调用的影响,
    也就是类级别的“不变式”,需要深入的思考每个行为的作用,仔细理解每个行为发生的前置后置条件,以及在整个对象生命周期中都要维护的一种不变的状态是什么。
class MyLinkedList(object):def __init__(self):"""Initialize your data structure here."""self.head = ListNode(-1)self.tail = ListNode(-1)self.head.next = self.tailself.tail.prev = self.headself.length = 0def 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 -1return self.getNode(index).valdef getNode(self,index):frist = self.head.nextfor i in range(index):frist = frist.nextreturn 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.nextnode = ListNode(val)self.head.next = nodenode.prev = self.headnode.next = fristfrist.prev = nodeself.length += 1def 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.prevnode = ListNode(val)self.tail.prev = nodenode.next = self.taillast.next = nodenode.prev = lastself.length += 1def 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:returnif index == self.length:self.addAtTail(val)returnold = self.getNode(index)node = ListNode(val)pre = old.prevpre.next = nodenode.prev = prenode.next = oldold.prev = nodeself.length += 1def 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:returnnode = self.getNode(index)pre = node.prevnext = node.nextpre.next = nextnext.prev = prenode.next = Nonenode.prev = Noneself.length -= 1
  1. 拼接两个有序链表
    此题关键是对例外情况的处理,两个输入的链表可能都为空,可能长短不一。都需要单独处理
class Solution(object):def mergeTwoLists(self, l1, l2):if l1 is None and l2 is None:return Noneelif l1 is None:return l2elif l2 is None:return l1sb ,headA,headB = ListNode(-1),l1,l2headSB = sbwhile headA  and  headB :if headA.val > headB.val:sb.next = headBheadB = headB.nextelif headA.val < headB.val:sb.next = headAheadA = headA.nextelse:sb.next = headAheadA = headA.nextsb = sb.nextsb.next = headBheadB = headB.nextsb = sb.nextif headA:sb.next = headAelif headB:sb.next = headBreturn headSB.next
  1. 将两个逆序链表表示的数字相加
    此题使用哨兵简化了代码,同时注意对进位的处理,
    还是非常好做的。
class Solution(object):def addTwoNumbers(self, l1, l2):rem = 0dummy = ListNode(0)p = dummywhile l1 or l2 or rem:s = (l1.val if l1 else 0)  + (l2.val if l2 else 0) + remrem = s/10p.next = ListNode(s%10)p = p.nextif l1:l1 = l1.nextif l2:l2 = l2.nextreturn dummy.next
  1. 扁平化多级双向链表
    具体解释看这里
    此题重点考察,对指针的处理,可以画图辅助思考,
    遍历链表主体,没有子链表则继续遍历。
    整个程序在一个循环内完成。
    有子链表则遍历子链表,得到子链表的头与尾的指针,将子链表的尾节点的next指向主体链表当前指针的next,如果此next不空,则将其prev指针指向子链表的尾节点,然后再次更新子链表的头节点,使其当前主链表遍历节点的next指针指向子链表的头节点,并且子链表头节点prev指针反过来指向它,并将其指向子孩子的指针赋值为None,将当前指针直接指向合并后子链表的尾指针的next,以此减少其遍历次数。往复迭代,具体可根据代码,画图理解。
    此题复杂在指针比较多,画图赋值分析,是写出bug free的关键。
class Solution(object):if not head:return Nonep = headwhile p:if not p.child:p = p.nextcontinuep1 = p.childp2 = p.childwhile p2.next:p2 = p2.nextp2.next = p.nextif p.next:p.next.prev = p2p.next = p1p1.prev = pp.child = Nonep = p1return head
  1. 复制带随机指针的链表
    此题关键是对随机指针的理解,复制节点的时候对随机指针的复制是相对的,必须保证随机指针指向的节点相对不变。
    详情点击这里
class Solution(object):def copyRandomList(self, head):if not head:return Nonep = headwhile p:tmp = RandomListNode(p.label)tmp.next = p.nextp.next = tmpp = tmp.nextp = headwhile p:if p.random:p.next.random = p.random.nextp = p.next.nextn,o = head.next,headnew = nwhile n:o.next = n.nexto = o.nextif not o:breakn.next = o.nextn = n.nextreturn new
  1. 旋转链表
    此题最好的解法可以在常数空间,线性时间完成.
    先整体逆序,在找到分区点,对两部分分别逆序。
    注意的是,逆序时要指定尾指针,因为链表时连续的

class Solution(object):def rotateRight(self, head, k):if head is None:return Noneif k == 0:return headcount = 0end = Nonetmp = headwhile tmp:tmp = tmp.nextcount += 1head = self.nx(head,end)k = k % counttmp = headwhile k != 0:tmp = tmp.nextk -= 1end = tmpnew_head = self.nx(head,end)head.next = self.nx(end,None)return new_headdef nx(self,head,end):if head is None:return Nonepre,curr,next = None,head,head.nextwhile curr != end:curr.next = prepre = currcurr = nextnext = next.next if next else Nonereturn pre
  1. 有序链表转换二叉搜索树
    详情
    此题本质就是对线性结构与树结构关系的考察,
    如何用线性结构存储一个二叉搜索树?有序的线性表即可,线性表可以是数组,也可以是链表。如此的话,如何用有序线性表反过来构造二叉搜索树? 二分法是关键,二分法就是有序线性表与二叉搜索的映射关系,那么如何取二分法中点?
    数组可以计算index下标,链表就可以用快慢指针,ok 此时问题已经解决,请看代码。
class Solution(object):def sortedListToBST(self, head):return self.insterC(head,None)def insterC(self,head,tail):if head is tail:return Noneif head.next is tail:return TreeNode(head.val)fast = mid = headwhile fast is not tail and fast.next is not tail:mid = mid.nextfast = fast.next.nexttree = TreeNode(mid.val)tree.left = self.insterC(head,mid)tree.right = self.insterC(mid.next,tail)return tree
  1. 两两交换链表中的节点
    此题类似链表逆序的思想,不过只要正确理解链表指针的关系,关注前驱,当前节点,后继三者的变换。注意检查代码,对边界条件的检查,例外情况的分析即可写出bug free,重点在于耐心,要有沉得住气。
    详情

class Solution(object):def swapPairs(self, head):if head is None:return headsb = ListNode(-1)sb.next = heada = sbb = headc = head.nextwhile c:a.next = cb.next = c.nextc.next = ba = bb = b.nextif b is None:breakc = b.nextreturn sb.next
  1. 排序链表
    此题本质上就是对归并排序的链表实现,题目要求O(nlong),链表实现归并排序的优势就是可以在常数空间下完成归并,缺点是查询中点不能想数组一样在常数时间内完成,所以其性能上比数组形式慢一些,但也在同一数量级,并且不需要连续空间存储,以及额外空间,某些情况下也是一个不错的选择。
    详情
class Solution(object):def sortList(self, head):if head is None or head.next is None :return headmid =  self.getMid(head)left = self.sortList(head)right = self.sortList(mid)return self.merge(left,right)def getMid(self,head):m,k = head,headsb = ListNode(-1)sb.next = headwhile k and k.next:k = k.next.nextm = m.nextsb = sb.nextsb.next = Nonereturn mdef merge(self,a,b):sb = ListNode(-1)curr = sbwhile a and b:if a.val >= b.val:curr.next = bb = b.nextelse:curr.next = aa = a.nextcurr = curr.nextif a:curr.next = aelif b:curr.next = breturn sb.next
  1. 重排链表
    详情
    此题关键在于正确理解题意,写几个测试用例体会一下发现其是三种常见链表行为的组合,获取链表中点,逆序链表,交叉合并链表。
class Solution(object):def reorderList(self, head):if not head or not head.next:returnif not head.next.next:returnmid = self.get_mid(head)head1 =  self.nx(mid)head =  self.marge(head,head1)def get_mid(self,head):pre = ListNode(-1)pre.next = headfast,slow = head,headwhile fast and fast.next:fast = fast.next.nextslow = slow.nextpre = pre.nextpre.next = Nonereturn slowdef nx(self,head):pre, cur, next = None,head,head.nextwhile cur:cur.next = prepre = curcur = nextif next:next = next.nextreturn predef marge(self,head1,head2):dummy = ListNode(-1)d = dummyp, q = head1,head2while p and q:d.next = pp = p.nextd = d.nextd.next = qq = q.nextd = d.nextif q:d.next = qelif p:d.next = preturn dummy.next
  1. 链表组件
    理解题中定义组件的概念的解题的关键,利用map key的特性以及确定计数策略即可解题,此题关键在于正确理解新概念,并识别出算法中经典的模式-集合查找。
class Solution:def numComponents(self, head, G):if head is None or len(G) == 0:return 0count = 0cur = headGMap = {}for v in G:GMap[v] = 0while cur:if cur.val in GMap and (cur.next is None or cur.next.val not in GMap):count += 1                cur = cur.nextreturn count
  1. 分隔链表01
    分成两步求解,第一找到分割规则。
    第二按规则切分。
    根据题意规则很简单,就是先平分再均摊余数
    此类问题在于找到规律,将其数学化表达并封装好操作行为。
class Solution:def splitListToParts(self, root, k):p = rootsize = 1while p and p.next:size += 1p = p.nextmod = size % knum = int(size / k)res = []tmp = rootwhile k != 0:if mod != 0:root = self.splitListNodeByLen(tmp,num+1)mod -= 1else:root = self.splitListNodeByLen(tmp,num)res.append(tmp)tmp = rootk -= 1return resdef splitListNodeByLen(self,root,l):if not root or l <= 0:return rootpre = Nonewhile l > 0:pre = rootroot = root.nextl -= 1pre.next = Nonereturn root
  1. 两数相加 II
    此题是连表相加的升级版,连表不再逆序需要自行处理,解体模式即是先一般到特殊的处理方式,此题我的解法比较糟糕,没有借鉴最佳实践,只是我自己想出来的,既然无法从低位开始,又是运算问题,直接想到使用栈这种辅助结构来处理。
    但是期间引入的额外的复杂性,之后再想如何优化。
class Solution:def addTwoNumbers(self, l1, l2):s1, s2 = [], []tmp1 ,tmp2 = l1, l2while tmp1:s1.append(tmp1.val)tmp1 = tmp1.nextwhile tmp2:s2.append(tmp2.val)tmp2 = tmp2.nextc = 0node = ListNode(-1)root = nodewhile len(s1) != 0 and len(s2) != 0:num = s1[-1] + s2[-1] + cs1 = s1[:len(s1)-1]s2 = s2[:len(s2)-1]if num >= 10:node.val = num - 10c = 1else:node.val = numc = 0if len(s1) == 0 and len(s2) == 0:if c == 1:node.next = ListNode(-1)node = node.nextnode.val =  cbreaknode.next = ListNode(-1)node = node.nextwhile len(s1) != 0:num = s1[-1] + cif num >= 10:node.val = num - 10c = 1else:node.val = numc = 0s1 = s1[:len(s1)-1]if len(s1) == 0:if c == 1:node.next = ListNode(-1)node = node.nextnode.val = cbreaknode.next = ListNode(-1)node = node.nextwhile len(s2) != 0:num = s2[-1] + cif num >= 10:node.val = num - 10c = 1else:node.val = numc = 0s2 = s2[:len(s2)-1]if len(s2) == 0:if c == 1:node.next = ListNode(-1)node = node.nextnode.val = cbreaknode.next = ListNode(-1)node = node.nextreturn self.nx(root)def nx(self,root):if root is None:return Nonepre = Nonenext = root.nextwhile root:root.next = prepre = rootroot = nextif next:next = next.nextreturn pre
  1. 分隔链表02
    此题是 快排分区中的连表实现。
class Solution:def partition(self, head, x):h1,h2 = ListNode(-1),ListNode(-1)dummy = ListNode(-1)dummy.next = headpre = dummytmp1,tmp2 = h1,h2while pre and pre.next:tmp = pre.nextpre.next = Nonepre = tmpif tmp.val >= x:tmp1.next = tmptmp1 = tmp1.nextelse:tmp2.next = tmptmp2 = tmp2.nexttmp2.next = h1.nextreturn h2.next
  1. 反转链表 II
    此题指针较多画图辅助更加高效。
class Solution:def reverseBetween(self, head, m, n):count = 0dummy = ListNode(-1)pre = dummypre.next = headwhile count < m-1:pre = pre.nextcount += 1last = prewhile count < n:last = last.nextcount += 1cur = pre.nextnext = last.nextself.reverse(cur, last)pre.next = lasthead = dummy.nextcur.next = nextreturn headdef reverse(self, cur,last):last.next = None;pre = Nonenext = cur.nextwhile cur:cur.next = prepre = curcur = nextif next:next = next.next
  1. 两数相加
    运用哨兵模式
class Solution(object):def addTwoNumbers(self, l1, l2):""""""rem = 0dummy = ListNode(0)p = dummywhile l1 or l2 or rem:s = (l1.val if l1 else 0)  + (l2.val if l2 else 0) + remrem = s/10p.next = ListNode(s%10)p = p.nextif l1:l1 = l1.nextif l2:l2 = l2.nextreturn dummy.next
  1. Plus One Linked List(连表加1运算)
    leetCode付费题,很简单但很巧妙。不是我想的原文
    思路是遍历链表,找到右起第一个不为9的数字,如果找不到这样的数字,说明所有数字均为9,那么在表头新建一个值为0的新节点,进行加1处理,然后把右边所有的数字都置为0即可。举例来说:

比如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;}
};
  1. Design Phone Directory(设计电话字典)
    这里,讲的很清楚,不用我再说了,主要考察数据结构的设计,事实上,这种问题先根据经验想最常见的结构是否可以满足要求,不足则尝试组合数据结构,关键是定义你要知道哪些信息才能使你离正确答案更近一步,这需要练习与总结。

  2. Convert Binary Search Tree to Sorted Doubly Linked List
    将一颗二叉搜索树,转换为一个循环双连表。
    非常经典的一道题,LeetCode收费,没办法OJ做,本地只有go的环境。所以用go写了个解。

此题利用分治思想,递归实现。原问题的模式可以看成:左子树成环 与 根成环合并,再与右子树成环合并。如此,
子问题就是: 1. "左子树 根 右子树"成环,2.合并。
递归基显然为根节点为空,直接返回。
递归的部分就是:左,右成环.
每次递归完成的事情就是: 将根成环,合并左根右三个环,合并动作就是将两个循环链表合并成一个连表的函数。

type TreeNode struct {Left  *TreeNodeRight *TreeNodeVal   int
}
func BST2DLL(root *TreeNode) *TreeNode {if root == nil{return nil}aLast  := BST2DLL(root.Left)bLast := BST2DLL(root.Right)root.Left = rootroot.Right = rootaLast = 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.LeftbLast := b.LeftJoin(aLast, b)Join(bLast, a)return a
}
func Join(a, b *TreeNode) {a.Right = bb.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 headdummy = ListNode(-1)dummy.next = headcur = headpre = dummywhile cur:left,right = cur,curfor i in range(k-1):cur = cur.nextif cur is None:return dummy.nextright = curself.nx(pre,left,right)cur = leftpre = leftcur = cur.nextreturn dummy.nextdef nx(self,pre,l,r):if not pre or not l or not r or l == r:return lp ,cur,next = pre,l,l.nexttmp = r.nextwhile cur != tmp:cur.next = pp = curcur = nextif next:next = next.nextpre.next = pl.next = tmp

32.合并K个排序链表
此题可以做的很精妙,但是我这里先给出一个直觉式的暴力解法,以后有机会在不断优化。此题可以理解为,每次从list中选择最大的节点,从中剔除利用哨兵组成新的节点,然后在将链表更新,不断跌代,最后当list为空结束。

class Solution:def mergeKLists(self, lists):""":type lists: List[ListNode]:rtype: ListNode"""dummy = ListNode(-1)cur = dummywhile len(lists) != 0:index = self.min(lists)if not lists[index]:del lists[index]continuecur.next = lists[index]if lists[index] and lists[index].next:lists[index] = lists[index].nextelse:del lists[index]cur = cur.nextreturn dummy.nextdef min(self,lists):m = lists[0]index = 0i = 1while i<len(lists):if lists[i] and m and m.val > lists[i].val:m = lists[i]index = ii += 1return index

33.删除链表中的节点
思想比较巧妙,你只有给定节点,没有前驱节点的指针.巧妙的利用赋值的思想解决。

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

34.链表的中间结点
本来按照顺序此题放在前面,后来发现居然没写,在这里加上吧。前面多次提到,中间节点,快慢指针呀

class Solution(object):def middleNode(self, head):""":type head: ListNode:rtype: ListNode"""if not head and not head.next:return headslow,fast = head,headwhile fast and fast.next:fast = fast.next.nextslow = slow.nextreturn slow

链表问题全面汇总与解析相关推荐

  1. 华为服务器万兆网卡驱动型号,思科华为常见的10G万兆光模块型号汇总与解析

    原标题:思科华为常见的10G万兆光模块型号汇总与解析 随着10G以太网技术的成熟化和普及化,不同型号的万兆光模块相继被设计出,且被广泛应用于不同的网络场景中进行数据传输.对比思科.华为等供应商提供的万 ...

  2. 【CV知识点汇总与解析】|激活函数篇

    [CV知识点汇总与解析]|激活函数篇 [写在前面] 本系列文章适合Python已经入门.有一定的编程基础的学生或人士,以及人工智能.算法.机器学习求职的学生或人士.系列文章包含了深度学习.机器学习.计 ...

  3. 转载:Elasticsearch面试题汇总与解析

    原始链接:https://www.wenyuanblog.com/blogs/elasticsearch-interview-questions.html Elasticsearch面试题汇总与解析 ...

  4. java2019 数据结构算法面试题_GitHub - sjyw/java-interview: 史上最全Java面试题汇总与解析(505道):2019最新版...

    Java 面试全解析 知识树 课程亮点 500道 Java 常见面试题 + 14万字核心知识解析 丰富的 Java 技术栈:基础和框架,线程池和锁优化,SpringBoot 和分布式消息队列,数据结构 ...

  5. c语言单链表数据显示,C++_C语言单链表常见操作汇总,C语言的单链表是常用的数据结 - phpStudy...

    #include #include //定义单链表结构体 typedef int ElemType; typedef struct Node { ElemType data; struct Node ...

  6. leetcode24. 两两交换链表中的节点(思路+解析)

    一:题目 二:思路 思路: 1.分析题意 这是相邻结点进行交换 如果是4个结点 那么1和2交换 3和4交换 如果是3个结点 那么就1和2进行交换 3不动 2.这里我们定义一个虚拟头节点方便操作,我们只 ...

  7. 单链表的逆置算法解析

    问题描述: 比方说,一个不带头节点的单链表原来从头到尾存储的是(1, 2, 3, 4, 5),逆置后链表从头到尾存储的是(5,4,3,2,1) 解决思路: 我暂时想到的有三种解法: ​ 1)从头到尾依 ...

  8. 26道JavaScript烧脑面试题汇总与解析

    今天为大家精选了26道稍微有点烧脑的JavaScript题,主要考察的是类型判断.作用域.this指向.原型.事件循环等知识点,每道题都配有笔者详细傻瓜式的解析,偏向于初学者,大佬请随意. 第1题 l ...

  9. java开发岗位面试题汇总及解析2

    二.Java IO 1. 讲讲IO里面的常见类,字节流.字符流.接口.实现类.方法阻塞. 答: 1)所有输入流类都是抽象类InputStream(字节输入流),或者抽象类Reader(字符输入流)的子 ...

最新文章

  1. linux宝塔如何开启gzip,宝塔nginx如何开启网站gzip
  2. RoboGuice 解析
  3. 由树先序遍历和中序遍历输出其后续遍历
  4. 4-1-getOutputStream()或getWriter()发送响应消息体及分析为什么不能同时使用
  5. 你见过股市亏最惨的有多惨?
  6. 购买域名以及申请证书
  7. javascript 怎样才能确定参数变量的个数呢?
  8. MySQL教程(十二)—— 数据的导入与导出
  9. linux搭建phantomjs+webdriver+testng+ant自动化工程
  10. ae万能弹性表达式_18种常用AE表达式解析【建议收藏】
  11. 贴吧android客户端,百度贴吧推出Android平台手机客户端
  12. LRc2022 M1原生支持功能介绍,Lightroom Classic 2022 Mac M1专用 ,解决lr闪退打不开卡死等一系列问题
  13. 计算机u盘病毒清除方式,彻底清除u盘病毒有什么方法呢
  14. Lenovo 10w 平板评测
  15. 计算机未响应硬盘,最近电脑打开磁盘或文件夹老程序未响应为什么啊,有什么办法可以解决?...
  16. Ubuntu+Sendmail+Dovecot+Openwebmail 邮件服务器搭建完全解决方案
  17. 三款红米手机搭载Elliptic Labs智能传感技术
  18. 区块链智能合约教材出版
  19. Tuxera NTFS Mac2022mac写入ntfs移动硬盘插件
  20. 发那科机器人协同作业程序,博途西门子1200搭配-威纶通触摸屏

热门文章

  1. 构件CAD平面图绘制
  2. 腾讯 QQ2007 Beta2 阿瑞斯精简版 v0.7.6.2
  3. TCP:当初取代NCP,如今害怕被取代
  4. QQ2007Bate3协议分析——登录认证篇
  5. 《SQL Server 2008从入门到精通》--20180717
  6. 专业调酒机器人Foxtender问世
  7. sqlyog-数据库同步
  8. VScode 更改python编译器版本
  9. PostgreSQL安装遇到的坑:Problem running post-install step/password authentication failed for user postgres
  10. 产品经理日常思考复盘:产品经理想做出好产品,先成为时间管理大师