LeetCode Hot 100 | 动态规划(下)(C++ 题解)

LeetCode Hot 100 | 动态规划(下)(C++ 题解)


前言

动态规划下篇涵盖最长有效括号、编辑距离、不同路径、最小路径和、最长回文子串和最长公共子序列六道经典题目,重点掌握二维 DP 的状态转移设计。


1. 32. 最长有效括号 🔴

题目描述

给你一个只包含 '('')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例 1:

输入:s = "(()"
输出:2

示例 2:

输入:s = ")()())"
输出:4

约束: 0 <= s.length <= 3 * 10^4

解题思路

len[i] 表示以 s[i] 结尾的最长有效括号长度(仅 s[i]==')' 时非零):

  • s[i-1]=='('len[i] = len[i-2] + 2
  • s[i-1]==')'m = i - len[i-1] - 1 处为 (len[i] = len[i-1] + 2 + len[m-1]

s = "(()())" 为例:

is[i]s[i-1]mlen[i]
1‘)’‘(’-0+2=2
2‘(’‘)’-0
3‘)’‘(’-len[1]+2=4
4‘(’‘)’-0
5‘)’‘(’-len[3]+2=6

ans=6 ✓

复杂度分析

  • 时间: O(n)
  • 空间: O(n)

C++ 代码

class Solution {
public:
    int longestValidParentheses(string s) {
        int n = s.size();
        vector<int> len(n, 0);
        int ans = 0;
        
        for (int i = 1; i < n; i++) {
            if (s[i] == ')') {
                if (s[i - 1] == '(') {
                    len[i] = i >= 2 ? len[i - 2] + 2 : 2;
                } else {
                    int m = i - len[i - 1] - 1;
                    if (m >= 0 && s[m] == '(')
                        len[i] = m - 1 >= 0 ? len[i - 1] + 2 + len[m - 1] : len[i - 1] + 2;
                }
            }
            ans = max(ans, len[i]);
        }
        
        return ans;
    }
};

2. 72. 编辑距离 🟡

题目描述

给你两个单词 word1word2,请返回将 word1 转换成 word2 所使用的最少操作数。你可以对一个单词进行如下三种操作:插入一个字符、删除一个字符、替换一个字符。

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3(horse→rorse→rose→ros)

示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5

约束: 0 <= word1.length, word2.length <= 500

72. 图解

解题思路

f[i][j] 表示 word1[0..i-1] 转换为 word2[0..j-1] 的最少操作数。

  • 初始化:f[0][j]=j(全插入),f[i][0]=i(全删除)
  • word1[i-1]==word2[j-1]f[i][j] = f[i-1][j-1]
  • 否则:f[i][j] = min(f[i-1][j], f[i][j-1], f[i-1][j-1]) + 1

word1="ab", word2="bc" 为例:

“”bc
“”012
a112
b212

返回 f[2][2]=2 ✓

复杂度分析

  • 时间: O(m × n)
  • 空间: O(m × n)

C++ 代码

class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size();
        int n = word2.size();
        vector<vector<int>> f(m + 1, vector<int>(n + 1, 0));
        
        for(int i = 1; i <= n; i++) {
            f[0][i] = f[0][i - 1] + 1;
        }
        
        for (int i = 1; i <=m; i++) {
            f[i][0] = f[i - 1][0] + 1;
            for (int j = 1; j <= n; j++) {
                if (word1[i - 1] == word2[j - 1]) {
                    f[i][j] = f[i - 1][j - 1];
                } else {
                    f[i][j] = min(min(f[i - 1][j], f[i][j - 1]), f[i - 1][j - 1]) + 1;
                }
            }
        }
        
        return f[m][n];
    }
};

3. 62. 不同路径 🟡

题目描述

一个机器人位于一个 m x n 网格的左上角。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。问总共有多少条不同的路径?

示例 1:

输入:m = 3, n = 7
输出:28

示例 2:

输入:m = 3, n = 2
输出:3

约束: 1 <= m, n <= 100

62. 图解

解题思路

dp[i][j] 表示到达 (i,j) 的路径数,等于 dp[i-1][j] + dp[i][j-1]。边界全为 1。

m=3, n=3 为例(实际为 (m+1)×(n+1) 的辅助数组,起点偏移):

123
1111
2123
3136

返回 dp[3][3]=6 ✓

复杂度分析

  • 时间: O(m × n)
  • 空间: O(m × n)

C++ 代码

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 1));
        
        for (int i = 2; i <= m; i++) {
            for (int j = 2; j <= n; j++) {
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        
        return dp[m][n];
    }
};

4. 64. 最小路径和 🟡

