文章目录
91.不同路径
题目描述
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 "Start" )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 "Finish" )。
问总共有多少条不同的路径?
示例 1:

输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
示例 3:
输入:m = 7, n = 3
输出:28
示例 4:
输入:m = 3, n = 3
输出:6
提示:
1 <= m, n <= 100
- 题目数据保证答案小于等于
2 * 10^9
思路
-
dp 数组含义 :
dp[i][j]
表示到达(i, j)
的不同路径数。 -
递推公式 :
dp[i][j] = dp[i-1][j] + dp[i][j-1]
,即从上方和左方转移。 -
初始化 :第一行和第一列的路径数均为
1
,因为机器人只能向右或向下走。 -
遍历顺序 :先初始化边界条件,再从
(1,1)
开始按行填充 dp 数组,最终返回dp[m-1][n-1]
。
代码
c
int uniquePaths(int m, int n) {
// 1. dp 数组含义:
// dp[i][j] 表示从起点 (0,0) 到达 (i,j) 的不同路径数
if (m == 1 || n == 1) return 1; // 如果 m 或 n 为 1,则只有一条路径可走
int dp[m][n]; // 定义 dp 数组,存储每个位置的路径数
// 2. dp 数组初始化:
// 由于机器人只能向右或向下移动,所以第一列和第一行的路径数都是 1
for (int i = 0; i < m; i++) dp[i][0] = 1; // 第一列所有位置的路径数均为 1
for (int i = 1; i < n; i++) dp[0][i] = 1; // 第一行所有位置的路径数均为 1
// 3. dp 递推公式:
// 机器人可以从上方 (i-1, j) 或左方 (i, j-1) 到达 (i, j)
// 因此,dp[i][j] = dp[i-1][j] + dp[i][j-1]
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]; // 递推计算
}
}
// 4. dp 遍历顺序:
// - 先填充第一列和第一行(初始化)
// - 再从 (1,1) 开始按行遍历整个 dp 数组,每个位置由上方和左方的值推导得出
return dp[m - 1][n - 1]; // 返回终点 (m-1, n-1) 的路径数
}
92.最小路径和
题目描述
给定一个包含非负整数的 *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
0 <= grid[i][j] <= 200
思路:dp
- 定义状态 :
dp[i][j]
表示从(0, 0)
到(i, j)
的最小路径和。
- 初始化 :
dp[0][0] = grid[0][0]
:起点的最小路径和就是网格的第一个元素。- 对于第一行和第一列,路径只能从左边或上边移动,因此:
dp[i][0] = dp[i-1][0] + grid[i][0]
(第一列)。dp[0][j] = dp[0][j-1] + grid[0][j]
(第一行)。
- 状态转移 :
- 对于其他位置
(i, j)
,最小路径和为:dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
。
- 对于其他位置
- 返回结果 :
- 返回
dp[m-1][n-1]
,即右下角的最小路径和。
- 返回
代码
c
#include <stdio.h>
#include <stdlib.h>
int minPathSum(int** grid, int gridSize, int* gridColSize) {
int m = gridSize;
int n = gridColSize[0];
if (m == 0 || n == 0) return 0; // 处理空网格
// 定义 DP 数组
int dp[m][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] = fmin(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[m - 1][n - 1];
}
93.最长回文子串
题目描述
- 定义状态 :
- 设
dp[i][j]
表示子串s[i..j]
是否是回文子串。
- 设
- 初始化 :
- 单个字符一定是回文子串,因此
dp[i][i] = true
。
- 单个字符一定是回文子串,因此
- 状态转移 :
- 对于长度大于 1 的子串
s[i..j]
:- 如果
s[i] == s[j]
,则dp[i][j]
的值取决于dp[i+1][j-1]
。 - 如果
s[i] != s[j]
,则dp[i][j] = false
。
- 如果
- 对于长度大于 1 的子串
- 记录最长回文子串 :
- 遍历
dp
数组,找到最长的回文子串,并将其复制到结果字符串ans
中。
- 遍历
- 返回结果 :
- 返回
ans
,即最长回文子串。
- 返回
代码
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
char* longestPalindrome(char* s) {
int n = strlen(s); // 获取字符串的长度
if (n == 0) return ""; // 如果字符串为空,直接返回空字符串
// 定义动态规划数组 dp[i][j],表示 s[i..j] 是否是回文子串
bool dp[n][n];
memset(dp, false, sizeof(dp)); // 初始化 dp 数组为 false
// 单个字符一定是回文子串
for (int i = 0; i < n; i++) {
dp[i][i] = true;
}
int maxLen = 1; // 记录最长回文子串的长度
int start = 0; // 记录最长回文子串的起始位置
// 检查长度为 2 的子串
for (int i = 0; i < n - 1; i++) {
if (s[i] == s[i + 1]) { // 如果两个相邻字符相等
dp[i][i + 1] = true; // 则 s[i..i+1] 是回文子串
maxLen = 2; // 更新最长回文子串的长度
start = i; // 更新最长回文子串的起始位置
}
}
// 检查长度大于 2 的子串
for (int len = 3; len <= n; len++) { // len 表示子串的长度
for (int i = 0; i <= n - len; i++) { // i 表示子串的起始位置
int j = i + len - 1; // j 表示子串的结束位置
if (s[i] == s[j] && dp[i + 1][j - 1]) { // 如果首尾字符相等且内部子串是回文
dp[i][j] = true; // 则 s[i..j] 是回文子串
if (len > maxLen) { // 如果当前回文子串更长
maxLen = len; // 更新最长回文子串的长度
start = i; // 更新最长回文子串的起始位置
}
}
}
}
// 分配内存并复制结果
char* ans = (char*)malloc(sizeof(char) * (maxLen + 1)); // 分配内存
strncpy(ans, s + start, maxLen); // 复制最长回文子串
ans[maxLen] = '\0'; // 添加字符串结束符
return ans; // 返回结果
}
94.最长公共子序列
题目描述
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。
提示:
1 <= text1.length, text2.length <= 1000
text1
和text2
仅由小写英文字符组成。
思路:dp
1. 状态定义
- d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 字符串 t e x t 1 text1 text1 的前 i i i 个字符 与 字符串 t e x t 2 text2 text2 的前 j j j 个字符 之间的最长公共子序列长度。
2. 初始状态
-
任何字符串和空字符串的最长公共子序列长度均为 0 0 0,即:
d p [ i ] [ 0 ] = 0 , d p [ 0 ] [ j ] = 0 dp[i][0] = 0, \quad dp[0][j] = 0 dp[i][0]=0,dp[0][j]=0 -
代码中通过
memset(dp, 0, sizeof(dp))
进行初始化。
3. 状态转移方程
对于 i ≥ 1 i \geq 1 i≥1 且 j ≥ 1 j \geq 1 j≥1,状态转移公式如下:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] + 1 , if t e x t 1 [ i − 1 ] = t e x t 2 [ j − 1 ] max ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) , if t e x t 1 [ i − 1 ] ≠ t e x t 2 [ j − 1 ] dp[i][j] = \begin{cases} dp[i-1][j-1] + 1, & \text{if } text1[i-1] = text2[j-1] \\ \max(dp[i-1][j], dp[i][j-1]), & \text{if } text1[i-1] \neq text2[j-1] \end{cases} dp[i][j]={dp[i−1][j−1]+1,max(dp[i−1][j],dp[i][j−1]),if text1[i−1]=text2[j−1]if text1[i−1]=text2[j−1]
解释:
- 匹配情况 :如果 t e x t 1 [ i − 1 ] = t e x t 2 [ j − 1 ] text1[i-1] = text2[j-1] text1[i−1]=text2[j−1],说明当前字符匹配成功,
- 则可以在之前的匹配基础上加 1 1 1,即 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i-1][j-1] + 1 dp[i][j]=dp[i−1][j−1]+1。
- 不匹配情况 :如果 t e x t 1 [ i − 1 ] ≠ t e x t 2 [ j − 1 ] text1[i-1] \neq text2[j-1] text1[i−1]=text2[j−1],则当前字符不匹配,
- 需要取去掉当前字符后的最大值,即 d p [ i ] [ j ] = max ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j] = \max(dp[i-1][j], dp[i][j-1]) dp[i][j]=max(dp[i−1][j],dp[i][j−1])。
代码
c
int longestCommonSubsequence(char* text1, char* text2) {
// 获取字符串 text1 和 text2 的长度
int m = strlen(text1), n = strlen(text2);
// 定义一个二维 DP 数组,大小为 (m+1) x (n+1)
// dp[i][j] 表示 text1 的前 i 个字符与 text2 的前 j 个字符之间的 LCS 长度
int dp[m+1][n+1];
// 将 dp 数组所有元素初始化为 0
memset(dp, 0, sizeof(dp));
// 遍历 text1 和 text2 的所有字符组合
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 如果当前字符相等,则可以将前面的 LCS 长度加 1
if (text1[i-1] == text2[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
else
// 如果不相等,则选择删除其中一个字符,取较大值
dp[i][j] = fmax(dp[i-1][j], dp[i][j-1]);
}
}
// 返回两个字符串的 LCS 长度
return dp[m][n];
}
95.编辑距离
题目描述
给你两个单词 word1
和 word2
, 请返回将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
提示:
0 <= word1.length, word2.length <= 500
word1
和word2
由小写英文字母组成
思路:dp
与583. 两个字符串的删除操作不同的是添加了插入和替换操作,其实插入和删除操作的效果是一样的,因此可以忽略添加的插入操作,只用删除和替换操作。
思路与上题相比,只是略有不同:
如果 word1[i-1] ≠ word2[j-1]
(当前字符不匹配),则有 3 种操作方式:
-
删除
word1[i-1]
(让word1
变短):dp[i-1][j] + 1
-
删除
word2[j-1]
(使word2
变短):dp[i][j-1] + 1
-
替换
word1[i-1]
为word2[j-1]
:dp[i-1][j-1] + 1
-
取最小值:
d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ] + 1 , d p [ i ] [ j − 1 ] + 1 , d p [ i − 1 ] [ j − 1 ] + 1 ) dp[i][j]=min(dp[i−1][j]+1,dp[i][j−1]+1,dp[i−1][j−1]+1) dp[i][j]=min(dp[i−1][j]+1,dp[i][j−1]+1,dp[i−1][j−1]+1)
其他都是相同的:
-
动态规划定义 :
dp[i][j]
表示word1
前i
个字符变为word2
前j
个字符的最小操作次数。 -
状态转移方程 :
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] , 如果 w o r d 1 [ i − 1 ] = w o r d 2 [ j − 1 ] min ( d p [ i − 1 ] [ j − 1 ] + 1 , d p [ i − 1 ] [ j ] + 1 , d p [ i ] [ j − 1 ] + 1 ) , 否则 dp[i][j] = \begin{cases} dp[i-1][j-1], & \text{如果 } word1[i-1] = word2[j-1] \\ \min(dp[i-1][j-1] + 1, dp[i-1][j] + 1, dp[i][j-1] + 1), & \text{否则} \end{cases} dp[i][j]={dp[i−1][j−1],min(dp[i−1][j−1]+1,dp[i−1][j]+1,dp[i][j−1]+1),如果 word1[i−1]=word2[j−1]否则 -
边界处理:
dp[i][0] = i
(删除word1
)dp[0][j] = j
(插入word2
)
代码
c
#include <stdio.h>
#include <string.h>
#include <math.h>
int minDistance(char* word1, char* word2) {
int m = strlen(word1);
int n = strlen(word2);
int dp[m+1][n+1];
// 初始化边界条件
for (int i = 0; i <= m; i++) {
dp[i][0] = i; // 需要删除 i 个字符
}
for (int j = 0; j <= n; j++) {
dp[0][j] = j; // 需要插入 j 个字符
}
// 计算 dp[i][j] 状态
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1[i-1] == word2[j-1]) { // 字符匹配,无需编辑
dp[i][j] = dp[i-1][j-1];
} else { // 选择删除、插入或替换
dp[i][j] = fmin(dp[i-1][j-1] + 1, // 替换
fmin(dp[i-1][j] + 1, // 删除
dp[i][j-1] + 1)); // 删除
}
}
}
return dp[m][n]; // 返回最少编辑距离
}