电子网站:datawhalechina.github.io/leetcode-no…
07.04
————————————
🚩 ——day13
√ 121. 买卖股票的最佳时机
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 第 i 天 卖出, 能获得的最高 利润
# pre 记录之前的最低 价格 贪心
ret = 0 # 最大利润
minPrice = prices[0]
for i in range(1, len(prices)):
ret = max(prices[i] - minPrice, ret)
minPrice = min(minPrice, prices[i])
return ret
class Solution {
public:
int maxProfit(vector<int>& prices) {
int minPrice = prices[0];
int ret = 0;
for (int i = 1; i < prices.size(); ++i){
ret = max(prices[i] - minPrice, ret);
minPrice = min(minPrice, prices[i]);
}
return ret;
}
};
√ 122. 买卖股票的最佳时机 II
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 贪心, 上帝视角。 有钱赚直接赚
ret = 0 # 总的利润
for i in range(1, len(prices)):
tmp = prices[i] - prices[i - 1] # 可以先卖 再买
if tmp > 0:
ret += tmp
return ret
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ret = 0; // 返回 利润和
for (int i = 1; i < prices.size(); ++i){
int tmp = prices[i] - prices[i - 1];
if (tmp > 0) ret += tmp;
}
return ret;
}
};
√ 300. 最长递增子序列 【贪心 + 二分查找】
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
# dp[i] : 以 nums[i] 结尾的最长 子序列长度
## 动态规划: O(N^2) O(N)
n = len(nums)
dp = [1] * n
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1) # 遍历 看看 接在 哪个 nums[j] 后面更长
return max(dp)
Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
## 贪心 + 二分查找: O(N log N ) O(N)
# tails[i]: 长度 为 i + 1 的子序列尾部元素的值
# 区间存在 tails[i] > nums[k] 第一个满足 的令 tails[i] = nums[k] 同一长度 使 尾部尽量小
# 区间 不存在 tials[i] > nums[k], 都可以 接, 接在 最长的后面
#
n = len(nums)
d = [] # 存储 严格 递增 子序列
for num in nums:
if not d or num > d[-1]:
d.append(num)
# 在 前面 找 替换的数
left = 0
right = len(d) - 1
loc = right
while left <= right:
mid = (left + right) // 2
if d[mid] >= num: # 注意这里
loc = mid # !!
right = mid - 1
else:
left = mid + 1
d[loc] = num
return len(d)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
// 贪心 + 二分查找 O(n log n) O(n)
vector<int> d; // 维护 一个 严格递增的序列
int n = nums.size();
for (int &num : nums){
if (d.empty() || d.back() < num){
d.emplace_back(num);
}
int left = 0, right = d.size() - 1;
int loc = right;
while (left <= right){
int mid = (left + right) / 2;
if (d[mid] >= num){
loc = mid;
right = mid - 1;
}else{
left = mid + 1;
}
}
d[loc] = num;
}
return d.size();
}
};
🚩——day14 【动态规划】
√ 1143. 最长公共子序列
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
# 动态规划 O(mn)
# dp[i][j] s1 前 i 个字符 s2 前 j 个字符 最长公共子序列的长度
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i - 1] == text2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
return dp[m][n]
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.size(), n = text2.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; ++i){
for (int j = 1; j <= n; ++j){
if (text1[i - 1] == text2[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}else{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
};
√ 64. 最小路径和
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
# dp[i][j]: 走到 [i, j] 格 获得的最小和
m, n = len(grid), len(grid[0])
dp = [[0] * n for _ in range(m)]
dp[0][0] = grid[0][0]
for i in range(1, m):
dp[i][0] = grid[i][0] + dp[i - 1][0]
for j in range(1, n):
dp[0][j] = grid[0][j] + dp[0][j - 1] # 注意 是之前的累加和
for i in range(1, m):
for j in range(1, n):
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]
return dp[m - 1][n - 1]
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
// dp[i][j] 第 [i, j] 能获得的最小和
int m = grid.size(), n = grid[0].size();
vector<vector<int>> dp(m, vector<int>(n));
dp[0][0] = grid[0][0];
for (int i = 1; i < m; ++i){
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < n; ++j){
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < m; ++i){
for (int j = 1; j < n; ++j){
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[m - 1][n - 1];
}
};
√ 72. 编辑距离
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
# dp[i][j] : s1 的 前 i 个 字符 换成 s2 的 前 j 个 字符 的 编辑距离
# 插入s1 dp[i][j - 1] + 1
# 删除s1 dp[i - 1][j] + 1
# 替换s1 dp[i - 1][j - 1] + 1
m, n = len(word1), len(word2)
if m * n == 0: # 其中 一个为空串
return n + m
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1): # s2 为空 s1 只能删
dp[i][0] = i
for j in range(n + 1):
dp[0][j] = j # s1 为空, s1 只能 增
for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
return dp[m][n]
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.size(), n = word2.size();
if (n * m == 0) return n + m; // 存在 空串
vector<vector<int>> dp(m + 1, vector<int>(n + 1)); // 注意 考虑 空串情况, 长度 + 1
for (int i = 0; i <= m; ++i){
dp[i][0] = i; // s1 只能删
}
for (int j = 0; j <= n; ++j){
dp[0][j] = j; // s1 只能 增加
}
for (int i = 1; i <= m; ++i){
for (int j = 1; j <= n; ++j){
if (word1[i - 1] == word2[j - 1]){ // 前 i 个字符中 最后一个字符相同, 无需操作
dp[i][j] = dp[i - 1][j - 1];
}else{
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
}
}
}
return dp[m][n];
}
};
🚩——day15
√ 62. 不同路径
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
# 组合 数学
# 总共 移动 m + n - 2 次, m - 1 向下, n - 1 向右
return math.comb(m + n - 2, m - 1)
C++ 组合的计算
class Solution {
public:
int uniquePaths(int m, int n) {
long long ret = 1;
for (int x = n, y = 1; y < m; ++x, ++y){
ret = ret * x / y; // m + n - 2 m - 1
}
return ret;
}
};
动态规划
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
# 动态规划 只能往右和 往下 ——> 只能 来自 左边 和 上边
dp = [[0] * n for _ in range(m)] # m 行 n 列 达到 坐标 [i, j]路径数
for i in range(m):
dp[i][0] = 1 # 只能 从上边来
for j in range(n):
dp[0][j] = 1 # 只能 从 左边来
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[m-1][n-1]
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m, vector<int>(n));
for (int i = 0; i < m; ++i){
dp[i][0] = 1; // 第 0 列
}
for (int j = 0; j < n; ++j){
dp[0][j] = 1; // 第 0 行
}
for (int i = 1; i < m; ++i){
for (int j = 1; j < n; ++j){
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n -1];
}
};
√ 152. 乘积最大子数组 【动态规划】
class Solution:
def maxProduct(self, nums: List[int]) -> int:
mx = nums[0]
mi = nums[0]
ret = nums[0] ## 两个负数 相乘 或 两个正数 相乘 都可能获得 最大值
for i in range(1, len(nums)):
# 因为 计算 mi 时还要用 mx, mi , 所以 要暂存 mi mx
temp_mx = mx
temp_mi = mi
mx = max(temp_mx * nums[i], nums[i], nums[i] * temp_mi)
mi = min(temp_mi * nums[i], nums[i], nums[i] * temp_mx)
ret = max(ret, mx)
return ret
class Solution {
public:
int maxProduct(vector<int>& nums) {
int ret = nums[0];
int maxP = nums[0], minP = nums[0];
for (int i = 1; i < nums.size(); ++i){
int mx = maxP, mi = minP;
maxP = max(nums[i], max(mx * nums[i], mi * nums[i]));
minP = min(nums[i], min(mx * nums[i], mi * nums[i])); // 负的也 尽量积累
ret = max(ret, maxP);
}
return ret;
}
};
√ 198. 打家劫舍
class Solution:
def rob(self, nums: List[int]) -> int:
# 动态规划 O(n) O(n)
# 不能 偷相邻的
# dp[i] 到 第 i 间房 能获得的 最大金额
n = len(nums)
if n == 1:
return nums[0]
# 后续 至少 2 个元素
dp = [0] * n
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, len(nums)):
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]) # 不偷当前, 偷当前的
return dp[-1]
class Solution:
def rob(self, nums: List[int]) -> int:
# 动态规划 O(n) O(1) 优化空间, 观察到
# 不能 偷相邻的
# dp[i] 到 第 i 间房 能获得的 最大金额 只和 前两间的情况有关
n = len(nums)
if n == 1:
return nums[0]
# 后续 至少 2 个元素
# 第 i 个 房间前 两个房间 能 获得的最大金额
total1 = nums[0]
total2 = max(nums[0], nums[1])
for i in range(2, n):
total1, total2 = total2, max(total2, total1 + nums[i]) # 不偷当前, 偷当前的
return total2
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if (n == 1) return nums[0];
int total1 = nums[0], total2 = max(nums[0], nums[1]);
for (int i = 2; i < n; ++i){
int temp = total2;
total2 = max(total1 + nums[i], total2); // 偷当前的, 不偷
total1 = temp;
}
return total2;
}
};
🚩——day16
√ 138. 随机链表的复制
"""
# 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]':
# 迭代 O(n) O(1)
# Step1: 复制 构建 构建 node1 -> node1_new -> node2 -> node2_new -> node3 -> node3_new ...
# cur.next.random = cur.random.next
# Step3 拆分
if not head: return
# 构建 拼接链表
cur = head
while cur:
tmp = Node(cur.val)
tmp.next = cur.next # 插入 链表中
cur.next = tmp
cur = tmp.next
# 处理 random 指向
cur = head
while cur:
if cur.random:
cur.next.random = cur.random.next
cur = cur.next.next
# 拆分 链表
cur = newHead = head.next
pre = head
while cur.next:
pre.next = pre.next.next
cur.next = cur.next.next
pre = pre.next
cur = cur.next
pre.next = None
return newHead
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
// 复制拼接 + 处理 random + 拆分
if (!head) return head;
// 复制 拼接 链表 node1 -> node1_new -> node2 -> node2_new -> ...
for (Node* node = head; node != nullptr; node = node->next->next){
Node* newNode = new Node(node->val);
newNode->next = node->next;
node->next = newNode;
}
// 处理 random 指向
for (Node* node = head; node != nullptr; node = node->next->next){
if (node->random){
node->next->random = node->random->next;
}
}
Node* newHead = head->next, *pre = head;
Node* cur = head->next;
while (cur->next){// 新链表 还有待处理结点 由于是对 复制链表进行操作,原链表 还有结点 待处理,则 复制的新链表同样 有结点 待处理
pre->next = pre->next->next;
cur->next = cur->next->next;
pre = pre->next;
cur = cur->next;
}
pre->next = nullptr;
return newHead;
}
};
√ 297. 二叉树的序列化与反序列化
层序遍历
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
if not root: return "[]"
q = collections.deque([root])
ret = []
while q:
node = q.popleft()
if node:
ret.append(str(node.val))
q.append(node.left)
q.append(node.right)
else:
ret.append("None")
return '[' + ','.join(ret) + ']'
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
if data == "[]": return
vals = data[1:-1].split(',')
root = TreeNode(int(vals[0]))
q = collections.deque([root])
i = 1
while q:
node = q.popleft()
if vals[i] != "None":
node.left = TreeNode(int(vals[i]))
q.append(node.left)
i += 1
if vals[i] != "None":
node.right = TreeNode(int(vals[i]))
q.append(node.right)
i += 1
return root
# Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if (!root) return "";
queue<TreeNode*> q;
q.emplace(root);
string ret;
while (!q.empty()){
TreeNode* node = q.front(); q.pop();
if (node){
ret += to_string(node->val) + ",";
q.emplace(node->left);
q.emplace(node->right);
}else{
ret += "NULL,";
}
}
ret.pop_back(); // 删掉 最后 的,
return ret; // 只要 相对顺序
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if (data == "") return nullptr;
// 获取 结点值 列表
list<string> dataList;
string tmp; // C++ 无法 直接添加 一个字符串
for (auto & ch : data){
if (ch == ','){
dataList.emplace_back(tmp);
tmp.clear();
}else{
tmp.push_back(ch);
}
}
if (!tmp.empty()){// 最后一个
dataList.emplace_back(tmp);
tmp.clear();
}
// BFS 恢复
TreeNode* root = new TreeNode(stoi(dataList.front()));
dataList.erase(dataList.begin());
queue<TreeNode*> q;
q.emplace(root);
while (!q.empty()){
TreeNode* node = q.front(); q.pop();
if (dataList.front() != "NULL"){
node->left = new TreeNode(stoi(dataList.front()));
q.emplace(node->left);
}
dataList.erase(dataList.begin()); // 左结点处理完成 删掉
if (dataList.front() != "NULL"){
node->right = new TreeNode(stoi(dataList.front()));
q.emplace(node->right);
}
dataList.erase(dataList.begin()); // 右结点处理完成 删掉
}
return root;
}
};
// Your Codec object will be instantiated and called as such:
// Codec ser, deser;
// TreeNode* ans = deser.deserialize(ser.serialize(root));
递归
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
if not root:
return 'None'
return str(root.val) +',' + str(self.serialize(root.left)) + ',' + str(self.serialize(root.right))
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
def dfs(dataList):
val = dataList.pop(0)
if val == 'None':
return None
root = TreeNode(int(val))
root.left = dfs(dataList) # 每 进一次循环 就会 弹出 一个值
root.right = dfs(dataList)
return root
dataList = data.split(',')
return dfs(dataList)
# Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Codec {
public:
// 序列化 /////////////////////////////
// 子模块 返回 结果
void rserialize(TreeNode* root, string& ret){
if (!root){
ret += "NULL,";
}else{
ret += to_string(root->val) + ",";
rserialize(root->left, ret);
rserialize(root->right, ret);
}
}
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
string ret;
rserialize(root, ret);
return ret;
}
// 反序列化 /////////////////////////////
// Decodes your encoded data to tree.
// 子模块 递归 重构 树
TreeNode* rdeserialize(list<string>& dataList){
if (dataList.front() == "NULL"){
dataList.erase(dataList.begin());
return nullptr;
}
TreeNode* root = new TreeNode(stoi(dataList.front())); // 建 根结点
dataList.erase(dataList.begin());
root->left = rdeserialize(dataList);
root->right = rdeserialize(dataList);
return root;
}
TreeNode* deserialize(string data) {// 处理
list<string> dataList;
string tmp; // C++ 无法 直接添加 一个字符串
for (auto & ch : data){
if (ch == ','){
dataList.emplace_back(tmp);
tmp.clear();
}else{
tmp.push_back(ch);
}
}
if (!tmp.empty()){// 最后一个
dataList.emplace_back(tmp);
tmp.clear();
}
return rdeserialize(dataList);
}
};
// Your Codec object will be instantiated and called as such:
// Codec ser, deser;
// TreeNode* ans = deser.deserialize(ser.serialize(root));
√ 209. 长度最小的子数组
滑动窗口
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
n = len(nums)
ret = n + 1 # 最大可能为 n
left, right = 0, 0
total = 0 # 计算 当前 窗口的和
while right < n:
total += nums[right]
while total >= target: # 保证 和 满足条件的前提下 修改 窗口
ret = min(ret, right - left + 1)
total -= nums[left]
left += 1 # 左边 右移
right += 1
return 0 if ret == n + 1 else ret # 没改过 为 0
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
// 滑动窗口 O(n) O(1)
int n = nums.size();
int ret = n + 1; // 最长 不会超过 n
int left = 0, right = 0;
int total = 0; // 记录 当前窗口的和
while (right < n){
total += nums[right];
while (total >= target){
ret = min(ret, right - left + 1);
total -= nums[left];
left += 1;
}
right += 1;
}
return ret == n + 1 ? 0 : ret;
}
};
√ 139. 单词拆分
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
# 动态规划 O(n^2) O(n)
# dp[i]: 前 i 个 字符 能否被 空格 分割成 若干个 字典中 存在的单词
wordDict = set(wordDict)
n = len(s)
dp = [False] * (n + 1)
dp[0] = True
for i in range(1, n + 1): # dp[i] s[0... i-1]
for j in range(i):
if dp[j] and s[j : i] in wordDict: # check(s[j...i-1])
dp[i] = True #
break
return dp[n]
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
// 动态规划 O(n^2) O(n)
// 哈希化
unordered_set<string> wordDictSet;
for (string &word : wordDict){
wordDictSet.insert(word);
}
int n = s.size();
vector<bool> dp(n, false);
dp[0] = true;
for (int i = 1; i <= n; ++i){
for (int j = 0; j < i; ++j){
if (dp[j] && wordDictSet.find(s.substr(j, i - j)) != wordDictSet.end()){
dp[i] = true;
break;
}
}
}
return dp[n];
}
};