LeetCode-Python 链表专题(思路+代码)

  • Post author:
  • Post category:python





Leetcode 2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

在这里插入图片描述

示例 1:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

示例 2:

输入:l1 = [0], l2 = [0]
输出:[0]

示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/add-two-numbers

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

先判断一下哪个链表长,然后用交换的方法确保一定是l1更长。

然后把l2的值加到l1上,全部加完之后遍历l1处理进位,记得处理最后一位需要进1的特殊情况。

class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None


class Solution:
    # @return a ListNode
    def addTwoNumbers(self, l1, l2):
        head = ListNode(0)
        l = head
        carry = 0
        while l1 or l2 or carry:
            sum, carry = carry, 0
            if l1:
                sum += l1.val
                l1 = l1.next
            if l2:
                sum += l2.val
                l2 = l2.next
            if sum > 9:
                carry = 1
                sum -= 10
            l.next = ListNode(sum)
            l = l.next
        return head.next


if __name__ == '__main__':
    a, a.next, a.next.next = ListNode(2), ListNode(4), ListNode(3)
    b, b.next, b.next.next = ListNode(5), ListNode(6), ListNode(4)
    result = Solution().addTwoNumbers(a, b)
    print("{0} -> {1} -> {2}".format(result.val, result.next.val, result.next.next.val))



Leetcode 24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

在这里插入图片描述

示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/swap-nodes-in-pairs

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

我们首先需要建立pre、node1、node2和lat四个指针即可。

pre   node1  node2   lat
 h  ->  1  ->  2  ->  3  ->  4

然后pre->next=node2;node2.next=node1;node1.next=lat

     -----------
     |         |
h    1 <- 2 -> 3 -> 4
|         |
-----------

接着更新四个指针即可。

