5 普通数组
5.1 【动态规划】最大子数组和
题目链接:https://leetcode.cn/problems/maximum-subarray/description/?envType=study-plan-v2&envId=top-100-liked
利用动态规划,首先定义数组 d p [ i ] dp[i] dp[i],表示终点的下标为 i i i的序列的最大子数和,那么主要取决于以下两种情况:
-
如果 d p [ i − 1 ] dp[i-1] dp[i−1]大于0,则加上当前的数字作为 d p [ i ] dp[i] dp[i]的子数和
-
如果 d p [ i − 1 ] dp[i-1] dp[i−1]小于0,则停止计算子数和,从第 i i i个开始重新计算,赋值为0,并加上当前的数字作为 d p [ i ] dp[i] dp[i]的子数和
最后的最大子数组和就是这个数组中最大的那个
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
ans = -10001
cur = 0
for n in nums:
cur += n
ans = max(cur,ans)
if cur <= 0:
cur = 0
return ans
5.2 【排序】合并区间
题目链接:https://leetcode.cn/problems/merge-intervals/description/?envType=study-plan-v2&envId=top-100-liked
首先对区间进行排序,然后遍历并合并重叠区间,最终返回合并后的非重叠区间列表。
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort()
l,r = intervals[0][0],intervals[0][1]
ans = []
for i in range(1,len(intervals)):
if intervals[i][0] <= r:
r = max(intervals[i][1],r)
else:
ans.append([l,r])
l,r = intervals[i][0],intervals[i][1]
if [l,r] not in ans:
ans.append([l,r])
return ans
5.3 【数组】轮转数组
题目链接:https://leetcode.cn/problems/rotate-array/description/?envType=study-plan-v2&envId=top-100-liked
- 翻转整个数组。
- 翻转前
k
个元素,将它们移到数组的开始。 - 翻转剩余的元素,将它们移到正确的位置。
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
def reverse(i,j):
while i < j:
nums[i],nums[j] = nums[j],nums[i]
i += 1
j -= 1
l = len(nums)
if k!= 0:
k %= l
reverse(0,l-1)
reverse(0,k-1)
reverse(k,l-1)
5.4 【前缀和】除自身以外数组的乘积
分别计算索引左边的数字乘积和索引右边的数字乘积,两者乘积结果即为该索引的乘积结果。
# 方法一
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
length = len(nums)
left_ans, right_ans, answer = [0]*length, [0]*length, [0]*length
left_ans[0] = 1
for i in range(1,length):
left_ans[i] = left_ans[i-1]*nums[i-1]
right_ans[length-1] = 1
for i in range(length-2,-1,-1):
right_ans[i] = right_ans[i+1]*nums[i+1]
for i in range(length):
answer[i] = left_ans[i]*right_ans[i]
return answer
# 方法二——不需要额外空间
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
suf = [1] * n
for i in range(n - 2, -1, -1):
suf[i] = suf[i + 1] * nums[i + 1]
pre = 1
for i, x in enumerate(nums):
suf[i] *= pre
pre *= x
return suf
5.5 【哈希表】缺失的第一个正数
题目链接:https://leetcode.cn/problems/first-missing-positive/?envType=study-plan-v2&envId=top-100-liked
这个问题可以通过将数组中的每个数字放到它应该在的位置来解决,即 nums[i] 应该放在 nums[i-1] 的位置,相当于把原来的数组当作一个哈希表来使用,具体步骤如下:
-
初始化:设置 max_len 为数组长度加1,这是我们需要处理的最大正整数。
-
标记无效数字:将所有非正数或大于 n 的数字设置为0。
-
放置数字:
-
- 对于每个数字 nums[i],如果它在范围内(1 到 max_len - 1),则将其放到正确的位置 nums[nums[i] % max_len - 1],并加上数组的最大长度,标记这个位置的数已经出现过了。
-
- 使用一个循环来重复这个过程,直到所有在范围内的数字都被放到了正确的位置。
-
查找缺失的最小正整数:
-
- 遍历数组,如果 nums[i] 小于 max_len,则 i+1 就是缺失的最小正整数。
-
- 如果所有位置都填满了,那么缺失的最小正整数就是 max_len。
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
n = len(nums)
max_len = n+1
for i in range(n):
if nums[i] <= 0 or nums[i] >= max_len:
nums[i] = 0
for i in range(n):
if nums[i] % max_len != 0:
cur = (nums[i] % max_len) - 1
nums[cur] = (nums[cur] % max_len) + max_len
for i in range(n):
if nums[i] < max_len:
return i+1
return max_len
6 矩阵
6.1 【哈希表】矩阵置零
用第一行和第一列的元素来表示每一行或者每一列是否存在零。
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
row = len(matrix)
col = len(matrix[0])
zero_first = [False,False]
# 第一行是否有零
for i in range(col):
if matrix[0][i] == 0:
zero_first[0] = True
break
# 第一列是否有零
for i in range(row):
if matrix[i][0] == 0:
zero_first[1] = True
break
# 记录其他行和列的零
for i in range(1,row):
for j in range(1,col):
if matrix[i][j] == 0:
matrix[i][0] = matrix[0][j] = 0
# 矩阵置零
for i in range(1,row):
for j in range(1,col):
if matrix[i][0] == 0 or matrix[0][j] == 0:
matrix[i][j] = 0
if zero_first[0] == True:
for i in range(col):
matrix[0][i] = 0
if zero_first[1] == True:
for i in range(row):
matrix[i][0] = 0
6.2 【模拟】螺旋矩阵
题目链接:https://leetcode.cn/problems/spiral-matrix/description/?envType=study-plan-v2&envId=top-100-liked
按照题目的意思,从左往右,从上往下,从右往左,从下往上,依次遍历矩阵元素,每次遍历完判断是否越界。
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
l,r,t,b,res = 0,len(matrix[0])-1,0,len(matrix)-1,[]
while True:
# left to right
for i in range(l,r+1):
res.append(matrix[t][i])
t += 1
if t > b:
break
# top to bottom
for i in range(t,b+1):
res.append(matrix[i][r])
r -= 1
if r < l:
break
# right to left
for i in range(r,l-1,-1):
res.append(matrix[b][i])
b -= 1
if b < t:
break
# bottom to top
for i in range(b,t-1,-1):
res.append(matrix[i][l])
l += 1
if l > r:
break
return res
6.3 【模拟】旋转图像
题目链接:https://leetcode.cn/problems/rotate-image/description/?envType=study-plan-v2&envId=top-100-liked
主对角线翻转+左右翻转。
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
length = len(matrix)
# 主对角线
for i in range(length):
for j in range(i+1,length):
matrix[i][j],matrix[j][i] = matrix[j][i],matrix[i][j]
# 左右
for i in range(length):
for j in range(length//2):
matrix[i][j],matrix[i][length-j-1] = matrix[i][length-j-1],matrix[i][j]
6.4 【分治】搜索二维矩阵 II
从矩阵左下角开始遍历,如果比当前的数小则索引上移,如果比当前的数大则索引右移。
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
i,j = len(matrix)-1,0
while i >= 0 and j < len(matrix[0]):
if matrix[i][j] > target:
i -= 1
elif matrix[i][j] < target:
j += 1
else:
return True
return False
7 链表
7.1 【双指针】相交链表
让A和B同时遍历,A遍历完了换到B的开始,B遍历完了换到A的开始,两者相等时就是遇到了公共节点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
A,B = headA,headB
while A != B:
A = A.next if A else headB
B = B.next if B else headA
return A
7.2 【链表】反转链表
头插法。
# 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]:
cur,pre = head,None
while cur:
cur.next,pre,cur = pre,cur,cur.next
return pre
7.3 【双指针】【递归】回文链表
首先找到链表的中间位置,然后对链表的后半部分进行翻转,最后比较翻转后的部分和前半部分是否一致。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def isPalindrome(self, head: Optional[ListNode]) -> bool:
if not head:
return True
# search for the median node
fast,slow = head,head
while fast.next and fast.next.next:
fast = fast.next.next
slow = slow.next
# reverse the right listnode
cur,pre = slow.next,None
while cur:
cur.next,pre,cur = pre,cur,cur.next
# compare
while pre:
if head.val != pre.val:
return False
head,pre = head.next,pre.next
return True
7.4 【双指针】环形链表
定义快慢指针,快指针一次走两步,慢指针一次走一步,如果存在环,则两个指针总会相遇,否则不存在环。
# 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 not head:
return False
fast,slow = head,head
while fast.next and fast.next.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
return True
return False
7.5 【双指针】环形链表 II
这道题在环形链表的基础上做了升级,首先还是用双指针判断链表是否成环,如果成环的话,此时让head和slow同时移动,最后相遇的时候就是环形开始的地方。
# 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]:
if not head:
return None
fast,slow = head,head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
while slow != head:
slow = slow.next
head = head.next
return slow
return None
7.6 【链表】合并两个有序链表
常规做法,挨个比较大小。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
l1p,l2p = list1,list2
ans = l = ListNode()
while l1p and l2p:
if l1p.val <= l2p.val:
l.next = ListNode(l1p.val)
l1p = l1p.next
l = l.next
else:
l.next = ListNode(l2p.val)
l2p = l2p.next
l = l.next
while l1p:
l.next = ListNode(l1p.val)
l1p = l1p.next
l = l.next
while l2p:
l.next = ListNode(l2p.val)
l2p = l2p.next
l = l.next
return ans.next
7.7 【链表】两数相加
题目链接:https://leetcode.cn/problems/add-two-numbers/description/?envType=study-plan-v2&envId=top-100-liked
挨个相加,保留进位,最后判断是否有多余的进位。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
ans = l = ListNode()
sign = 0
while l1 or l2:
l1_num = l1.val if l1 else 0
l2_num = l2.val if l2 else 0
num = l1_num + l2_num + sign
l.next = ListNode(num%10)
sign = num//10
l = l.next
if l1:
l1 = l1.next
if l2:
l2 = l2.next
if sign:
l.next = ListNode(sign)
return ans.next
7.8 【双指针】删除链表的倒数第 N 个结点
利用双指针left和right,首先让right遍历n个节点,再让两个指针同时遍历,当right遍历到链表结尾时,left所指的就是倒数第n个节点,正常删除即可。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
left = right = head
cnt = 0
while cnt < n:
right = right.next
cnt += 1
if not right:
return head.next
while right.next:
left = left.next
right = right.next
left.next = left.next.next
return head
7.9 【递归】两两交换链表中的节点
详见代码。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
node0 = dummy = ListNode(next=head)
node1 = head
while node1 and node1.next:
node2 = node1.next
node3 = node2.next
node0.next = node2
node2.next = node1
node1.next = node3
node0 = node1
node1 = node3
return dummy.next
7.10 【链表】K 个一组翻转链表
详见代码。
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
n = 0
cur = head
while cur:
n += 1
cur = cur.next
p0 = dummy = ListNode(next=head)
pre = None
cur = head
while n >= k:
n -= k
for _ in range(k):
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
nxt = p0.next
nxt.next = cur
p0.next = pre
p0 = nxt
return dummy.next
7.11 【哈希表】随机链表的复制
利用哈希表,首先构建链表中的每个结点,再根据哈希表中的结点分别构造next和random引用,相当于对原始链表遍历两次。
"""
# 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]':
dict = {}
cur = head
while cur:
dict[cur] = Node(cur.val)
cur = cur.next
cur = head
while cur:
dict[cur].next = dict.get(cur.next)
dict[cur].random = dict.get(cur.random)
cur = cur.next
if not head:
return head
else:
return dict[head]
7.12 【排序】排序链表
题目链接:https://leetcode.cn/problems/sort-list/description/?envType=study-plan-v2&envId=top-100-liked
首先找到链表的中间位置,然后使用归并排序以此递归遍历链表。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head or not head.next:
return head
slow,fast = head,head.next
while fast and fast.next:
slow,fast = slow.next,fast.next.next
mid = slow.next
slow.next = None
left,right = self.sortList(head),self.sortList(mid)
h = ans = ListNode()
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 ans.next
7.13 【分治】合并 K 个升序链表
两两合并。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
def merge(l1,l2):
cur = tmp = ListNode()
while l1 and l2:
if l1.val < l2.val:
tmp.next = l1
l1 = l1.next
else:
tmp.next = l2
l2 = l2.next
tmp = tmp.next
if l1:
tmp.next = l1
if l2:
tmp.next = l2
return cur.next
ans = None
for i in lists:
ans = merge(ans,i)
return ans
7.14 【哈希表】LRU 缓存
题目链接:https://leetcode.cn/problems/lru-cache/description/?envType=study-plan-v2&envId=top-100-liked
详见代码,通过构造双向链表和哈希表来模拟LRU缓存,要注意及时将最久未使用的结点移动到链表末尾,便于删除,这里采取的措施是定义一个函数,将链表中的某个结点移动到链表末尾,在get和put方法中都会遇到这种情况。
class ListNode:
# 双向链表构建
def __init__(self, key=None, value=None):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.hashmap = {}
# 头结点head和尾结点tail
self.head = ListNode()
self.tail = ListNode()
# 初始化双向链表
self.head.next = self.tail
self.tail.prev = self.head
# 将链表中的某个结点移到链表末尾tail
def move_to_tail(self, key):
node = self.hashmap[key]
# 取出node结点
node.next.prev = node.prev
node.prev.next = node.next
# 移到末尾
node.next = self.tail
node.prev = self.tail.prev
self.tail.prev.next = node
self.tail.prev = node
def get(self, key: int) -> int:
if key in self.hashmap:
self.move_to_tail(key)
res = self.hashmap.get(key)
if res == None:
return -1
else:
return res.value
def put(self, key: int, value: int) -> None:
if key in self.hashmap:
self.hashmap[key].value = value
self.move_to_tail(key)
else:
if len(self.hashmap) == self.capacity:
# 删除最久未访问结点
self.hashmap.pop(self.head.next.key)
self.head.next = self.head.next.next
self.head.next.prev = self.head
newNode = ListNode(key, value)
self.hashmap[key] = newNode
newNode.prev = self.tail.prev
newNode.next = self.tail
self.tail.prev.next = newNode
self.tail.prev = newNode
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)