# 0326【LeetCode 算法笔记】Task04 88 - 100

77 阅读11分钟

开源内容:github.com/datawhalech…

电子网站: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 

image.png

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

image.png

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)

image.png

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. 最长公共子序列

image.png

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. 编辑距离

参考链接

image.png

image.png

image.png

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]

image.png

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)

image.png

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. 乘积最大子数组 【动态规划】

image.png

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

image.png

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]

image.png

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];
    }
};