题目描述

给定一个包含非负整数的 m x n 网格 grid,请找出一条从左上角到右下角的路径,使得路径上的数字总和最小。每次只能向下或向右移动一步。

示例 1:

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7(1→3→1→1→1)

示例 2:

输入:grid = [[1,2,3],[4,5,6]]
输出:12

约束: m == grid.length, n == grid[i].length, 1 <= m, n <= 200

64. 图解

解题思路

原地 DP:先处理第一行(累加),再处理第一列(累加),然后对其余位置取 min(上, 左) + 当前值

grid = [[1,3,1],[1,5,1],[4,2,1]] 为例:

012
0145
1276
2687

返回 grid[2][2]=7 ✓

复杂度分析

  • 时间: O(m × n)
  • 空间: O(1)(原地)

C++ 代码

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        
        for (int i = 1; i < n; i++) {
            grid[0][i] += grid[0][i - 1];
        }
        
        for (int i = 1; i < m; i++) {
            grid[i][0] += grid[i - 1][0];
            for (int j = 1; j < n; j++) {
                grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];
            }
        }
        
        return grid[m - 1][n - 1];
    }
};

5. 5. 最长回文子串 🟡

题目描述

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad"
输出:"bab"(或"aba")

示例 2:

输入:s = "cbbd"
输出:"bb"

约束: 1 <= s.length <= 1000, s 仅由数字和英文字母组成

5. 图解

解题思路

中心扩展法:对每个位置,分别以奇数长度(单字符中心)和偶数长度(双字符中心)向外扩展,记录最长回文的起始位置和长度。

s = "babad" 从 i=1 (a) 奇数扩展为例:

leftrights[left]s[right]操作
11‘a’‘a’相等,len=1, start=1
02‘b’‘b’相等,len=3, start=0
-13越界-停止

找到 “bab”,len=3 ✓

复杂度分析

  • 时间: O(n²)
  • 空间: O(1)

C++ 代码

class Solution {
public:
    string longestPalindrome(string s) {
        int left = 0;
        int right = 0;
        int start = 0;
        int len = 0;
        
        for (int i = 0; i < s.size(); i++) {
            left = i;
            right = i;
            while(left >=0 && right < s.size() && s[left] == s[right]) {
                if (right - left + 1 > len) {
                    len = right - left + 1;
                    start = left;
                }
                left--;
                right++;
            }

            left = i;
            right = i + 1;
            while(left >= 0 && right < s.size() && s[left] == s[right]) {
                if (right - left + 1 > len){
                    len = right - left + 1;
                    start = left;
                }
                left--;
                right++;
            }
            
        }
        return s.substr(start, len);
    }
};

6. 1143. 最长公共子序列 🟡

题目描述

给定两个字符串 text1text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列,返回 0

示例 1:

输入:text1 = "abcde", text2 = "ace"
输出:3("ace")

示例 2:

输入:text1 = "abc", text2 = "abc"
输出:3

约束: 1 <= text1.length, text2.length <= 1000

1143. 图解

解题思路

f[i+1][j+1] 表示 text1[0..i]text2[0..j] 的LCS长度:

  • text1[i]==text2[j]f[i+1][j+1] = f[i][j] + 1
  • 否则:f[i+1][j+1] = max(f[i][j+1], f[i+1][j])

text1="ace", text2="ace" 为例:

“”ace
“”0000
a0111
c0122
e0123

返回 f[3][3]=3 ✓

复杂度分析

  • 时间: O(m × n)
  • 空间: O(m × n)

C++ 代码

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int m = text1.size();
        int n = text2.size();
        vector<vector<int>> f(text1.size() + 1, vector<int>(text2.size() + 1, 0));   
        for (int i = 0; i < text1.size(); i++) {
            for (int j = 0; j < text2.size(); j++) {
                if (text1[i] == text2[j])
                    f[i + 1][j + 1] = f[i][j] + 1;
                else
                    f[i + 1][j + 1] = max(f[i][j + 1], f[i + 1][j]);
            }
        }
        
        return f[m][n];
    }
};

总结

题号题目难度核心技巧时间复杂度
32最长有效括号🔴困难DP,区分前一字符是否为’(’O(n)
72编辑距离🟡中等二维DP,三种操作对应三个方向O(m×n)
62不同路径🟡中等二维DP,路径数=上+左O(m×n)
64最小路径和🟡中等原地DP,取min(上,左)+当前O(m×n)
5最长回文子串🟡中等中心扩展,奇偶两种情况O(n²)
1143最长公共子序列🟡中等二维DP,字符相等则+1否则取maxO(m×n)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值