剑指Offer and Leetcode刷题总结之二:链表

目录

 

剑指06:从尾到头打印链表

剑指18:删除链表的节点/Leetcode237:删除链表中的节点

剑指22:链表中倒数第K个节点/Leetcode19:删除链表的倒数第N个节点

Leetcode141:环形链表 / Leetcode142:环形链表II / 剑指23:链表中环的入口节点

剑指24/Leetcode206:链表反转

剑指35:复杂链表的赋值

剑指52:两个链表的第一个公共节点 / Leetcode160:相交链表

Leetcode21:合并两个有序链表

Leetcode23:合并K个有序链表(复杂度分析参考)

Leetcode24:两两交换链表中的节点

Leetcode25:K 个一组翻转链表

Leetcode83:删除排序链表中的重复元素

Leetcode82:删除排序链表中的重复元素 II

Leetcode203:移除链表元素

Leetcode234:回文链表

Leetcode876:链表的中间节点


剑指06:从尾到头打印链表

基本思路:顺序打印之后倒序即可;

class Solution06(object):
    """
    输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
    
    方法1:顺序打印,再reverse;
    总结:
    时间复杂度,首先遍历一遍生成顺序list O(n);其次reverse过程时间复杂度O(n),总结来说O(n)
    """
    def reversePrint(self, head):
        if head == None:
            return []
        elif head.next == None:
            return [head.val]
        else:
            p = head
            ans = [p.val]
        while(p.next is not None):
            p = p.next
            ans.append(p.val)
        ans.reverse()
        return ans

    """
    方法2:
    这种先进后出的场景一定要想到栈这种数据结构stack
    时间复杂度:仍然先遍历一遍链表,得到顺序list,复杂度为O(n),倒叙输出的复杂度也为O(n)
    空间复杂度不好判断,基于reverse操作是否用到了额外的空间;
    """
    def reversePrint_02(self, head):
        stack = []
        while head:
            stack.append(head.val)
            head = head.next
        return stack[::-1] # 倒叙输出,后进先出,即stack的思想
    
    """
    方法3:
    递归思想!!!!待继续研究!!!!!!
    """
    def reversePrint_03(self, head):
        """
        if head is None:
            return []
        return self.reversePrint_03(head.next) + [head.val]
        """
        return self.reversePrint_03(head.next) + [head.val] if head else []

剑指18:删除链表的节点/Leetcode237:删除链表中的节点

题目:给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点

示例:输入: head = [4,5,1,9], val = 5;输出: [4,1,9];解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

基本思路:遍历查找

class Solution18(object):
    """
    方法1:
    遍历查找删除
    时间复杂度--最好:O(1); 最坏:O(n); 平均:O(n/2),其实也就是O(n)
    空间复杂度--O(1)
    """
    def deleteNode(self, head, val):
        s = ListNode(0) # 哨兵节点
        s.next = head
        p = s
        while p.next:
            if p.next.val == val:
                p.next = p.next.next
                return s.next
            else:
                p = p.next
        return s.next

剑指22:链表中倒数第K个节点/Leetcode19:删除链表的倒数第N个节点

基本思路:双指针

class Solution22(object):
    """
    输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
    """
    """
    方法1:
    首先遍历一遍,得到链表的长度,再根据len(ListNode)-k+1得到预期的链表位置
    时间复杂度为O(n)
    空间复杂度为O(1)
    """
    def getKthFromEnd(self, head, k):
        i = 0
        p = head
        while p:
            i += 1
            p = p.next
        j = i - k
        i = 0
        while head:
            if i == j:
                return head
            else:
                i += 1
                head = head.next
                
    """
    方法2:
    双指针, 设置双指针的距离,只用遍历一次即可;
    时间复杂度为O(n)
    空间复杂度为O(1)
    """
    def getKthFromEnd_02(self, head, k):
        lo, fa = head, head
        while k > 0:
            fa = fa.next
            k -= 1
        while fa:
            fa = fa.next
            lo = lo.next
        return lo

Leetcode141:环形链表 / Leetcode142:环形链表II / 剑指23:链表中环的入口节点

