前言
整理力扣刷题思路。
- 语言:python
- 题库:来自neetcode: link
一、预备知识
1.链表
链表(Linked list)是一种常见的基础数据结构,用于存储一系列数据元素。与数组不同,链表中的数据元素不是按顺序存储,而是通过指针相互链接在一起。
链表的基本思想
- 链表通过不连续的存储方式,自适应内存大小,以及指针的灵活使用,巧妙地简化了数据的插入、删除和移动操作。
- 链表的基本思想是利用结构体的设置,额外开辟出一份内存空间作为指针,它总是指向下一个节点。
- 一个个节点通过
NEXT
指针相互串联,形成了链表。
单链表的定义
- 单链表是一种链式存取的数据结构,由一系列节点组成。
- 每个节点包含两部分:
DATA
:自定义的数据类型,可以是整数、字符、结构体等。NEXT
:指向下一个链表节点的指针。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
参考python代码的定义,单链表就是给定一个头节点(head),可以访问到节点值(head.val)和下一个节点(head.next)。
链表的优点
- 插入和删除操作只需要修改指针所指向的区域,不需要进行大量的数据移动操作。
- 链表适用于动态内存分配,不需要预先分配固定大小的空间。
链表的类型
- 单链表:节点之间只有一个指针,指向下一个节点。
- 双链表:每个节点有两个指针,分别指向前一个节点和后一个节点。
- 循环链表:尾节点的
NEXT
指针指向头节点,形成一个闭环。
二、解题思路
1.迭代
206.reverse-linked-list
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
link
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
#如果链表为空或者只有一个值,那么反转还是自身
if not head or not head.next:
return head
newhead = None
while head:
#切断原节点与其下一个节点的联系,建立新联系
tmp = head.next
head.next = newhead
newhead = head
#处理下一个节点
head = tmp
return newhead
25.reverse-nodes-in-k-group
给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
link
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if not head or k==1:
return head
vec = []
tmp = dummy = ListNode(0,head)
while tmp:
vec.append(tmp)
tmp = tmp.next
m = (len(vec)-1)//k
ans = dummy
for i in range(m):
#反转第i组的k个节点
cur = vec[i*k+1]
vec[(i+1)*k].next = None
ans.next = self.reverseList(cur)
#将ans更新为第i组节点反转前的第一个节点
#也就是反转后的最后一个节点
ans = cur
#连接最后剩余的少于k的节点
if (len(vec)-1)%k != 0:
ans.next = vec[m*k+1]
return dummy.next
def reverseList(self, head):
newhead = None
while head:
tmp = head.next
head.next = newhead
newhead = head
head = tmp
return newhead
这一题是上面反转链表的进阶
21.merge-two-sorted-lists
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
link
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
if not list1:
return list2
if not list2:
return list1
newlist = ListNode(0)
tmp = newlist
while list1 and list2:
if list1.val <= list2.val:
tmp.next = list1
list1 = list1.next
else:
tmp.next = list2
list2 = list2.next
tmp = tmp.next
tmp.next = list1 if list1 else list2
return newlist.next
2.递归
206
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head==None or head.next==None:
return head
# 递归到最后这里的cur就是最后一个节点
cur = self.reverseList(head.next)
# 如果链表是 1->2->3->4->5,那么此时的cur就是5
# 此时的head是4,head的下一个是5,下下一个是空
# 所以head.next.next 就是5->4
head.next.next = head
# 防止链表循环,需要将head.next设置为空
head.next = None
# 每层递归函数都返回cur,也就是最后一个节点
return cur
21
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
if not list1:
return list2
if not list2:
return list1
if list1.val <= list2.val:
list1.next = self.mergeTwoLists(list1.next, list2)
return list1
else:
list2.next = self.mergeTwoLists(list1, list2.next)
return list2
23.merge-k-sorted-lists
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
if not lists:
return None
n = len(lists)
if n==1:
return lists[0]
left = self.mergeKLists(lists[:n//2])
right = self.mergeKLists(lists[n//2:])
return self.mergeTwoLists(left,right)
def mergeTwoLists(self, list1, list2):
if not list1:
return list2
if not list2:
return list1
if list1.val <= list2.val:
list1.next = self.mergeTwoLists(list1.next, list2)
return list1
else:
list2.next = self.mergeTwoLists(list1, list2.next)
return list2
借用21题的合并两个链表,为了减少不必要的计算,使用二分法合并
3.快慢指针
141.linked-list-cycle
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
link
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
if not head or not head.next:
return False
#快指针的速度比慢指针大1
#若存在环,一定会追及
fast,slow = head.next,head
while fast and fast.next:
if fast == slow:
return True
fast = fast.next.next
slow = slow.next
#若不存在环,快指针会先走出链表
return False
142.linked-list-cycle-ii
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast,slow = head,head
while True:
if not fast or not fast.next:
return None
fast = fast.next.next
slow = slow.next
if fast == slow:
break
cur = head
while cur != slow:
cur = cur.next
slow = slow.next
return slow
两次指针,参考:link
287.find-the-duplicate-number
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
link
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
def next(i):
return nums[i]
fast,slow = 0,0
while True:
fast = next(next(fast))
slow = next(slow)
if fast == slow:
break
cur = 0
while cur != slow:
cur = next(cur)
slow = next(slow)
return slow
类似环形链表,参考:link
19.remove-nth-node-from-end-of-list
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
link
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
if not head or not head.next:
return None
fast = slow = dummy = ListNode(0,head)
#快指针先走n+1步
for _ in range(n+1):
fast = fast.next
#快指针走出链表时,慢指针刚好在要删除的节点前一位
while fast:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy.next
143.reorder-list
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
link
class Solution:
def reorderList(self, head: Optional[ListNode]) -> None:
"""
Do not return anything, modify head in-place instead.
"""
if not head or not head.next or not head.next.next:
return
middle = self.findMiddle(head)
fir = head
sec = self.reverseList(middle.next)
middle.next = None
self.mergeTwo(fir,sec)
def reverseList(self, head):
if not head or not head.next:
return head
newhead = None
while head:
tmp = head.next
head.next = newhead
newhead = head
head = tmp
return newhead
def mergeTwo(self, l1, l2):
while l1 and l2:
tmp = l1.next
l1.next = l2
l1 = tmp
tmp_ = l2.next
l2.next = l1
l2 = tmp_
def findMiddle(self, head):
fast = slow = head
while fast.next and fast.next.next:
fast = fast.next.next
slow = slow.next
return slow
这一题可以整合前面几个题目的做法,也可以做前后两指针
class Solution:
def reorderList(self, head: Optional[ListNode]) -> None:
"""
Do not return anything, modify head in-place instead.
"""
if not head or not head.next or not head.next.next:
return
vec = []
tmp = head
while tmp:
vec.append(tmp)
tmp = tmp.next
l,r = 0,len(vec)-1
while l<r:
vec[l].next = vec[r]
l += 1
vec[r].next = vec[l]
r -= 1
vec[l].next = None
4.双向链表
146.lru-cache
请你设计并实现一个满足 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) 的平均时间复杂度运行。
link
class Node:
__slots__ = 'prev','next','key','val'
def __init__(self, key=0, val=0):
self.key = key #节点存储key值是为了删除时可以在字典中定位
self.val = val
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.dummy = Node()
self.dummy.prev = self.dummy
self.dummy.next = self.dummy
self.map = {}
def removeNode(self, node):
#删除一个节点/抽出一本书
node.prev.next = node.next
node.next.prev = node.prev
def pushTop(self, node):
#添加头节点/把书放到最上面
#这里每个节点的prev是它上面的书,next是下面的书
#对于哨兵节点dummy,它的prev是最底部,next是最上面
node.prev = self.dummy
node.next = self.dummy.next
node.prev.next = node
node.next.prev = node
def getNode(self, key):
#没有这本书
if key not in self.map:
return None
#有这本书,则先抽出来再放到最上面/删除节点再把节点添加为头节点
self.removeNode(self.map[key])
self.pushTop(self.map[key])
return self.map[key]
def get(self, key: int) -> int:
node = self.getNode(key)
return node.val if node else -1
def put(self, key: int, value: int) -> None:
node = self.getNode(key)
if node:
#有这本书,则只需要更新value
node.val = value
return
#没有这本书,则把这本新书放到最上面/添加为头节点
self.map[key] = newnode = Node(key,value)
self.pushTop(newnode)
#字典容量超出capacity,抽出最底下的书/删除底部节点
if len(self.map)>self.capacity:
bottom = self.dummy.prev
self.removeNode(bottom)
del self.map[bottom.key]
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
这道题的参考写的很清楚,用堆书比喻:link
5.哈希
138.copy-list-with-random-pointer
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。
link
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return None
hashdic = {}
cur = head
while cur:
hashdic[cur] = Node(cur.val)
cur = cur.next
cur = head
while cur:
hashdic[cur].next = hashdic.get(cur.next,None)
hashdic[cur].random = hashdic.get(cur.random, None)
cur = cur.next
return hashdic[head]
参考:link