class Solution:
    def swapPairs(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        h = ListNode(-1)
        h.next = head
        pre = h
        while pre.next != None and pre.next.next != None:
            node1 = pre.next
            node2 = node1.next
            lat = node2.next

            pre.next = node2            
            node2.next = node1
            node1.next = lat
            
            pre = node1

        return h.next






Leetcode 21. 合并两个有序链表

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

在这里插入图片描述

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/merge-two-sorted-lists

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> 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

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

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        if l1 and l2:
            if l1.val > l2.val: 
                l1, l2 = l2, l1
            l1.next = self.mergeTwoLists(l1.next, l2)
        return l1 or l2




Leetcode 206:反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

在这里插入图片描述

在这里插入图片描述

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/reverse-linked-list

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路:

要将链表 1 -> 2 -> 3 -> 4 -> Null 反转为 4 -> 3 -> 2 -> 1 -> Null ,需要一个 cur 指针表示当前遍历到的节点;一个 pre 指针表示当前节点的前驱节点;在循环中还需要一个中间变量 temp 来保存当前节点的后驱节点。

算法流程:

首先 pre 指针指向 Null,cur 指针指向 head;

当 cur != Null,执行循环。

    先将 cur.next 保存在 temp 中防止链表丢失:temp = cur.next

    接着把 cur.next 指向前驱节点 pre:cur.next = pre

    然后将 pre 往后移一位也就是移到当前 cur 的位置:pre = cur

    最后把 cur 也往后移一位也就是 temp 的位置:cur = temp

当 cur == Null,结束循环,返回 pre。

在这里插入图片描述

class Solution:
    def reverseList(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        pre = None
        cur = head

        while cur != None:
            temp= cur.next
            cur.next = pre
            pre = cur
            cur = temp

        return pre

使用递归就更简单了,我们知道reverseList(head)返回输入的链表反转后的head,那么如果reverseList(head.next)的话

head
  1->2<-3<-4<-5
              |
             node

我们此时只需要head.next.next=head,也就是先建立一个双向连接

head
  1->2<-3<-4<-5
   <-         |
             node

然后再head.next=None,返回node即可。

      head
  null<-1<-2<-3<-4<-5
                    |
                   node
class Solution:
    def reverseList(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if head == None or head.next == None:
            return head
        node = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return node



Leetcode 25. K 个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

进阶:

你可以设计一个只使用常数额外空间的算法来解决此问题吗?
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

在这里插入图片描述

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:

在这里插入图片描述

输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

示例 3:

输入:head = [1,2,3,4,5], k = 1
输出:[1,2,3,4,5]

示例 4:

输入:head = [1], k = 1
输出:[1]

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/reverse-nodes-in-k-group

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

所有涉及链表翻转的题目都可以由下述代码中的reverseMN(M, N)进行实现,注意这里的M, N是节点。该函数的目的是返回 M -> ... -> N 链表反转后的首尾节点。
以下是一些相关题目:
    206.反转链表      
    25.反转第m到第n个节点
    92.每K个一组翻转链表
    143.重排链表

只需将每个场景下的 M, N 界定清晰就够了

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        
        def reverseMN(M, N):
            # M节点到N节点 翻转后返回首尾节点
            tail = None
            cur = M
            while cur != N:
                temp = cur.next
                cur.next = tail
                tail = cur 
                cur = temp
            cur.next = tail
            return N, M

        static_dummy = ListNode(-1)
        static_dummy.next = head
        movable_dummy = static_dummy
        start = head

        while start:
            temp_list = []
            for i in range(k):
                if start:
                    temp_list.append(start)
                    start = start.next
                else:
                    break
            if len(temp_list) == k:
                cur_head, cur_tail = reverseMN(temp_list[0], temp_list[-1])
                movable_dummy.next = cur_head
                movable_dummy = cur_tail
            else:
                movable_dummy.next = temp_list[0]
                movable_dummy = temp_list[-1]
        return static_dummy.next
  



Leetcode 141. 环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

示例 1:

在这里插入图片描述

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

在这里插入图片描述

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

在这里插入图片描述

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/linked-list-cycle

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

首先 头结点为空 则一定没环
定义一个慢节点 每次走一步
定义一个快节点 每次走两步
由于快节点将比慢节点更快到达尾部 所以循环条件只用设定快节点的
若是单节点链表 默认无环 循环中j.next != None 将筛选出单节点链表
若链表有两个节点 j移动一次后 就到了链表尾为空的地方 j != None 则让循环停止
链表有三个节点时 j移动一次 到达链表最尾元素 通过j,next!= None停止循环

所以循环条件定为 while j!= None and j.next != None 即可
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        if head == None:
            return False

        i = head
        j = head
 
        while j!= None and j.next != None:
            i = i.next
            j = j.next.next

            if i == j:
                return True

            # if j == None:
            #     return False

        return False


hash表

思路

使用hash表记录已经访问过的节点
class Solution:
	    def hasCycle(self, head: ListNode) -> bool:
	        # 1. python map
	        m = {}
	        while head:
	            if m.get(head):
	                return True
	            m[head] = 1
	            head = head.next
	        return False

链表计数(空间复杂度O(1))

思路

链表中最多10000个节点,超过10000就是有环

class Solution:
	    def hasCycle(self, head: ListNode) -> bool:
	        # 3. 计数 10000
	        count = 0
	        while head and count <= 10000:
	            count, head = count + 1, head.next
	        return count > 10000

链表反转(空间复杂度O(1),变更原链表结构)

思路

按顺序反转链表,由于环之前的部分已经反转,最后会返回到head节点

空间复杂度为O(1),但是

class Solution:
	    def hasCycle(self, head: ListNode) -> bool:
	        # 4. 列表倒置
	        if not head or not head.next:
	            return False
	        p, q = None, head
	        while q:
	            u, p, q = p, q, q.next
	            p.next = u
	        return head == p



Leetcode 160. 相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交:

在这里插入图片描述

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

自定义评测:

评测系统 的输入如下(你设计的程序 不适用 此输入):

intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
listA - 第一个链表
listB - 第二个链表
skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数

评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。

示例 1:

在这里插入图片描述

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

在这里插入图片描述

输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

在这里插入图片描述

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路:

设「第一个公共节点」为 node ,「链表 headA」的节点数量为 aaa ,「链表 headB」的节点数量为 bbb ,「两链表的公共尾部」的节点数量为 ccc ,则有:

头节点 headA 到 node 前,共有 a−ca - ca−c 个节点;
头节点 headB 到 node 前,共有 b−cb - cb−c 个节点;

在这里插入图片描述

考虑构建两个节点指针 A​ , B 分别指向两链表头节点 headA , headB ,做如下操作:

指针 A 先遍历完链表 headA ,再开始遍历链表 headB ,当走到 node 时,共走步数为:

a+(b−c)a + (b – c) a+(b−c)

指针 B 先遍历完链表 headB ,再开始遍历链表 headA ,当走到 node 时,共走步数为:

b+(a−c)b + (a – c) b+(a−c)

如下式所示,此时指针 A , B 重合,并有两种情况:

a+(b−c)=b+(a−c)a + (b – c) = b + (a – c) a+(b−c)=b+(a−c)

若两链表 有 公共尾部 (即 c>0c > 0c>0 ) :指针 A , B 同时指向「第一个公共节点」node 。
若两链表 无 公共尾部 (即 c=0c = 0c=0 ) :指针 A , B 同时指向 nullnullnull 。

因此返回 A 即可。

如下图所示,为 a=5a = 5a=5 , b=3b = 3b=3 , c=2c = 2c=2 示例的算法执行过程。

在这里插入图片描述

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        A, B = headA, headB
        while A != B:
            A = A.next if A else headB
            B = B.next if B else headA
        return A



Leetcode 143. 重排链表

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln – 1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln – 1 → L2 → Ln – 2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

在这里插入图片描述

输入:head = [1,2,3,4]
输出:[1,4,2,3]

示例 2:

在这里插入图片描述

输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/reorder-list

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

class Solution:
    def reorderList(self, head: ListNode) -> None:       
        if not head or not head.next:
            return head
        # 用快慢指针把链表一分为二,前半部分长度 >= 后半部分长度
        # first 为前半部分的头部,second 为后半部分的头部
        first = low = fast = head
        while fast.next and fast.next.next:
            fast, low = fast.next.next, low.next
        second, node, low.next, second.next = low.next, low.next.next, None, None
        # 后半部分逆序
        while node:
            node.next, second, node = second, node, node.next
        # 前后部分交替链接
        while second:
            first.next, second.next, first, second = second, first.next, first.next, second.next

class Solution:
    def reorderList(self, head: ListNode) -> None:
        if not head:return
        # 找到中点
        slow,fast = head,head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        # 翻转后半
        pre,cur = None,slow.next
        slow.next = None 
        while cur:
            nxt = cur.next
            cur.next = pre
            pre,cur = cur,nxt
        # 交替合并
        p,q = head,pre
        while q:
            nxt = p.next
            p.next = q
            p,q = q,nxt



Leetcode237. 删除链表中的节点

有一个单链表的 head,我们想删除它其中的一个节点 node。

给你一个需要删除的节点 node 。你将 无法访问 第一个节点 head。

链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点。

删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:

给定节点的值不应该存在于链表中。
链表中的节点数应该减少 1。
node 前面的所有值顺序相同。
node 后面的所有值顺序相同。

自定义测试:

对于输入,你应该提供整个链表 head 和要给出的节点 node。node 不应该是链表的最后一个节点,而应该是链表中的一个实际节点。
我们将构建链表,并将节点传递给你的函数。
输出将是调用你函数后的整个链表。

示例 1:

输入:head = [4,5,1,9], node = 5
输出:[4,1,9]

解释:指定链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9

示例 2:

输入:head = [4,5,1,9], node = 1
输出:[4,5,9]

解释:指定链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9

提示:

链表中节点的数目范围是 [2, 1000]
-1000 <= Node.val <= 1000
链表中每个节点的值都是 唯一 的
需要删除的节点 node 是 链表中的节点 ,且 不是末尾节点

来源:力扣(LeetCode)

链接:https://leetcode.cn/problems/delete-node-in-a-linked-list

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

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



Leetcode19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1
输出:[]

示例 3:

输入:head = [1,2], n = 1
输出:[1]

提示:

链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz

来源:力扣(LeetCode)

链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路与算法

一种容易想到的方法是,我们首先从头节点开始对链表进行一次遍历,得到链表的长度 LL。随后我们再从头节点开始对链表进行一次遍历,当遍历到第 L−n+1L−n+1 个节点时,它就是我们需要删除的节点。

为了与题目中的 nn 保持一致,节点的编号从 11 开始,头节点为编号 11 的节点。

为了方便删除操作,我们可以从哑节点开始遍历 L−n+1L−n+1 个节点。当遍历到第 L−n+1L−n+1 个节点时,它的下一个节点就是我们需要删除的节点,这样我们只需要修改一次指针,就能完成删除操作。

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        def getLength(head: ListNode) -> int:
            length = 0
            while head:
                length += 1
                head = head.next
            return length
        
        dummy = ListNode(0, head)
        length = getLength(head)
        cur = dummy
        for i in range(1, length - n + 1):
            cur = cur.next
        cur.next = cur.next.next
        return dummy.next

双指针

思路与算法

我们也可以在不预处理出链表的长度,以及使用常数空间的前提下解决本题。

由于我们需要找到倒数第 nn 个节点,因此我们可以使用两个指针 firstfirst 和 secondsecond 同时对链表进行遍历,并且 firstfirst 比 secondsecond 超前 nn 个节点。当 firstfirst 遍历到链表的末尾时,secondsecond 就恰好处于倒数第 nn 个节点。

具体地,初始时 firstfirst 和 secondsecond 均指向头节点。我们首先使用 firstfirst 对链表进行遍历,遍历的次数为 nn。此时,firstfirst 和 secondsecond 之间间隔了 n−1n−1 个节点,即 firstfirst 比 secondsecond 超前了 nn 个节点。

在这之后,我们同时使用 firstfirst 和 secondsecond 对链表进行遍历。当 firstfirst 遍历到链表的末尾(即 firstfirst 为空指针)时,secondsecond 恰好指向倒数第 nn 个节点。

根据方法一和方法二,如果我们能够得到的是倒数第 nn 个节点的前驱节点而不是倒数第 nn 个节点的话,删除操作会更加方便。因此我们可以考虑在初始时将 secondsecond 指向哑节点,其余的操作步骤不变。这样一来,当 firstfirst 遍历到链表的末尾时,secondsecond 的下一个节点就是我们需要删除的节点。

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        dummy = ListNode(0, head)
        first = head
        second = dummy
        for i in range(n):
            first = first.next

        while first:
            first = first.next
            second = second.next
        
        second.next = second.next.next
        return dummy.next



Leetcode61. 旋转链表

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

示例 1:

输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]

示例 2:

输入:head = [0,1,2], k = 4
输出:[2,0,1]

提示:

链表中节点的数目在范围 [0, 500] 内
-100 <= Node.val <= 100
0 <= k <= 2 * 109

来源:力扣(LeetCode)

链接:https://leetcode.cn/problems/rotate-list

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路及算法

记给定链表的长度为 nn,注意到当向右移动的次数 k≥nk≥n 时,我们仅需要向右移动 k mod nkmodn 次即可。因为每 nn 次移动都会让链表变为原状。这样我们可以知道,新链表的最后一个节点为原链表的第 (n−1)−(k mod n)(n−1)−(kmodn) 个节点(从 00 开始计数)。

这样,我们可以先将给定的链表连接成环,然后将指定位置断开。

具体代码中,我们首先计算出链表的长度 nn,并找到该链表的末尾节点,将其与头节点相连。这样就得到了闭合为环的链表。然后我们找到新链表的最后一个节点(即原链表的第 (n−1)−(k mod n)(n−1)−(kmodn) 个节点),将当前闭合为环的链表断开,即可得到我们所需要的结果。

特别地,当链表长度不大于 11,或者 kk 为 nn 的倍数时,新链表将与原链表相同,我们无需进行任何处理。

在这里插入图片描述

class Solution:
    def rotateRight(self, head: ListNode, k: int) -> ListNode:
        if k == 0 or not head or not head.next:
            return head
        
        n = 1
        cur = head
        while cur.next:
            cur = cur.next
            n += 1
        
        if (add := n - k % n) == n:
            return head
        
        cur.next = head
        while add:
            cur = cur.next
            add -= 1
        
        ret = cur.next
        cur.next = None
        return ret



Leetcode234. 回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

示例 1:

输入:head = [1,2,2,1]
输出:true

示例 2:

输入:head = [1,2]
输出:false

提示:

链表中节点数目在范围[1, 105] 内
0 <= Node.val <= 9

来源:力扣(LeetCode)

链接:https://leetcode.cn/problems/palindrome-linked-list

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

如果你还不太熟悉链表,下面有关于列表的概要讲述。

有两种常用的列表实现,分别为数组列表和链表。如果我们想在列表中存储值,它们是如何实现的呢?

数组列表底层是使用数组存储值,我们可以通过索引在 O(1)O(1) 的时间访问列表任何位置的值,这是由基于内存寻址的方式。
链表存储的是称为节点的对象,每个节点保存一个值和指向下一个节点的指针。访问某个特定索引的节点需要 O(n)O(n) 的时间,因为要通过指针获取到下一个位置的节点。

确定数组列表是否回文很简单,我们可以使用双指针法来比较两端的元素,并向中间移动。一个指针从起点向中间移动,另一个指针从终点向中间移动。这需要 O(n)O(n) 的时间,因为访问每个元素的时间是 O(1)O(1),而有 nn 个元素要访问。

然而同样的方法在链表上操作并不简单,因为不论是正向访问还是反向访问都不是 O(1)O(1)。而将链表的值复制到数组列表中是 O(n)O(n),因此最简单的方法就是将链表的值复制到数组列表中,再使用双指针法判断。

算法

一共为两个步骤:

复制链表值到数组列表中。
使用双指针法判断是否为回文。

第一步,我们需要遍历链表将值复制到数组列表中。我们用 currentNode 指向当前节点。每次迭代向数组添加 currentNode.val,并更新 currentNode = currentNode.next,当 currentNode = null 时停止循环。

执行第二步的最佳方法取决于你使用的语言。在 Python 中,很容易构造一个列表的反向副本,也很容易比较两个列表。而在其他语言中,就没有那么简单。因此最好使用双指针法来检查是否为回文。我们在起点放置一个指针,在结尾放置一个指针,每一次迭代判断两个指针指向的元素是否相同,若不同,返回 false;相同则将两个指针向内移动,并继续判断,直到两个指针相遇。

在编码的过程中,注意我们比较的是节点值的大小,而不是节点本身。正确的比较方式是:node_1.val == node_2.val,而 node_1 == node_2 是错误的。

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        vals = []
        current_node = head
        while current_node is not None:
            vals.append(current_node.val)
            current_node = current_node.next
        return vals == vals[::-1]



Leetcode445. 两数相加 II

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例1:

输入:l1 = [7,2,4,3], l2 = [5,6,4]

输出:[7,8,0,7]

示例2:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[8,0,7]

示例3:

输入:l1 = [0], l2 = [0]
输出:[0]

提示:

链表的长度范围为 [1, 100]
0 <= node.val <= 9
输入数据保证链表代表的数字无前导 0

来源:力扣(LeetCode)

链接:https://leetcode.cn/problems/add-two-numbers-ii

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路与算法

本题的主要难点在于链表中数位的顺序与我们做加法的顺序是相反的,为了逆序处理所有数位,我们可以使用栈:把所有数字压入栈中,再依次取出相加。计算过程中需要注意进位的情况。

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        s1, s2 = [], []
        while l1:
            s1.append(l1.val)
            l1 = l1.next
        while l2:
            s2.append(l2.val)
            l2 = l2.next
        ans = None
        carry = 0
        while s1 or s2 or carry != 0:
            a = 0 if not s1 else s1.pop()
            b = 0 if not s2 else s2.pop()
            cur = a + b + carry
            carry = cur // 10
            cur %= 10
            curnode = ListNode(cur)
            curnode.next = ans
            ans = curnode
        return ans



Leetcode23. 合并 K 个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

示例 2:

输入:lists = []
输出:[]

示例 3:

输入:lists = [[]]
输出:[]

提示:

k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i] 按 升序 排列
lists[i].length 的总和不超过 10^4

来源:力扣(LeetCode)

链接:https://leetcode.cn/problems/merge-k-sorted-lists

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

在介绍k个有序链表的合并前需要简单介绍下两个链表的有序合并(leetcode21)方法。如果不考虑时间复杂性与空间复杂性实现的方法比较简单,这里要求时间复性为O(n)O(n),空间复杂性为O(1)O(1)。以示例中的前两个链表为例来说明相关过程。

首先,需要定义一个head链表头来作为最终结果链表的表头。

image.png

对比两个链表的当前节点的值,取小的作为head的next。先选择当前的l1加入到head链表中,然后l1跳到下一个节点。

image.png

再比较当前的L1与l2值的大小。

image.png

重复整个过程真到最后:

image.png

class Solution:
    #合并两个链表leetcode 21
    def merge2Lists(self,list1,list2):
        head=ListNode(0)
        curr_list=head
        while list1 and list2:
            if list1.val<list2.val:
                curr_list.next=list1
                list1=list1.next
            else:
                curr_list.next=list2
                list2=list2.next
            curr_list=curr_list.next
        if list1:
            curr_list.next=list1
        if list2:
            curr_list.next=list2
        return head.next



Leetcode148. 排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

示例 1:

在这里插入图片描述

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:

在这里插入图片描述

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:

输入:head = []
输出:[]

来源:力扣(LeetCode)

链接:https://leetcode.cn/problems/sort-list

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

归并排序(递归法)

题目要求时间空间复杂度分别为 O(nlogn)O(nlogn) 和 O(1)O(1),根据时间复杂度我们自然想到二分法,从而联想到归并排序;

对数组做归并排序的空间复杂度为 O(n)O(n),分别由新开辟数组 O(n)O(n) 和递归函数调用 O(logn)O(logn) 组成,而根据链表特性:
    数组额外空间:链表可以通过修改引用来更改节点顺序,无需像数组一样开辟额外空间;
    递归额外空间:递归调用函数将带来 O(logn)O(logn) 的空间复杂度,因此若希望达到 O(1)O(1) 空间复杂度,则不能使用递归。

通过递归实现链表归并排序,有以下两个环节:
    分割 cut 环节: 找到当前链表 中点,并从 中点 将链表断开(以便在下次递归 cut 时,链表片段拥有正确边界);
        我们使用 fast,slow 快慢双指针法,奇数个节点找到中点,偶数个节点找到中心左边的节点。
        找到中点 slow 后,执行 slow.next = None 将链表切断。
        递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 tmp(因为链表是从 slow 切断的)。
        cut 递归终止条件: 当 head.next == None 时,说明只有一个节点了,直接返回此节点。
    合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
        双指针法合并,建立辅助 ListNode h 作为头部。
        设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
        返回辅助ListNode h 作为头部的下个节点 h.next。
        时间复杂度 O(l + r),l, r 分别代表两个链表长度。
    当题目输入的 head == None 时,直接返回 None。

在这里插入图片描述

class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        if not head or not head.next: return head # termination.
        # cut the LinkedList at the mid index.
        slow, fast = head, head.next
        while fast and fast.next:
            fast, slow = fast.next.next, slow.next
        mid, slow.next = slow.next, None # save and cut.
        # recursive for cutting.
        left, right = self.sortList(head), self.sortList(mid)
        # merge `left` and `right` linked list and return it.
        h = res = ListNode(0)
        while left and right:
            if left.val < right.val: h.next, left = left, left.next
            else: h.next, right = right, right.next
            h = h.next
        h.next = left if left else right
        return res.next





Leetcode146. LRU 缓存

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

示例:

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释

LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

来源:力扣(LeetCode)

链接:https://leetcode.cn/problems/lru-cache

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

哈希表 + 双向链表

算法

LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。

双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。

哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。

这样以来,我们首先使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部,即可在 O(1)O(1) 的时间内完成 get 或者 put 操作。具体的方法如下:

对于 get 操作,首先判断 key 是否存在:

    如果 key 不存在,则返回 −1−1;

    如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。

对于 put 操作,首先判断 key 是否存在:

    如果 key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;

    如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。

上述各项操作中,访问哈希表的时间复杂度为 O(1)O(1),在双向链表的头部添加节点、在双向链表的尾部删除节点的复杂度也为 O(1)O(1)。而将一个节点移到双向链表的头部,可以分成「删除该节点」和「在双向链表的头部添加节点」两步操作,都可以在 O(1)O(1) 时间内完成。

小贴士

在双向链表的实现中,使用一个伪头部(dummy head)和伪尾部(dummy tail)标记界限,这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在。

在这里插入图片描述

class DLinkedNode:
    def __init__(self, key=0, value=0):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None


class LRUCache:

    def __init__(self, capacity: int):
        self.cache = dict()
        # 使用伪头部和伪尾部节点    
        self.head = DLinkedNode()
        self.tail = DLinkedNode()
        self.head.next = self.tail
        self.tail.prev = self.head
        self.capacity = capacity
        self.size = 0

    def get(self, key: int) -> int:
        if key not in self.cache:
            return -1
        # 如果 key 存在,先通过哈希表定位,再移到头部
        node = self.cache[key]
        self.moveToHead(node)
        return node.value

    def put(self, key: int, value: int) -> None:
        if key not in self.cache:
            # 如果 key 不存在,创建一个新的节点
            node = DLinkedNode(key, value)
            # 添加进哈希表
            self.cache[key] = node
            # 添加至双向链表的头部
            self.addToHead(node)
            self.size += 1
            if self.size > self.capacity:
                # 如果超出容量,删除双向链表的尾部节点
                removed = self.removeTail()
                # 删除哈希表中对应的项
                self.cache.pop(removed.key)
                self.size -= 1
        else:
            # 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            node = self.cache[key]
            node.value = value
            self.moveToHead(node)
    
    def addToHead(self, node):
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node
    
    def removeNode(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev

    def moveToHead(self, node):
        self.removeNode(node)
        self.addToHead(node)

    def removeTail(self):
        node = self.tail.prev
        self.removeNode(node)
        return node




版权声明:本文为lht0909原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。