基本思路:快慢指针

class Solution141():
    """
    快慢指针:
    如果有环的话,那么快指针一定可以喝慢指针重合,如果没有环的话,快指针就可以走到链表末端None
    """
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        pre = ListNode(0)
        pre.next = head
        sl, fa = pre, head
        while fa and fa.next:
            if fa == sl: return True
            fa = fa.next.next
            sl = sl.next
        return False

class Solution(object):
    def detectCycle(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if not head: return None
        p = ListNode(0)        # 设置哨兵node,后续sl ans fa必须从哨兵节点同时开始
        p.next = head
        sl, fa = head, head.next
        while sl != fa:
            if not fa or not fa.next: return None
            fa = fa.next.next
            sl = sl.next
        fa = p                 # 这里其中一个指针要从哨兵节点开始走,因为设置起点是哨兵节点,fa and lo必须从同一起点开始走
        while sl != fa:
            fa, sl = fa.next, sl.next
        return fa

剑指24/Leetcode206:链表反转

基本思路:双指针

class Solution(object):
    def reverseList(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if not head: return head
        cur = head.next
        head.next = None
        while cur:
            tmp = cur.next
            cur.next = head
            head = cur
            cur = tmp
        return head

剑指35:复杂链表的赋值

剑指52:两个链表的第一个公共节点 / Leetcode160:相交链表

基本思路:a + b = b + a;如果相遇的node为None,则没有公共nodes;

class Solution(object):
    def getIntersectionNode(self, headA, headB):
        """
        :type head1, head1: ListNode
        :rtype: ListNode
        """
        pa, pb = headA, headB
        while pa != pb:
            if pa:
                pa = pa.next
            else:
                pa = headB
            if pb:
                pb = pb.next
            else:
                pb = headA
        return pa

class Solution160(object):
    """
    思路:首先遍历求长度,长的先跑,跑到短的头部,逐一比较即可;
    时间复杂度:O(n)
    空间复杂度:O(1)
    """
    def getIntersectionNode_01(self, headA, headB):
        """
        :type head1, head1: ListNode
        :rtype: ListNode
        """
        m, n = headA, headB
        l1, l2 = 0, 0
        while m:
            m = m.next
            l1 += 1
        while n:
            n = n.next
            l2 += 1
        if l1 > l2:
            while l1 > l2:
                headA = headA.next
                l1 -= 1
        elif l2 > l1:
            while l2 > l1:
                headB = headB.next
                l2 -= 1
        while headA and headB:
            if headA == headB:
                return headA
            else:
                headA = headA.next
                headB = headB.next
        return None
    """
    方法2:技巧消除长度差
    首先遍历求长度,长的先跑,跑到短的头部,逐一比较即可;
    headA = 不同部分a + 相同部分c
    headB = 不同部分b + 相同部分c
    a + c + b + c = b + c + a + c 在最后第4部分c一定会相交(if有相交)
    if没有香蕉:then在最后的None相遇;
    时间复杂度:O(n)
    空间复杂度:O(1)
    """
    def getIntersectionNode_02(self, headA, headB):
        if not headA or not headB: return None
        a = headA
        b = headB
        while(a != b):
            a = a.next if a else headB
            b = b.next if b else headA
        return a

Leetcode21:合并两个有序链表

题目:将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

基本思路:递归,直接递归即可,不用想太多。

时间复杂度O(m+n):m和n分别为两个链表的长度。因为每次调用递归都会去掉 l1 或者 l2 的头节点(直到至少有一个链表为空),函数 mergeTwoList 至多只会递归调用每个节点一次。因此,时间复杂度取决于合并后的链表长度,即 O(n+m)。

空间复杂度O(m+n):调用函数栈的次数

class Solution(object):
    """
    方法1:递归,直接理解
    """
    def mergeTwoLists(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        if not l1: return l2
        if not l2: return l1
        if l1.val <= l2.val:
            l1.next = self.mergeTwoLists(l1.next, l2)
            return l1
        else:
            l2.next = self.mergeTwoLists(l1, l2.next)
            return l2

Leetcode23:合并K个有序链表复杂度分析参考

题目:合并 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

基本思路

1. 两两合并 -- 时间复杂度 O(kn) ,空间复杂度 O(1)【不考虑递归调用栈】

2. 分而治之 -- 时间复杂度 O(logk * n),空间复杂度 O(1)【不考虑递归调用栈】

3. 优先队列 -- 空间复杂度 O(k),时间复杂度 O(nlog(k));n是所有节点个数,k是链表数

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def mergeKLists(self, lists):
        """
        :type lists: List[ListNode]
        :rtype: ListNode
        """
        def mergeTwoList(l1, l2):
            if not l1: return l2
            if not l2: return l1
            if l1.val < l2.val:
                l1.next = mergeTwoList(l1.next, l2)
                return l1
            else:
                l2.next = mergeTwoList(l1, l2.next)
                return l2
        """
        方法1:两两合并 --会超时
        """
        # tmp = None
        # for l in lists:
        #     tmp = mergeTwoList(tmp, l)
        # return tmp
        """
        方法2:分而治之
        """
        if len(lists) == 0: return None
        if len(lists) == 1: return lists[0]
        if len(lists) == 2: return mergeTwoList(lists[0], lists[1])

        mid = len(lists) // 2
        return mergeTwoList(self.mergeKLists(lists[: mid]), self.mergeKLists(lists[mid:]))
    
    """
    方法3:小顶堆
    """
    def mergeKLists(self, lists):
        """
        :type lists: List[ListNode]
        :rtype: ListNode
        """
        import heapq
        if not lists: return None
        dummy = p = ListNode(0)
        heap = []
        for l in lists:              # 将所有元素都放入堆中
            while l:
                heapq.heappush(heap, l.val)
                l = l.next
        while heap:                  # 将小顶堆中元素逐一pop出来,每次pop出来的都是最小值
            val = heapq.heappop(heap)
            p.next = ListNode(val)
            p = p.next
        p.next = None
        return dummy.next

Leetcode24:两两交换链表中的节点

-->跟Leetcode25是完全相同的思路

题目:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:给定 1->2->3->4, 你应该返回 2->1->4->3.

基本思路:翻转区间之前pre指针;翻转区间内start指针<--pre.next;翻转区间内end指针<--pre.next.next;

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution(object):
    def swapPairs(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if not head: return head
        dummy = ListNode(0)
        dummy.next = head
        pre = dummy
        while pre.next and pre.next.next:   # 如果pre.next(start) and pre.next.next(end)同时存在才执行翻转操作            
            # 基于pre,赋值start and end指针
            start, end = pre.next, pre.next.next
            # 以下3行执行翻转操作
            start.next = end.next
            pre.next = end
            end.next = start
            # 翻转之后移动更新pre
            pre = start
        return dummy.next

Leetcode25:K 个一组翻转链表

题目:给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

示例:给你这个链表:1->2->3->4->5;当 k = 2 时,应当返回: 2->1->4->3->5;当 k = 3 时,应当返回: 3->2->1->4->5

基本思路:见代码逻辑

class Solution(object):
    def reverseKGroup(self, head, k):
        """
        :type head: ListNode
        :type k: int
        :rtype: ListNode
        """
        dummy = ListNode(0)
        dummy.next = head
        pre = end = dummy
        while end.next:
            i = 0
            while i < k and end:
                end = end.next
                i += 1
            if not end: 
                break # 说明到了链表的最后部分,不用进行接下来的反转操作
            # 为区间链表反转做好准备以及链表反转
            start = pre.next
            nextNode = end.next
            end.next = None
            pre.next = self.reverseList(start)
            # 进行下一轮循环
            start.next = nextNode
            pre = end = start # start被反转了之后到了区间最末端
        return dummy.next
    def reverseList(self, head):
        if not head: return head
        cur = head.next
        head.next = None
        while cur:
            tmp = cur.next
            cur.next = head
            head = cur
            cur = tmp
        return head

Leetcode83:删除排序链表中的重复元素

题目:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例输入: 1->1->2->3->3 输出: 1->2->3

基本思路:因为是已经排序的链表,直接遍历即可

class Solution83(object):
    """
    方法1:暴力遍历一遍
    时间复杂度:O(n)
    空间复杂度:O(1)
    """
    def deleteDuplicates(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        cur = head
        if not cur or not cur.next: return head # None或者仅有一个element;
        while cur.next:
            if cur.next.val == cur.val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        return head

Leetcode82:删除排序链表中的重复元素 II

题目:给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。

示例输入: 1->2->3->3->4->4->5 输出: 1->2->5;输入: 1->1->1->2->3 输出: 2->3

基本思路:利用区间指针start and end,同时也利用前驱pre指针;

class Solution(object):
    def deleteDuplicates(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if not head: return None
        dummy = ListNode(0)
        dummy.next = head           # 这个特别注意,在定义哨兵节点之后,一定要连接上head;
        pre = dummy
        start = end = dummy.next
        while end and end.next:
            while end and end.next: # 通过start and end node位置,判断该元素是否是重复的;
                if end.next.val == end.val:
                    end = end.next
                else:
                    break
            if start != end:           # 如果存在重复,执行删除操作
                pre.next = end.next    # 删除操作
                start = end = pre.next # 更新start and end
            else:
                pre = end              # 不重复,更新pre
                start = end = end.next # 同时也更新start and end
        return dummy.next

Leetcode147:对链表进行插入排序

示例输入: 4->2->1->3 输出: 1->2->3->4

基本思路:插入排序的思路

class Solution:
    def insertionSortList(self, head: ListNode) -> ListNode:
        dummy = ListNode(float('-inf')) # 找个排头
        pre = dummy
        cur = head  # 依次拿head节点
        while cur:
            tmp = cur.next  # 把下一次节点保持下来
            while pre.next and pre.next.val < cur.val: # 找到插入的位置
                pre = pre.next
            # 进行插入操作
            cur.next = pre.next
            pre.next = cur
            pre= dummy
            cur = tmp
        return dummy.next

Leetcode203:移除链表元素

题目:删除链表中等于给定值 val 的所有节点。

示例输入: 1->2->6->3->4->5->6, val = 6 输出: 1->2->3->4->5

class Solution203(object):
    """
    方法1:
    遍历一遍即可
    """
    def removeElements(self, head, val):
        """
        :type head: ListNode
        :type val: int
        :rtype: ListNode
        """
        # create a 哨兵node
        fh = ListNode(0)
        fh.next = head
        head = fh
        while fh.next:
            if fh.next.val == val:
                fh.next = fh.next.next
            else:
                fh = fh.next
        return head.next

Leetcode234:回文链表

基本思路:快慢指针找中点,然后开始比较--在前半部分遍历的时候,就同时反转操作
时间复杂度:遍历找中点O(n),再比较O(n)
空间复杂度:存指针O(1)

class Solution234(object):
    """
    方法1:快慢指针找中点,然后开始比较
    时间复杂度:遍历找中点O(n),再比较O(n)
    空间复杂度:存指针O(1)
    """
    def isPalindrome(self, head):
        # 先找链表中点
        fa, sl = head, head
        while fa and fa.next:
            fa = fa.next.next
            sl = sl.next
        # 求链表长度,如果是奇数,则设置中点的下一个节点为比较起点;
        l = 0
        m = head
        while m:
            m = m.next
            l += 1
        if l % 2 == 1:
            sl = sl.next
        # 反转后半部分链表
        sl = self.reverseList(sl)
        # 逐一比较nodes
        while sl:
            if sl.val == head.val:
                sl = sl.next
                head = head.next
            else:
                return False
        return True
    
    def reverseList(self, head):
        if not head or not head.next: return head
        cur = self.reverseList(head.next)        # 1-> 2<-3<-4<-5, 此时cur = 5, 最后return也是cur
        head.next.next = head                    # head为1,head.next.next要指向自己
        head.next = None
        return cur
    
    """
    方法2优化版:快慢指针找中点,然后开始比较--在前半部分遍历的时候,就同时反转操作
    时间复杂度:遍历找中点O(n),再比较O(n)
    空间复杂度:存指针O(1)
    """
    def isPalindrome_02(self, head):
        # 先计算链表长度(后面需要基于长度为奇偶进行不同处理)
        l = 0
        m = head
        while m:
            m = m.next
            l += 1
            
        # 找链表中点同时反转
        fa, sl = head, head
        rh = None
        while fa and fa.next:
            fa = fa.next.next  # 快指针往前两步;
            tmp = sl           # 慢指针走前把位置记一下,用于待会儿做反转指针的head
            sl = sl.next       # 慢指针往前一步;
            tmp.next = rh  
            rh = tmp
            
        if l % 2 == 1:
            sl = sl.next
            
        while sl and rh:
            if sl.val != rh.val:
                return False
            else:
                sl, rh = sl.next, rh.next
        return True

Leetcode876:链表的中间节点

基本思路:快慢双指针

class Solution876(object):
    def middleNode(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        fa, sl = head, head
        while fa and fa.next:
            fa = fa.next.next
            sl = sl.next
        return sl

Leetcode206:反转链表 || 反转双链表

"""
1. 反转单链表
"""
def reverseList(head):
    if not head: return head
    cur = head.next
    head.next = None
    while cur:
        tmp = cur.next
        cur.next = head
        head = cur
        cur = next
    return head
"""
2. 反转双链表
"""
def reverseDoubleList(head):
    if not head: return head
    pre = None
    while head:
        tmp = head.next
        head.next = pre
        head.pre = tmp
        pre = head
        head = tmp
    return pre

Leetcode426:将二叉搜索树转化为排序的双向链表题解参考1 and 题解参考2

题目:将一个 二叉搜索树 就地转化为一个 已排序的双向循环链表 。对于双向循环列表,你可以将左右孩子指针作为双向循环链表的前驱和后继指针,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。特别地,我们希望可以 就地 完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中最小元素的指针。

基本思路:递归

"""
方法1
"""
class Solution:
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        def dfs(cur):
            if not cur: return
            dfs(cur.left) # 递归左子树
            if self.pre: # 修改节点引用
                self.pre.right, cur.left = cur, self.pre
            else: # 记录头节点
                self.head = cur
            self.pre = cur # 保存 cur
            dfs(cur.right) # 递归右子树
        
        if not root: return
        self.pre = None
        dfs(root)
        self.head.left, self.pre.right = self.pre, self.head
        return self.head

"""
方法2
"""
class Solution:
    def treeToDoublyListCore(self, root: 'Node') -> ('Node', 'Node'):
        """
        :param root: 树的根节点
        :return: 双向链表的 头节点 和 尾节点
        """
        # 根为空,那么 对应的双向链表的 头节点 和 尾节点 也为空
        if root is None:
            return None, None

        # 左子树 对应的 双向链表的头节点和尾节点
        left_head, left_tail = self.treeToDoublyListCore(root.left)
        # 右子树 对应的 双向链表的头节点和尾节点
        right_head, right_tail = self.treeToDoublyListCore(root.right)

        # 根的 左节点 与 左子树的尾节点 互相连接
        # 根的 右节点 与 右子树的头节点 互相连接
        root.left, root.right = left_tail, right_head
        if left_tail:
            left_tail.right = root
        if right_head:
            right_head.left = root

        # 左子树的头节点 如果存在则作为当前 双向链表的头节点,否则使用 根节点。尾节点同理。
        return left_head if left_head else root, right_tail if right_tail else root

    def treeToDoublyList(self, root: 'Node') -> 'Node' or None:
        """
        递归遍历
        时间复杂度:O(N),所有节点遍历一次。
        空间复杂度:O(N),当二叉搜索树退化为链表时,树的深度为 N.
        """
        head, tail = self.treeToDoublyListCore(root)
        # 改造成循环双向链表
        if head and tail:
            head.left, tail.right = tail, head
        return head

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值