电子网站:datawhalechina.github.io/leetcode-no…
07.03
——————————
🚩——day9
√ 958. 二叉树的完全性检验
# 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 = [root]
flag = False # 标记 之前是否 出现过 空结点
while q:
nxt = []
for node in q:
if not node:
flag = True
else: # 现在 不空
# 之前 出现过 空节点
if flag:
return False
nxt.append(node.left) # 直接 将 子节点 加到 下一层
nxt.append(node.right)
q = nxt
return True
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool isCompleteTree(TreeNode* root) {
vector<TreeNode*> q;
q.emplace_back(root);
bool flag = false; // 是否 出现了 空节点
while (!q.empty()){
vector<TreeNode*> nxt;
for (TreeNode* node : q){// vector 才可以 这样遍历
if (!node){
flag = true; // 出现 空结点了
}else{
if (flag) return false;
nxt.emplace_back(node->left);
nxt.emplace_back(node->right);
}
}
q = move(nxt);
}
return true;
}
};
√ 543. 二叉树的直径
# 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.ret = 0
def dfs(node):
if not node:
return 0
L = dfs(node.left)
R = dfs(node.right)
self.ret = max(self.ret, L + R) # 只 比 深度 计算 多了 这行 代码
return max(L, R) + 1
dfs(root)
return self.ret
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
int ret;
int dfs(TreeNode* node){
if (!node) return 0;
int L = dfs(node->left);
int R = dfs(node->right);
ret = max(ret, L + R); // 只比 求深度模块 多了这句判断
return max(L, R) + 1;
}
public:
int diameterOfBinaryTree(TreeNode* root) { // 观察发现, 路径长度 恰好 等于 左边的深度 + 右边的深度
ret = 0;
dfs(root);
return ret;
}
};
√ 662. 二叉树最大宽度
# 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 widthOfBinaryTree(self, root: Optional[TreeNode]) -> int:
# 最左 的 非空结点 null null... 最右的非空结点 宽度
# 记录每层 最左的节点编号 最右节点的编号 求 差
ret = 1 # 最大宽度
q = [[root, 1]] # 节点, 编号
while q:
ret = max(ret, q[-1][1] - q[0][1] + 1) # 注意 , 是根据 q 来算。 q 可保证 非空
nxt = []
for node, i in q:
if node.left:
nxt.append([node.left, i * 2])
if node.right:
nxt.append([node.right, i * 2 + 1])
q = nxt
return ret
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int widthOfBinaryTree(TreeNode* root) {
vector<pair<TreeNode*, unsigned long long>> q;
unsigned long long ret = 1; // 最大 宽度
q.emplace_back(root, 1L); // 结点, 编号
while (!q.empty()){
ret = max(ret, q.back().second - q[0].second + 1); // 可保证 q 必定 非空
vector<pair<TreeNode*, unsigned long long>> nxt;
for (auto &[node, i] : q){
if (node->left){
nxt.emplace_back(node->left, i * 2);
}
if (node->right){
nxt.emplace_back(node->right, i * 2 + 1);
}
}
q = move(nxt);
}
return ret;
}
};
🚩——day10
√ 322. 零钱兑换
float('inf') 和 math.inf
动态规划
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
# 自下而上
# dp[i]: 组成金额 i 所需最少的硬币数量
# dp[i] = min(dp[i - cj]) + 1 # 遍历 硬币面值
# dp = [inf] * (amount + 1) # 直接用 inf 的前提是 import math。LeetCode 一般都导入,所以不会错,实际应用忘记导入会报错
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for x in range(coin, amount + 1):
dp[x] = min(dp[x], dp[x - coin] + 1)
return dp[amount] if dp[amount] != inf else -1
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
// dp[i]: 凑成 金额 i 所需的最少硬币数 为 dp[i]
// dp[i] = min(dp[i - cj]) + 1 遍历 各面值
int Max = amount + 1;
vector<int> dp(amount + 1, Max);
dp[0] = 0;
for (int coin : coins){// 遍历 硬币面值
for (int x = coin; x <= amount; ++x){// 从面值 coin 开始 凑
dp[x] = min(dp[x], dp[x - coin] + 1);
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
};
BFS
根据题意, 凑钱的过程 可表示为一棵树, 求最少的硬币数,即最短路径, 可考虑 用 BFS
一定 要记得标记 访问过的点 !!!
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
if amount == 0:
return 0
q = collections.deque([[amount, 0]]) # 待凑的金额, 当前的硬币数
visited = set()
visited.add(amount)
while q:
cur, cnt = q.popleft()
for coin in coins:
if cur == coin:
return cnt + 1
if cur - coin > 0 and cur - coin not in visited:
q.append([cur - coin, cnt + 1])
visited.add(cur - coin)
return -1
√ 78. 子集
回溯
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
ret = []
path = []
n = len(nums)
def dfs(i): # nums[i] 选不选
if i == n:
ret.append(path.copy())
return
path.append(nums[i]) # 选
dfs(i + 1)
path.pop() # 不选
dfs(i + 1)
dfs(0)
return ret
class Solution {
public:
vector<int> path;
vector<vector<int>> ret;
void dfs(int cur, vector<int>& nums){
if (cur == nums.size()){
ret.emplace_back(path);
return;
}
path.emplace_back(nums[cur]); // 选
dfs(cur + 1, nums);
path.pop_back(); // 不选
dfs(cur + 1, nums);
}
vector<vector<int>> subsets(vector<int>& nums) {
dfs(0, nums);
return ret;
}
};
二进制 mask 表示选或不选
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
# 用 长度 为 len(nunms) 的 二进制 表示 选不选 nums[i]
n = len(nums)
ret = []
for mask in range(1 << n):# 0 - 2^n-1 如 n = 2, 依次为 00 01 10 11 依次构造 这些模版的子集
path = []
for i in range(n):
if mask & (1 << i): # mask 的 第 i 位 为 1 # 写法: (mask >> i) & 1
path.append(nums[i])
ret.append(path)
return ret
class Solution {
public:
vector<int> path;
vector<vector<int>> ret;
vector<vector<int>> subsets(vector<int>& nums) {
// O(n * 2^n) O(n)
int n = nums.size();
for (int mask = 0; mask < (1 << n); ++mask){
path.clear();
for (int i = 0; i < n; ++i){
if (mask & (1 << i)){// 1 左移 i 选 nums[i]
path.emplace_back(nums[i]);
}
}
ret.emplace_back(path);
}
return ret;
}
};
√ 221. 最大正方形
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
# 动态规划
# dp[i][j]: 以 (i, j) 为 右下角, 且只包含 1 的 最大正方形的边长
# matrix[i][j] = 0 dp[i][j] = 0
# dp[i][j] = min(dp[i-1][j], dp[i-1][j-1], dp[i][j-1]) + 1
maxSide = 0 # 记录 最大正方形的 边长
m, n = len(matrix), len(matrix[0])
dp = [[0] * n for _ in range(m)]
for i in range(m):
for j in range(n):
if matrix[i][j] == '1':
if i == 0 or j == 0:
dp[i][j] = 1
else:
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
maxSide = max(maxSide, dp[i][j])
return maxSide**2
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
// 动态规划 O(mn)、O(mn)
int m = matrix.size(), n = matrix[0].size();
int maxSide = 0;
vector<vector<int>> dp(m, vector<int>(n, 0));
for (int i = 0; i < m; ++i){
for (int j = 0; j < n; ++j){
if (matrix[i][j] == '1'){
if (i == 0 || j == 0){
dp[i][j] = 1;
}else{
dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
}
maxSide = max(maxSide, dp[i][j]);
}
}
return maxSide * maxSide;
}
};
🚩——day11
√ 24. 两两交换链表中的节点 【迭代】
# 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]:
# 迭代 O(n) O(1)
# 0 1 2 3 4 cur node1 node2 node2.next
# 0 2 1 3 4
dummy = ListNode(0)
dummy.next = head
cur = dummy
while cur.next and cur.next.next:
node1 = cur.next
node2 = cur.next.next
cur.next = node2
node1.next = node2.next # 先走 这一步
node2.next = node1
cur = node1
return dummy.next
# 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]:
# 迭代 O(n) O(1)
# 0 1 2 3 4 node0 node1 node2 node3
# 0 2 1 3 4
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
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
// 0 1 2 3 4
// 2 1
ListNode* dummy = new ListNode(0, head);
ListNode *node0 = dummy, *node1 = head;
ListNode *node2, *node3;
while (node1 && node1->next){
node2 = node1->next;
node3 = node2->next;
node0->next = node2;
node2->next = node1;
node1->next = node3;
node0 = node1;// 注意顺序
node1 = node3;
}
return dummy->next;
}
};
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
// 0 1 2 3 4
// 2 1
ListNode* dummy = new ListNode(0, head);
ListNode *cur = dummy;
ListNode *node1 = head, *node2;
while (cur->next && cur->next->next){
node1 = cur->next;
node2 = cur->next->next;
cur->next = node2;
node1->next = node2->next;
node2->next = node1;
cur = node1;
}
return dummy->next;
}
};
√ 70. 爬楼梯
class Solution:
def climbStairs(self, n: int) -> int:
# O(n) O(n)
dp = [0] * (n + 1) # 到达 第 i 个 阶梯的方法数
dp[0] = dp[1] = 1 # 注意 dp[0] 是 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2] # 注意 计算 dp[i] 的时候 只用了 前两个数, 为进一步优化提供了 可能
return dp[n]
class Solution:
def climbStairs(self, n: int) -> int:
f0 = 1
f1 = 1
for i in range(2, n + 1):
f0, f1 = f1, f0 + f1
return f1
class Solution {
public:
int climbStairs(int n) {
int f0 = 1, f1 = 1;
for (int i = 2; i <= n; ++i){
int fi = f0 + f1;
f0 = f1;
f1 = fi;
}
return f1;
}
};
√ 53. 最大子数组和
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
pre = nums[0]
ret = nums[0]
for i in range(1, n):
pre = max(pre + nums[i], nums[i]) # 是否 取 nums[i] 前面的累加和
ret = max(ret, pre)
return ret
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int pre = nums[0], ret = nums[0];
for (int i = 1; i < nums.size(); ++i){
pre = max(pre + nums[i], nums[i]);
ret = max(ret, pre);
}
return ret;
}
};
分治
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
def myDivide(left, right):
if left == right:
return nums[left]
mid = (left + right)//2
leftMax = myDivide(left, mid)
rightMax = myDivide(mid + 1, right)
# 计算 包含 nums[mid] 的两边 跨序列的 crossMax
leftCrossMax = nums[mid]
leftSum = 0 # 从 mid 到 left 的累加和
for i in range(mid, left - 1, -1):
leftSum += nums[i]
leftCrossMax = max(leftCrossMax, leftSum)
rightCrossMax = nums[mid + 1]
rightSum = 0
for i in range(mid + 1, right + 1):
rightSum += nums[i]
rightCrossMax = max(rightCrossMax, rightSum)
crossMax = leftCrossMax + rightCrossMax
return max(leftMax, rightMax, crossMax)
return myDivide(0, len(nums) - 1)
class Solution {
public:
int maxSubArray(vector<int>& nums) {
return myDivide(nums, 0, nums.size() - 1);
}
private:
int myDivide(vector<int>& nums, int left, int right){
if (left == right){
return nums[left];
}
int mid = (left + right) / 2;
int leftMax = myDivide(nums, left, mid);
int rightMax = myDivide(nums, mid + 1, right);
// 横 跨 左右的 最大值
int leftCrossMax = nums[mid];
int leftCrossSum = 0;
for (int i = mid; i >= left; --i){
leftCrossSum += nums[i];
leftCrossMax = max(leftCrossSum, leftCrossMax); // 累加后 左边 能获得的最大值
}
// 右边 累加的最大值
int rightCrossMax = nums[mid + 1];
int rightCrossSum = 0;
for (int i = mid + 1; i <= right; ++i){
rightCrossSum += nums[i];
rightCrossMax = max(rightCrossSum, rightCrossMax);
}
int crossMax = leftCrossMax + rightCrossMax;
return max(leftMax, max(rightMax, crossMax));
}
};
🚩——day12
√ 46. 全排列 【回溯】
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
ret = []
path = [0] * n # 依次 填入 选的数
on_path = [False] * n # 是否 已选
def backtrack(i):
if i == n: # 都 填好了
ret.append(path.copy())
return
for j in range(n):
if not on_path[j]: # 第 j 个 数 没选过
path[i] = nums[j] # 选
on_path[j] = True
dfs(i + 1)
on_path[j] = False # 不选 回溯
backtrack(0)
return ret
class Solution {
void backtrack(vector<int>& nums, vector<vector<int>>& ret, vector<int>& path, vector<int>& on_path, int i){
int n = nums.size();
if (i == n){
ret.emplace_back(path);
return;
}
for (int j = 0; j < n; ++j){
if (!on_path[j]){
path[i] = nums[j];
on_path[j] = true;
backtrack(nums, ret, path, on_path, i + 1); // 每次 回溯 都需要知道 path 和 on_path
on_path[j] = false;
}
}
}
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> ret;
int n = nums.size();
vector<int> path(n), on_path(n);
backtrack(nums, ret, path, on_path, 0);
return ret;
}
};
√ 22. 括号生成
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
#
ret = []
m = 2 * n # 注意 n 是 括号对数
path = [''] * m #
def dfs(i, cnt_left): # 当前 处理的位置 左括号个数
if i == m:
ret.append(''.join(path))
return
if cnt_left < n:
path[i] = '('
dfs(i + 1, cnt_left + 1)
if i - cnt_left < cnt_left: # 右括号数 < 左括号数 可填 右括号
path[i] =')'
dfs(i + 1, cnt_left)
dfs(0, 0)
return ret
class Solution {
// C++ 字符串 是可改的, 无需像 python 那样 变成 list
void dfs(const int n, vector<string>& ret, int cnt_left, string& path){
if (path.size() == n * 2){
ret.emplace_back(path);
return;
}
if (cnt_left < n){
path += '(';
dfs(n, ret, cnt_left + 1, path);
path.pop_back();
}
if (path.size() - cnt_left < cnt_left){// 右括号数 < 左括号数
path += ')';
dfs(n, ret,cnt_left, path);
path.pop_back();
}
}
public:
vector<string> generateParenthesis(int n) {
vector<string> ret;
string path;
dfs(n, ret, 0, path); // 左括号 个数
return ret;
}
};
√ 39. 组合总和
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
ret = []
path = []
def backtrack(start, cur):
if cur == 0:
ret.append(path.copy())
return
for i in range(start, len(candidates)): # 避免 重复 子序列
if cur - candidates[i] < 0:
break
path.append(candidates[i]) # 选
backtrack(i, cur - candidates[i]) # 注意 这里 是 i
path.pop() # 回溯
candidates.sort()
backtrack(0, target) # 当前 位置, 当前 和
return ret
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> ret;
vector<int> path;
sort(candidates.begin(), candidates.end()); // 避免 重复 子序列
backtrack(candidates, ret, 0, path, target);
return ret;
}
private:
void backtrack(const vector<int>& candidates, vector<vector<int>>& ret, int start, vector<int>& path, int cur){
if (cur == 0){
ret.emplace_back(path);
return;
}
for (int i = start; i < candidates.size(); ++i){// 从 start 开始, 避免 重复
if (cur - candidates[i] < 0) break;
path.emplace_back(candidates[i]);
backtrack(candidates, ret, i, path, cur - candidates[i]);
path.pop_back(); // 回溯
}
}
};
√ 93. 复原 IP 地址 【递归】
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
if len(s) < 4 or len(s) > 12: return [] # 因为 题目中 1 <= s.length <= 20
ret = []
path = ""
def dfs(cur, i, path): # 当前已获取的段数, 当前 处理的 s 索引下标
if cur == 4 and i == len(s):
ret.append(path[:-1])
return
if cur > 4: # 后面 循环 切割的时候 已经限制 len(s) 之内, 因此判断 分段数 是否超过 4 即可
return
for j in range(i, min(i + 3, len(s))): # 某段的 结束索引 255 最多 3位数
if int(s[i: j + 1]) <= 255 and (s[i] != '0' or i == j):
dfs(cur + 1, j + 1, path + s[i : j + 1] + '.')
dfs(0, 0, "")
return ret
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
if len(s) < 4 or len(s) > 12: return [] # 因为 题目中 1 <= s.length <= 20
ret = []
path = [0] * 4 # 存储 4个 数字 0 - 255
def dfs(cur, i): # 处理到 path[cur], 当前 处理的 s 索引下标
if cur == 4:
if i == len(s):
ipAddr = '.'.join(str(seg) for seg in path)
ret.append(ipAddr)
return # 不管 是否 找到符合 要求的, 都返回, 超 4 段了
if i == len(s): # 后面 循环 切割的时候 已经限制 len(s) 之内, 因此判断 分段数 是否超过 4 即可
return
# 处理 前导 0
if s[i] == '0':
path[cur] = 0 # 单独成段
dfs(cur + 1, i + 1)
return
# 无 前导0
addr = 0
for j in range(i, len(s)): # 某段的 结束索引
addr = addr * 10 + (ord(s[j]) - ord('0'))
if 0 < addr <= 255:
path[cur] = addr
dfs(cur + 1, j + 1) # 注意 这里 是 j + 1
else:
break
dfs(0, 0)
return ret
class Solution {
private:
vector<string> ret;
vector<int> path;
public:
void dfs(const string& s, int cur, int i) {
// 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案
if (cur == 4) {
if (i == s.size()) {
string ipAddr;
for (int i = 0; i < 4; ++i) {
ipAddr += to_string(path[i]);
if (i != 3) {
ipAddr += ".";
}
}
ret.emplace_back(move(ipAddr));
}
return; // 这里 要是 cur > 4 就 跳出了
}
// 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯
if (i == s.size()) {
return;
}
// 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0
if (s[i] == '0') {
path[cur] = 0;
dfs(s, cur + 1, i + 1);
return;
}
// 一般情况,枚举每一种可能性并递归
int addr = 0;
for (int j = i; j < s.size(); ++j) {
addr = addr * 10 + (s[j] - '0');
if (addr > 0 && addr <= 0xFF) {
path[cur] = addr;
dfs(s, cur + 1, j + 1);
} else {
break;
}
}
}
vector<string> restoreIpAddresses(string s) {
path.resize(4);
dfs(s, 0, 0);
return ret;
}
};