LeetCode HOT100 简单题
LeetCode HOT100 简单题详细学习版
目录
- LC 1 两数之和
- LC 20 有效的括号
- LC 21 合并两个有序链表
- LC 70 爬楼梯
- LC 94 二叉树的中序遍历
- LC 101 对称二叉树
- LC 104 二叉树的最大深度
- LC 121 买卖股票的最佳时机
- LC 136 只出现一次的数字
- LC 141 环形链表
- LC 160 相交链表
- LC 169 多数元素
- LC 206 反转链表
- LC 226 翻转二叉树
- LC 234 回文链表
- LC 283 移动零
- LC 338 比特位计数
- LC 448 找到所有数组中消失的数字
- LC 461 汉明距离
- LC 543 二叉树的直径
- LC 617 合并二叉树
1. LC 1 两数之和
难度:简单
标签:数组、哈希表
原题简述
给定数组和 target,返回两个不同下标,使 nums[i]+nums[j]=target。
知识点介绍
哈希表把“找补数”变成 O(1) 查询。
标准思路
遍历每个数 num,检查 target-num 是否已经出现;没出现就记录 num 的下标。
最优标准解
from typing import List
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
seen = {} # 记录已经遍历过的数字:数字 -> 下标
for i, num in enumerate(nums): # 枚举当前数字,尝试和前面数字配对
need = target - num # 当前数字需要 need 这个补数才能凑成 target
if need in seen: # 补数已经在前面出现,当前下标和补数下标就是答案
return [seen[need], i] # 返回补数的下标和当前数字的下标
seen[num] = i # 当前数字暂时没有配对成功,存起来给后面的数字用
复杂度
时间 O(n),空间 O(n)。
易错点
先查补数再存当前数,避免同一个元素被用两次。
多解补充
多解 1:暴力枚举
取舍说明: 能做但 O(n²),只适合理解题意。
from typing import List
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(len(nums)): # 枚举当前数字,尝试和前面数字配对
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target: # 检查这一对数字是否正好等于 target
return [i, j]
快速复习
查配对:哈希表记录已出现数字。
2. LC 20 有效的括号
难度:简单
标签:字符串、栈
原题简述
判断括号字符串是否按正确类型和顺序闭合。
知识点介绍
后打开的左括号必须先被关闭,符合栈的后进先出。
标准思路
左括号入栈;右括号检查栈顶是否是对应左括号;最后栈为空才有效。
最优标准解
class Solution:
def isValid(self, s: str) -> bool:
stack = [] # 保存还没有匹配成功的左括号
match = {')': '(', ']': '[', '}': '{'} # 记录每种右括号对应的左括号
for ch in s: # 从左到右检查每个括号
if ch in match: # 遇到右括号,需要和栈顶左括号匹配
if not stack or stack[-1] != match[ch]: # 栈空或栈顶类型不对,括号无效
return False # 当前情况不符合题意
stack.pop() # 匹配成功,弹出这个左括号
else:
stack.append(ch) # 左括号入栈,等待后续匹配
return not stack # 栈空或栈顶类型不对,括号无效
复杂度
时间 O(n),空间 O(n)。
易错点
右括号出现时栈可能为空;最后要检查栈为空。
多解补充
多解 1:手写分支
取舍说明: 可通过,但重复判断多,不如映射表清晰。
class Solution:
def isValid(self, s: str) -> bool:
stack = [] # 保存还没有匹配成功的左括号
for ch in s: # 从左到右检查每个括号
if ch in '([{': # 遇到右括号,需要和栈顶左括号匹配
stack.append(ch) # 左括号入栈,等待后续匹配
else:
if not stack: # 栈空或栈顶类型不对,括号无效
return False # 当前情况不符合题意
top = stack[-1] # 栈空或栈顶类型不对,括号无效
if (ch == ')' and top == '(') or (ch == ']' and top == '[') or (ch == '}' and top == '{'):
stack.pop() # 匹配成功,弹出这个左括号
else:
return False # 当前情况不符合题意
return not stack # 栈空或栈顶类型不对,括号无效
快速复习
括号匹配:左入栈,右查栈顶。
3. LC 21 合并两个有序链表
难度:简单
标签:链表、递归
原题简述
合并两个升序链表,返回合并后的升序链表头。
知识点介绍
链表题核心是改 next 指针;dummy 统一处理头节点。
标准思路
比较两个链表当前节点,小的接到结果尾部;某条为空后直接接另一条剩余部分。
最优标准解
from typing import Optional
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode() # 虚拟头节点,方便统一处理链表头部变化
cur = dummy # 虚拟头节点,方便统一处理链表头部变化
while list1 and list2: # 两个链表都还有节点时,比较当前值
if list1.val <= list2.val: # list1 当前节点更小或相等,先接 list1
cur.next = list1 # 把 list1 当前节点接到结果链表后面
list1 = list1.next # list1 后移到下一个节点
else:
cur.next = list2 # 把 list2 当前节点接到结果链表后面
list2 = list2.next # list2 后移到下一个节点
cur = cur.next # 结果链表尾指针后移
cur.next = list1 if list1 else list2 # 把 list1 当前节点接到结果链表后面
return dummy.next # 返回虚拟头节点后面的真实链表头
复杂度
时间 O(m+n),空间 O(1)。
易错点
返回 dummy.next;接完节点后原链表指针要后移。
多解补充
多解 1:递归
取舍说明: 同样优秀,代码短;缺点是递归栈 O(m+n)。
from typing import Optional
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 当前节点更小或相等,先接 list1
list1.next = self.mergeTwoLists(list1.next, list2)
return list1
else:
list2.next = self.mergeTwoLists(list1, list2.next)
return list2
快速复习
有序链表合并:dummy + 每次接小的。
4. LC 70 爬楼梯
难度:简单
标签:动态规划
原题简述
每次爬 1 或 2 阶,求到第 n 阶的方法数。
知识点介绍
到第 i 阶只可能来自 i-1 或 i-2。
标准思路
f(i)=f(i-1)+f(i-2),只保留前两项即可。
最优标准解
class Solution:
def climbStairs(self, n: int) -> int:
if n <= 2:
return n
prev2, prev1 = 1, 2
for _ in range(3, n + 1):
cur = prev1 + prev2
prev2, prev1 = prev1, cur
return prev1
复杂度
时间 O(n),空间 O(1)。
易错点
n=2 的答案是 2;普通递归会超时。
多解补充
多解 1:DP 数组
取舍说明: 更直观但空间 O(n)。
class Solution:
def climbStairs(self, n: int) -> int:
if n <= 2:
return n
dp = [0] * (n + 1) # 创建 DP 表,保存子问题答案
dp[1], dp[2] = 1, 2 # 创建 DP 表,保存子问题答案
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2] # 创建 DP 表,保存子问题答案
return dp[n]
快速复习
爬楼梯就是斐波那契。
5. LC 94 二叉树的中序遍历
难度:简单
标签:树、DFS、栈
原题简述
返回二叉树中序遍历结果。
知识点介绍
中序顺序固定为左、根、右。
标准思路
递归处理左子树,记录当前值,再递归右子树。
最优标准解
from typing import List, Optional
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
ans = []
def dfs(node):
if not node: # 空节点是递归边界
return # 结束当前递归分支
dfs(node.left) # 递归处理左子树
ans.append(node.val) # 中序位置访问当前节点
dfs(node.right) # 递归处理右子树
dfs(root)
return ans # 返回最终答案
复杂度
时间 O(n),空间 O(h)。
易错点
中序是左根右;空树返回 []。
多解补充
多解 1:迭代栈
取舍说明: 同样优秀;不用递归时写这个。
from typing import List, Optional
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
ans, stack = [], []
cur = root
while cur or stack:
while cur:
stack.append(cur) # 加入当前路径、节点或中间结果
cur = cur.left
cur = stack.pop()
ans.append(cur.val)
cur = cur.right
return ans # 返回最终答案
快速复习
中序遍历:左、根、右。
6. LC 101 对称二叉树
难度:简单
标签:树、DFS、BFS
原题简述
判断二叉树是否轴对称。
知识点介绍
对称比较镜像位置:左左对右右,左右对右左。
标准思路
递归比较两个节点是否互为镜像:都空真,一个空假,值不同假,然后比较外侧和内侧。
最优标准解
from typing import Optional
class Solution:
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
def check(a, b):
if not a and not b:
return True # 当前检查通过
if not a or not b or a.val != b.val:
return False # 当前情况不符合题意
return check(a.left, b.right) and check(a.right, b.left)
return True if not root else check(root.left, root.right) # 交换当前节点左右孩子
复杂度
时间 O(n),空间 O(h)。
易错点
比较镜像位置,不是同方向位置。
多解补充
多解 1:BFS 队列
取舍说明: 同样优秀,一对一对比较镜像节点。
from collections import deque
from typing import Optional
class Solution:
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
if not root:
return True # 当前检查通过
q = deque([(root.left, root.right)]) # 交换当前节点左右孩子
while q:
a, b = q.popleft()
if not a and not b:
continue
if not a or not b or a.val != b.val:
return False # 当前情况不符合题意
q.append((a.left, b.right)) # 加入当前路径、节点或中间结果
q.append((a.right, b.left)) # 加入当前路径、节点或中间结果
return True # 当前检查通过
快速复习
对称树:外侧对外侧,内侧对内侧。
7. LC 104 二叉树的最大深度
难度:简单
标签:树、DFS、BFS
原题简述
返回根节点到最远叶子节点路径上的节点数。
知识点介绍
树深度递归定义:空树为 0,非空为左右深度最大值加 1。
标准思路
递归求左右深度,返回 max(left,right)+1。
最优标准解
from typing import Optional
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1
复杂度
时间 O(n),空间 O(h)。
易错点
深度按节点数算;空树是 0。
多解补充
多解 1:BFS 层序
取舍说明: 同样优秀,一层一层数。
from collections import deque
from typing import Optional
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
q = deque([root]) # 队列用于按层遍历二叉树
depth = 0
while q:
depth += 1
for _ in range(len(q)):
node = q.popleft()
if node.left:
q.append(node.left) # 加入当前路径、节点或中间结果
if node.right:
q.append(node.right) # 加入当前路径、节点或中间结果
return depth
快速复习
最大深度:左右最大深度 + 1。
8. LC 121 买卖股票的最佳时机
难度:简单
标签:数组、贪心、动态规划
原题简述
只允许买卖一次股票,求最大利润。
知识点介绍
如果今天卖出,最佳买入价一定是今天之前的最低价。
标准思路
遍历价格,维护历史最低价和最大利润。
最优标准解
from typing import List
class Solution:
def maxProfit(self, prices: List[int]) -> int:
min_price = prices[0] # 记录到当前为止最低买入价
ans = 0
for price in prices[1:]:
ans = max(ans, price - min_price) # 记录到当前为止最低买入价
min_price = min(min_price, price) # 记录到当前为止最低买入价
return ans # 返回最终答案
复杂度
时间 O(n),空间 O(1)。
易错点
必须先买后卖;一路下跌时返回 0。
多解补充
多解 1:DP 持有/不持有
取舍说明: 可解释为 DP,但会被压缩成两个变量。
from typing import List
class Solution:
def maxProfit(self, prices: List[int]) -> int:
cash = 0 # cash 表示当天结束后手里没股票的最大利润
hold = -prices[0] # hold 表示当天结束后手里持有股票的最大利润
for price in prices[1:]:
cash = max(cash, hold + price) # cash 表示当天结束后手里没股票的最大利润
hold = max(hold, -price) # hold 表示当天结束后手里持有股票的最大利润
return cash # cash 表示当天结束后手里没股票的最大利润
快速复习
股票一次交易:维护最低价。
9. LC 136 只出现一次的数字
难度:简单
标签:数组、位运算
原题简述
除一个数出现一次外,其余数都出现两次,找出唯一数。
知识点介绍
异或性质:x^x=0,x^0=x。
标准思路
把所有数异或,成对数字抵消,剩下唯一数。
最优标准解
from typing import List
class Solution:
def singleNumber(self, nums: List[int]) -> int:
ans = 0 # 异或初始值为 0,因为 x ^ 0 = x
for num in nums:
ans ^= num # 成对出现的数字会被异或抵消
return ans # 返回最终答案
复杂度
时间 O(n),空间 O(1)。
易错点
异或符号是 ^;适用前提是其他数都出现两次。
多解补充
多解 1:哈希计数
取舍说明: 直观但空间 O(n)。
from typing import List
class Solution:
def singleNumber(self, nums: List[int]) -> int:
count = {}
for x in nums:
count[x] = count.get(x, 0) + 1
for x, c in count.items():
if c == 1:
return x
快速复习
成对抵消:异或。
10. LC 141 环形链表
难度:简单
标签:链表、双指针
原题简述
判断链表是否有环。
知识点介绍
快慢指针:有环时快指针会追上慢指针。
标准思路
slow 每次一步,fast 每次两步;相遇则有环,fast 到空则无环。
最优标准解
from typing import Optional
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
slow = fast = head # slow 找中点或入环点,fast 用来制造速度差
while fast and fast.next: # fast 还能走两步时,继续推进快慢指针
slow = slow.next # slow 每次走一步
fast = fast.next.next # fast 每次走两步
if slow is fast: # 快慢指针相遇,说明存在环
return True # 当前检查通过
return False # 当前情况不符合题意
复杂度
时间 O(n),空间 O(1)。
易错点
循环条件是 fast and fast.next;节点比较用 is。
多解补充
多解 1:集合记录节点
取舍说明: 直观但空间 O(n)。
from typing import Optional
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
seen = set()
while head:
if head in seen:
return True # 当前检查通过
seen.add(head)
head = head.next
return False # 当前情况不符合题意
快速复习
链表判环:快慢指针。
11. LC 160 相交链表
难度:简单
标签:链表、双指针
原题简述
返回两个链表相交的起始节点,不相交返回 None。
知识点介绍
相交是节点对象相同;双指针换头让两指针走过相同总长度。
标准思路
pA 走 A+B,pB 走 B+A;有交点相遇,无交点同时 None。
最优标准解
from typing import Optional
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
pA, pB = headA, headB # pA 从链表 A 出发
while pA is not pB: # 两个指针没走到同一节点时继续同步前进
pA = pA.next if pA else headB # pA 从链表 A 出发
pB = pB.next if pB else headA # pB 从链表 B 出发
return pA
复杂度
时间 O(m+n),空间 O(1)。
易错点
比较节点对象;不能修改链表。
多解补充
多解 1:哈希集合
取舍说明: 容易理解但空间 O(m)。
from typing import Optional
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
seen = set()
while headA: # B 走完后切到 A,抵消长度差
seen.add(headA) # B 走完后切到 A,抵消长度差
headA = headA.next # B 走完后切到 A,抵消长度差
while headB: # A 走完后切到 B,抵消长度差
if headB in seen: # A 走完后切到 B,抵消长度差
return headB # A 走完后切到 B,抵消长度差
headB = headB.next # A 走完后切到 B,抵消长度差
return None # 没有可返回的节点或结果
快速复习
相交链表:双指针换头。
12. LC 169 多数元素
难度:简单
标签:数组、摩尔投票
原题简述
找出现次数大于 n/2 的元素,题目保证存在。
知识点介绍
摩尔投票:不同元素互相抵消,多数元素最后一定留下。
标准思路
count 为 0 时换候选人,相同加一,不同减一。
最优标准解
from typing import List
class Solution:
def majorityElement(self, nums: List[int]) -> int:
candidate, count = None, 0
for num in nums:
if count == 0:
candidate = num
count += 1 if num == candidate else -1
return candidate
复杂度
时间 O(n),空间 O(1)。
易错点
题目保证多数元素存在;否则最后要验证。
多解补充
多解 1:哈希计数
取舍说明: 直观但空间 O(n)。
from typing import List
class Solution:
def majorityElement(self, nums: List[int]) -> int:
count = {}
for x in nums:
count[x] = count.get(x, 0) + 1
if count[x] > len(nums) // 2:
return x
快速复习
多数元素:摩尔投票。
13. LC 206 反转链表
难度:简单
标签:链表、递归
原题简述
反转单链表并返回新头。
知识点介绍
核心是改变每个节点的 next 指向,先保存 next 防止断链。
标准思路
prev 表示反转后前一个节点,cur 表示当前节点。
最优标准解
from typing import Optional
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
prev, cur = None, head # prev 是反转后当前节点要指向的前一个节点
while cur:
nxt = cur.next # 先保存原来的下一个节点,避免断链
cur.next = prev # prev 是反转后当前节点要指向的前一个节点
prev = cur # prev 是反转后当前节点要指向的前一个节点
cur = nxt # 先保存原来的下一个节点,避免断链
return prev
复杂度
时间 O(n),空间 O(1)。
易错点
先保存 nxt;最后返回 prev。
多解补充
多解 1:递归
取舍说明: 同样经典,但空间 O(n)。
from typing import Optional
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head or not head.next:
return head
new_head = self.reverseList(head.next)
head.next.next = head
head.next = None
return new_head
快速复习
反转链表四步:存 next、改 next、移 prev、移 cur。
14. LC 226 翻转二叉树
难度:简单
标签:树、DFS、BFS
原题简述
交换二叉树中每个节点的左右孩子。
知识点介绍
每个节点做相同操作,递归天然适合。
标准思路
当前节点交换左右孩子,然后递归处理左右子树。
最优标准解
from typing import Optional
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root:
return None # 没有可返回的节点或结果
root.left, root.right = root.right, root.left # 交换当前节点左右孩子
self.invertTree(root.left)
self.invertTree(root.right)
return root # 返回处理后的根节点
复杂度
时间 O(n),空间 O(h)。
易错点
不是只换根节点,要换所有节点。
多解补充
多解 1:BFS
取舍说明: 同样优秀,逐层交换。
from collections import deque
from typing import Optional
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root:
return None # 没有可返回的节点或结果
q = deque([root]) # 队列用于按层遍历二叉树
while q:
node = q.popleft()
node.left, node.right = node.right, node.left
if node.left:
q.append(node.left) # 加入当前路径、节点或中间结果
if node.right:
q.append(node.right) # 加入当前路径、节点或中间结果
return root # 返回处理后的根节点
快速复习
翻转树:每个节点交换左右。
15. LC 234 回文链表
难度:简单
标签:链表、双指针
原题简述
判断链表是否正读反读相同。
知识点介绍
快慢指针找中点,反转后半段后即可从两端比较。
标准思路
slow 到中点,反转 slow 开始的后半段,前半段和反转后半段逐个比较。
最优标准解
from typing import Optional
class Solution:
def isPalindrome(self, head: Optional[ListNode]) -> bool:
slow = fast = head # slow 找中点或入环点,fast 用来制造速度差
while fast and fast.next: # fast 还能走两步时,继续推进快慢指针
slow = slow.next # slow 每次走一步
fast = fast.next.next # fast 每次走两步
prev, cur = None, slow # prev 是反转后当前节点要指向的前一个节点
while cur:
nxt = cur.next # 先保存原来的下一个节点,避免断链
cur.next = prev # prev 是反转后当前节点要指向的前一个节点
prev = cur # prev 是反转后当前节点要指向的前一个节点
cur = nxt # 先保存原来的下一个节点,避免断链
p1, p2 = head, prev # prev 是反转后当前节点要指向的前一个节点
while p2:
if p1.val != p2.val: # 前半段和反转后半段对应值不同,不是回文
return False # 当前情况不符合题意
p1, p2 = p1.next, p2.next
return True # 当前检查通过
复杂度
时间 O(n),空间 O(1)。
易错点
比较时以后半段为准;奇数长度也适用。
多解补充
多解 1:数组法
取舍说明: 最简单但空间 O(n)。
from typing import Optional
class Solution:
def isPalindrome(self, head: Optional[ListNode]) -> bool:
vals = []
while head:
vals.append(head.val) # 加入当前路径、节点或中间结果
head = head.next
return vals == vals[::-1]
快速复习
回文链表:找中点 + 反转后半段。
16. LC 283 移动零
难度:简单
标签:数组、双指针
原题简述
把所有 0 移到末尾,保持非零元素相对顺序,原地修改。
知识点介绍
slow 指向下一个非零元素应该写入的位置。
标准思路
遍历数组,遇到非零就放到 slow 位置。
最优标准解
from typing import List
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
slow = 0 # slow 指向下一个非零元素应该放的位置
for i in range(len(nums)):
if nums[i] != 0: # 非零元素需要保留到数组前面
nums[slow], nums[i] = nums[i], nums[slow] # slow 指向下一个非零元素应该放的位置
slow += 1 # slow 指向下一个非零元素应该放的位置
复杂度
时间 O(n),空间 O(1)。
易错点
函数不返回数组;要保持相对顺序。
多解补充
多解 1:覆盖后补零
取舍说明: 同样优秀,逻辑更直观。
from typing import List
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
slow = 0 # slow 指向下一个非零元素应该放的位置
for x in nums:
if x != 0:
nums[slow] = x # slow 指向下一个非零元素应该放的位置
slow += 1 # slow 指向下一个非零元素应该放的位置
for i in range(slow, len(nums)):
nums[i] = 0 # 非零元素写完后,剩余位置补 0
快速复习
移动零:slow 管非零区。
17. LC 338 比特位计数
难度:简单
标签:位运算、动态规划
原题简述
返回 0 到 n 每个数二进制中 1 的个数。
知识点介绍
i>>1 去掉最低位,i&1 判断最低位是否为 1。
标准思路
ans[i]=ans[i>>1]+(i&1)。
最优标准解
from typing import List
class Solution:
def countBits(self, n: int) -> List[int]:
ans = [0] * (n + 1)
for i in range(1, n + 1):
ans[i] = ans[i >> 1] + (i & 1)
return ans # 返回最终答案
复杂度
时间 O(n),空间 O(n)。
易错点
返回 n+1 个数;包含 0。
多解补充
多解 1:消掉最低位 1
取舍说明: 同样优秀。
from typing import List
class Solution:
def countBits(self, n: int) -> List[int]:
ans = [0] * (n + 1)
for i in range(1, n + 1):
ans[i] = ans[i & (i - 1)] + 1
return ans # 返回最终答案
快速复习
比特 DP:右移 + 最低位。
18. LC 448 找到所有数组中消失的数字
难度:简单
标签:数组、原地哈希
原题简述
数组长度 n,值在 1..n,找没出现的数字。
知识点介绍
数字 x 对应下标 x-1,出现过就把对应位置标负。
标准思路
第一遍标记,第二遍找仍为正的位置。
最优标准解
from typing import List
class Solution:
def findDisappearedNumbers(self, nums: List[int]) -> List[int]:
for x in nums:
idx = abs(x) - 1
if nums[idx] > 0:
nums[idx] = -nums[idx] # 把对应位置标负,表示这个数字出现过
ans = []
for i, x in enumerate(nums):
if x > 0:
ans.append(i + 1)
return ans # 返回最终答案
复杂度
时间 O(n),空间 O(1) 不算返回结果。
易错点
用 abs(x) 取原值;重复数字不要反复变号。
多解补充
多解 1:哈希集合
取舍说明: 直观但空间 O(n)。
from typing import List
class Solution:
def findDisappearedNumbers(self, nums: List[int]) -> List[int]:
seen = set(nums)
return [x for x in range(1, len(nums) + 1) if x not in seen]
快速复习
原地哈希:x 标记 x-1。
19. LC 461 汉明距离
难度:简单
标签:位运算
原题简述
返回两个整数二进制不同位数量。
知识点介绍
异或后 1 的位置就是不同位。
标准思路
先 x^y,再统计 1 的个数。
最优标准解
class Solution:
def hammingDistance(self, x: int, y: int) -> int:
num = x ^ y # 异或后为 1 的位,就是两个数不同的位
ans = 0
while num:
num &= num - 1 # 每次消掉 num 最右边的一个 1
ans += 1
return ans # 返回最终答案
复杂度
时间 O(k),空间 O(1)。
易错点
先异或再数 1。
多解补充
多解 1:逐位右移
取舍说明: 更直观。
class Solution:
def hammingDistance(self, x: int, y: int) -> int:
num = x ^ y # 异或后为 1 的位,就是两个数不同的位
ans = 0
while num:
ans += num & 1 # 检查当前最低位是否为 1
num >>= 1
return ans # 返回最终答案
快速复习
汉明距离:异或后数 1。
20. LC 543 二叉树的直径
难度:简单
标签:树、DFS
原题简述
返回二叉树任意两节点最长路径的边数。
知识点介绍
经过某节点的最长路径是左深度+右深度。
标准思路
后序 DFS 返回深度,同时更新全局直径。
最优标准解
from typing import Optional
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
ans = 0
def depth(node):
nonlocal ans
if not node: # 空节点是递归边界
return 0
left = depth(node.left) # 求左子树向下的最大深度
right = depth(node.right) # 求右子树向下的最大深度
ans = max(ans, left + right) # 经过当前节点的最长路径等于左右深度之和
return max(left, right) + 1
depth(root)
return ans # 返回最终答案
复杂度
时间 O(n),空间 O(h)。
易错点
直径按边数;返回给父节点的是深度。
多解补充
多解 1:重复求深度
取舍说明: 能做但 O(n²),不推荐。
from typing import Optional
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
def dep(node):
return 0 if not node else max(dep(node.left), dep(node.right)) + 1 # 空节点是递归边界
def dfs(node):
if not node: # 空节点是递归边界
return 0
return max(dep(node.left) + dep(node.right), dfs(node.left), dfs(node.right)) # 递归处理左子树
return dfs(root)
快速复习
直径:每个节点算 left+right。
21. LC 617 合并二叉树
难度:简单
标签:树、DFS、BFS
原题简述
合并两棵二叉树,重叠节点值相加,不重叠节点保留。
知识点介绍
双树递归重点处理一空一不空的情况。
标准思路
如果某棵树为空返回另一棵;都不空则值相加并递归合并左右子树。
最优标准解
from typing import Optional
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
if not root1:
return root2
if not root2:
return root1 # 返回合并后的第一棵树根节点
root1.val += root2.val # 两个节点重叠时,把值相加到 root1 上
root1.left = self.mergeTrees(root1.left, root2.left) # 递归合并左子树,并接回 root1.left
root1.right = self.mergeTrees(root1.right, root2.right) # 递归合并右子树,并接回 root1.right
return root1 # 返回合并后的第一棵树根节点
复杂度
时间 O(m+n) 级别,空间 O(h)。
易错点
递归返回值要接回左右孩子;原地合并会修改 root1。
多解补充
多解 1:创建新树
取舍说明: 不修改原树,但会创建新节点。
from typing import Optional
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
if not root1:
return root2
if not root2:
return root1 # 返回合并后的第一棵树根节点
node = TreeNode(root1.val + root2.val)
node.left = self.mergeTrees(root1.left, root2.left)
node.right = self.mergeTrees(root1.right, root2.right)
return node # 返回当前构造出的节点
快速复习
合并树:空返另一棵,都有则相加。