LeetCode Hot 100
1 Tree
1.1 Recursion
- 递归三角:
node
,node.left
,node.right
构成一个递归三角,即当前节点,左子树,右子树 - 递: 调用递归函数是用来向下递的
- 归: return是用来返回给父节点的, 比如
return root
,return value
- 边界条件: 用于终止向下递,要考虑空节点的情况,即当前节点,左子树,右子树都为空
- 先记录当前节点,再向左子树递,最后向右子树递
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
self.res = []
def dfs(node):
if node is None: return
self.res.append(node.val) # 前序位置
dfs(node.left)
dfs(node.right)
dfs(root)
return self.res
- 先向左子树递,再记录当前节点,最后向右子树递
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
self.res = []
def dfs(node):
if node is None: return
dfs(node.left)
self.res.append(node.val) # 中序位置
dfs(node.right)
dfs(root)
return self.res
- 先向左子树递,再向右子树递,最后记录当前节点
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
self.res = []
def dfs(node):
if node is None: return
dfs(node.left)
dfs(node.right)
self.res.append(node.val) # 后序位置
dfs(root)
return self.res
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root: return 0
l_depth = self.maxDepth(root.left)
r_depth = self.maxDepth(root.right)
return max(l_depth, r_depth) + 1
- 两端点一定是叶子结点,因为如果不是叶子结点,则会继续走向叶子结点
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
self.ans = 0
def dfs(node):
if node is None: return -1 # 叶子结点返回0给父节点
l_len = dfs(node.left)
r_len = dfs(node.right)
self.ans = max(self.ans, l_len + r_len + 2)
return max(l_len, r_len) + 1
dfs(root)
return self.ans
- p.val和q.val是否相同
- 子问题:左子树和左子树是否相同,右子树和右子树是否相同
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if p is None or q is None:
return p is q
return p.val == q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
- p.left和q.right比较,p.right和q.left比较
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if p is None or q is None:
return p is q
return p.val == q.val and self.isSameTree(p.left, q.right) and self.isSameTree(p.right, q.left)
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
return self.isSameTree(root.left, root.right)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root: return
left = self.invertTree(root.left)
right = self.invertTree(root.right)
root.left, root.right = right, left
return root
1.2 LevelOrderTraversal
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return []
ans = []
from collections import deque
q = deque([root])
while q:
tmp = []
for _ in range(len(q)): # for loop [], until it's full
node = q.popleft()
tmp.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
ans.append(tmp)
return ans
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return []
ans = []
from collections import deque
q = deque([root])
while q:
tmp = []
for _ in range(len(q)): # for loop [], until it's full
node = q.popleft()
tmp.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
ans.append(tmp[::-1] if len(ans)%2 else tmp)
return ans
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
if root is None:
return
from collections import deque
q = deque([root])
tmp = []
while q:
node = q.popleft()
tmp.append(node.val)
if node.right: q.append(node.right)
if node.left: q.append(node.left)
return tmp[-1]
- 二叉树的完全性检验
- 思路:以BFS遍历,第一次遇到空节点flag=True,如果之后遇到的节点都是空节点,则为完全二叉树
- 细节: 不需要判断if node.left和if node.left,因为在下一次出队时,无论是None还是非空,都会被判断。如果Node为空,则不会再继续入队,q终将会完全出队(则此时为完全二叉树),或半路打断(则此时为非完全二叉树)。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isCompleteTree(self, root: Optional[TreeNode]) -> bool:
q = deque([root])
flag = False
while q:
node = q.popleft()
if not node:
flag = True
else:
if flag: return False # flag为true时遇到非空节点, 说明不是完全二叉树
q.append(node.left) # 无需判断node.left是否为空
q.append(node.right)
return True
# Time Complexity: O(n), where n is the total number of nodes in the binary tree.
# Space Complexity: O(n), because in the worst case, the queue can hold all nodes of the tree at one level, and we are storing all nodes in memory.
1.3 BST
BST(Binary Search Tree),二叉搜索树具有左子树 < Root < 右子树的性质。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
def dfs(nums, lo, hi):
if lo > hi: return
# 以升序数组的中间元素作为根节点 root
mid = lo + (hi - lo) // 2
root = TreeNode(nums[mid])
root.left = dfs(nums, lo, mid - 1)
root.right = dfs(nums, mid + 1, hi)
return root
return dfs(nums, 0, len(nums)-1)
1.4 Common Ancestor
https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/
https://leetcode.cn/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/
2 Sliding Window
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
ans = 0
cnt = Counter()
left = 0
for right, c in enumerate(s): # add the new character to the s, from right
cnt[c] += 1
while cnt[c] > 1: # abcb -> bcb -> cb
cnt[s[left]] -= 1
left += 1
ans = max(ans, right - left + 1)
return ans
- 找到字符串中所有字母异位词: https://leetcode.cn/problems/find-all-anagrams-in-a-string
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
n, m, res = len(s), len(p),[]
if n < m: return res
p_cnt = [0] * 26
s_cnt = [0] * 26
for i in range(m): # initialize p_cnt
p_cnt[ord(p[i]) - ord("a")] += 1
s_cnt[ord(s[i]) - ord("a")] += 1
if s_cnt == p_cnt:
res.append(0)
for i in range(m,n): # sliding window
s_cnt[ord(s[i-m])-ord("a")] -= 1
s_cnt[ord(s[i])-ord("a")] += 1
if s_cnt == p_cnt:
res.append(i-m+1) # start index
return res
3 Prefix Sum
- 计算前缀和s,注意要初始化一个0
- 因为s[j] - s[i] = k,所以s[j] - k = s[i]
- 每次寻找 s[j] 前面有多少个前缀和等于 s[j]−k, 用哈希表记录前缀和的个数,就能 O(1) 算出前面有多少个 s[j]−k
- 遍历前缀和s, ans += cnt[s[j] - k], cnt[s[i]] += 1,顺序不能颠倒,因为如果k=0,会多记一次
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
s = [0] * (len(nums) + 1)
for i, num in enumerate(nums):
s[i+1] = s[i] + num
ans = 0
cnt = Counter()
for sj in s:
ans += cnt[sj - k]
cnt[sj] += 1
return ans
4 Stack
class Solution:
def isValid(self, s: str) -> bool:
if len(s)%2 != 0:
return False
mp = {"}":"{", ")":"(", "]":"["}
st = []
for c in s:
if c not in mp: # c是左括号
st.append(c)
elif not st or st.pop() != mp[c]: # c是右括号
return False # 没有左括号,或右括号不匹配
return not st # 所有左括号必须匹配完毕,检查左括号比右括号多的情况,如{{}
5 Linked List
5.1 反转链表
- 初始化:pre指向null,cur指向head
- 先暂存head.next,再改变head.next,最后更新head:
- tmp = cur.next
- cur.next = pre
- pre = cur
- cur = tmp
- 必记性质:反转结束后,pre指向这一段的末尾,cur指向这一段末尾的下一个节点
# 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]:
pre, cur = None, head
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
92.反转链表II: https://leetcode.cn/problems/reverse-linked-list-ii/
5.2 快慢指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
- 关注fast和slow的相对运动,相当于fast相对运动一格,如果有环,最终fast肯定会与slow相遇
# 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:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if fast is slow:
return True
return False
- 哈希表记录遍历过的节点, 记录大于2表示有环,且为入口
# 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]:
visited = Counter()
while head:
if visited[head]:
return head
visited[head] += 1
head = head.next
return None
Follow up: Can you solve it using O(1) (i.e. constant) memory?
- 最差情况: 当慢指针进入环时,快指针刚好在慢指针前面,根据相对距离分析,快指针需要走(环长-1)步的相对距离才能追上慢指针,其余任何情况快指针走的步数都小于(环长-1),所以慢指针移动的距离是小于环长的。
- slwo和fast相遇之后:head从出发点出发,slow从相遇点出发,head和slow相遇的位置必定是入口,这是因为设相遇点到入口的距离为c, a - c = (k - 1)(b + c)
# 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]:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if fast is slow: # 有环
while slow is not head:
slow = slow.next
head = head.next
return slow
return None
- 找到链表中点,反转链表
- 先暂存head.next,再改变head.next,最后更新head:
- tmp = head.next
- tmp2 = head2.next
- head.next = head2
- head2.next = tmp
- head = tmp
- head2 = tmp2
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head):
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
def reverseList(self, head):
pre = None
cur = head
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
def reorderList(self, head: Optional[ListNode]) -> None:
mid = self.middleNode(head)
head2 = self.reverseList(mid)
while head2.next:
tmp = head.next
tmp2 = head2.next
head.next = head2
head2.next = tmp
head = tmp
head2 = tmp2
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head):
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
def reverseList(self, head):
pre = None
cur = head
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
def isPalindrome(self, head: Optional[ListNode]) -> bool:
if head is None:
return True
mid = self.middleNode(head)
head2 = self.reverseList(mid)
while head2:
if head.val != head2.val:
return False
head = head.next
head2 = head2.next
return True
5.3 删除链表
5.4 前后指针
- 初始化: A指向headA,B指向headB,同时开始向前遍历
- 遍历链表: a + (b - c) = b + (a - c)
# 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 = headA
B = headB
while A!=B:
A = A.next if A else headB
B = B.next if B else headA
return A
6 Two Pointers
6.1 相向双指针
- 容器的高度取决于短线,容器的宽度取决于这两条线的距离(下标的差)
7 Dynamic Programing
动态规划五部曲:
- 确定dp数组以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
7.1 0-1背包
7.2 完全背包
1. 确定dp数组以及下标的含义
dp[j]: 凑齐j金额的最小硬币数
2. 确定递推公式
dp[j] = min(d[j], d[j-coin] + 1)
3. dp数组如何初始化
dp = [float("inf")] * (amount + 1),
dp[0] = 0,表示容量为0的背包,装0个
4. 确定遍历顺序
默认循环外层物品,内层背包,由于是完全背包,不需要考虑物品只能拿一次,所以从前向后遍历dp数组。
所以,coins(物品)放在外循环,target(背包)在内循环。且内循环正序。
5. 举例推导dp数组
以输入:coins = [1, 2, 5], amount = 5为例
dp = [0, inf,inf, inf,inf, inf]
加入1,考虑1,dp = [0, 1, 2, 3, 4, 5]
加入2,考虑1, 2,dp = [0, 1, 1, 2, 2, 3]
加入5,考虑1, 2, 5, dp = [0, 1, 1, 2, 2, 1]
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
dp = [float("inf")] * (amount + 1)
dp[0] = 0
for coin in coins:
for j in range(coin, amount + 1):
dp[j] = min(dp[j], dp[j - coin] + 1)
return dp[-1] if dp[-1] != float("inf") else -1 # 如果dp[-1]是inf,说明无法凑齐该金额
7.3 打家劫舍
1. 确定dp数组以及下标的含义
dp[]
2. 确定递推公式
3. dp数组如何初始化
4. 确定遍历顺序
5. 举例推导dp数组
7.4 子序列/子串
- 子序列(Subsequence): 不连续
- 子数组(Subarray)和子串(Substring):连续
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
m, n = len(text1), len(text2)
dp = [[0] * (n+1) for _ in range(m+1)]
for i, x in enumerate(text1):
for j, y in enumerate(text2):
if x == y:
dp[i+1][j+1] = dp[i][j] + 1
else:
dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j])
return dp[m][n]
-
哈希表
num_set
:通过哈希表存储数组中的所有元素,支持常数时间复杂度的查找操作。 -
跳过非起点的数字:通过检查
num - 1
是否存在,我们确保每个序列只被完整计算一次,避免了重复计算。 -
举例推导
我们以 nums = [100, 4, 200, 1, 3, 2] 为例:
- 将数组 nums 转换为集合 set(nums)
- 逐个遍历数组:
- 遍历 100:99 不在集合中,因此它是一个序列的起点。当前序列只有 100,长度为 1。
- 遍历 4:3 在集合中,因此跳过,不处理(因为 4 已经被包含在以 1 开始的序列中)。
- 遍历 200:199 不在集合中,因此它是一个序列的起点。当前序列只有 200,长度为 1。
- 遍历 1:0 不在集合中,因此它是一个序列的起点。检查 2、3、4 都在集合中,最长序列为 [1, 2, 3, 4],长度为 4。
最终最长序列的长度为 4。
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
if not nums:
return 0
nums = set(nums) # 去重
max_cnt = 0
for num in nums:
if num - 1 not in nums: # num没有前一个元素,说明cur是作为连续序列的第一个元素
cur = num
cnt = 1
while cur + 1 in nums: # check the consecutive sequence
cur += 1
cnt += 1
max_cnt = max(cnt, max_cnt)
return max_cnt
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
hash_l = Counter(ransomNote)
hash_c = Counter(magazine)
for l in hash_l:
if hash_l[l] > hash_c.get(l,0):
return False
return True