zoukankan      html  css  js  c++  java
  • 链表问题全面解析

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

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

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

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

    经典链表问题:

    1. 设计单链表:
      这里利用了哨兵的思想
    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
    
    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
    
    1. 判断环的入口点

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

    
    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
    
    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,headB
            while p1 != p2:
                p1 = headB if p1 == None else p1.next
                p2 = headA if p2 == None else p2.next
            return 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,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
    

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

    1. 单链表反转
      两个指针实现c版:
    
    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
    
    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 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
    
    1. 奇偶链表
      通过两个指针相互next,交错前进,巧妙的实现奇偶节点的独立遍历,然后将其独自串联,并合并。
      难点在于指针关系不要搞错,可以使用画图的技巧辅助思考,链表问题考验编程实现能力,要沉得住气,耐心检查这才是关键。
    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
    
    1. 回文链表
      快慢指针找到中点,将后半部分链表逆序,再次从头和中点位置遍历链表,逐一比较其值来判断回文。
      这里的重点是在编码过程中适当的抽象,可以简化代码逻辑,大大提高编写代码的正确性和可读性。
    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
    
    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.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
    
    
    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
    
    1. 将两个逆序链表表示的数字相加
      此题使用哨兵简化了代码,同时注意对进位的处理,
      还是非常好做的。
    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. 扁平化多级双向链表
      具体解释看这里
      此题重点考察,对指针的处理,可以画图辅助思考,
      遍历链表主体,没有子链表则继续遍历。
      整个程序在一个循环内完成。
      有子链表则遍历子链表,得到子链表的头与尾的指针,将子链表的尾节点的next指向主体链表当前指针的next,如果此next不空,则将其prev指针指向子链表的尾节点,然后再次更新子链表的头节点,使其当前主链表遍历节点的next指针指向子链表的头节点,并且子链表头节点prev指针反过来指向它,并将其指向子孩子的指针赋值为None,将当前指针直接指向合并后子链表的尾指针的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
    
    
    1. 复制带随机指针的链表
      此题关键是对随机指针的理解,复制节点的时候对随机指针的复制是相对的,必须保证随机指针指向的节点相对不变。
      详情点击这里
    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
    
    1. 旋转链表
      此题最好的解法可以在常数空间,线性时间完成.
      先整体逆序,在找到分区点,对两部分分别逆序。
      注意的是,逆序时要指定尾指针,因为链表时连续的
    
    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
    
    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 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
    
    
    1. 两两交换链表中的节点
      此题类似链表逆序的思想,不过只要正确理解链表指针的关系,关注前驱,当前节点,后继三者的变换。注意检查代码,对边界条件的检查,例外情况的分析即可写出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
    
    1. 排序链表
      此题本质上就是对归并排序的链表实现,题目要求O(nlong),链表实现归并排序的优势就是可以在常数空间下完成归并,缺点是查询中点不能想数组一样在常数时间内完成,所以其性能上比数组形式慢一些,但也在同一数量级,并且不需要连续空间存储,以及额外空间,某些情况下也是一个不错的选择。
      详情
    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
    
    1. 重排链表
      详情
      此题关键在于正确理解题意,写几个测试用例体会一下发现其是三种常见链表行为的组合,获取链表中点,逆序链表,交叉合并链表。
    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
    
    
    1. 链表组件
      理解题中定义组件的概念的解题的关键,利用map key的特性以及确定计数策略即可解题,此题关键在于正确理解新概念,并识别出算法中经典的模式-集合查找。
    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
    
    
    1. 分隔链表01
      分成两步求解,第一找到分割规则。
      第二按规则切分。
      根据题意规则很简单,就是先平分再均摊余数
      此类问题在于找到规律,将其数学化表达并封装好操作行为。
    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
    
    1. 两数相加 II
      此题是连表相加的升级版,连表不再逆序需要自行处理,解体模式即是先一般到特殊的处理方式,此题我的解法比较糟糕,没有借鉴最佳实践,只是我自己想出来的,既然无法从低位开始,又是运算问题,直接想到使用栈这种辅助结构来处理。
      但是期间引入的额外的复杂性,之后再想如何优化。
    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
    
    1. 分隔链表02
      此题是 快排分区中的连表实现。
    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
    
    1. 反转链表 II
      此题指针较多画图辅助更加高效。
    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
    
    1. 两数相加
      运用哨兵模式
    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. 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  *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
    
  • 相关阅读:
    Spring Boot缓存实战 Redis 设置有效时间和自动刷新缓存
    快速排序
    JDK,JRE,JVM区别与联系
    RocketMQ
    IO、NIO、AIO 内部原理分析
    java设计模式-回调、事件监听器、观察者模式
    Spring源码相关
    java单例模式几种实现方式
    RabbitMQ学习笔记二:Java使用RabbitMQ
    RabbitMQ学习笔记一:本地Windows环境安装RabbitMQ Server
  • 原文地址:https://www.cnblogs.com/jueyoq/p/11258220.html
Copyright © 2011-2022 走看看