leetcode刷题动态规划-第1篇
Posted 非晚非晚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode刷题动态规划-第1篇相关的知识,希望对你有一定的参考价值。
当你超过别人一点点,别人会嫉妒你;当你超过别人一大截,别人就会羡慕你。所以尽可能的超越吧!坚持终会有收获,大家一起加油!
1. 最长回文子串
题目:给你一个字符串 s,找到 s 中最长的回文子串。
解题思路(参考官网解答):
(两种算法时间复杂度都是 O ( n 2 ) O(n^2) O(n2),中心扩展算法空间复杂的 O ( 1 ) O(1) O(1),动态规划的空间复杂度为 O ( n 2 ) O(n^2) O(n2),复杂度中心扩散相对比较好实现)
- 动态规划法:dp[i][j]为dp[i+1][j-1]的下一个状态,如果s[i] = s[j],dp[i][j]的回文与否和上一状态有关。
- 中心扩展算法:所有的字串都是由中心向两边扩散出来的,也就是由1或者2个相同的字串得到。
动态规划法求解:
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
if (n < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
// dp[i][j] 表示 s[i..j] 是否是回文串
vector<vector<int>> dp(n, vector<int>(n));
// 初始化:所有长度为 1 的子串都是回文串
for (int i = 0; i < n; i++) {
dp[i][i] = true;
}
// 递推开始
// 先枚举子串长度,每种长度都会计算到。
for (int L = 2; L <= n; L++) {
// 枚举左边界,左边界的上限设置可以宽松一些
for (int i = 0; i < n; i++) {
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
int j = L + i - 1;
// 如果右边界越界,就可以退出当前循环
if (j >= n) {
break;
}
if (s[i] != s[j]) {
dp[i][j] = false;
} else {
if (j - i < 3) {//长度在1、2、3
dp[i][j] = true;
} else {//长度超过3,则由它的字串决定
dp[i][j] = dp[i + 1][j - 1];
}
}
// 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substr(begin, maxLen);
}
};
中心扩展算法:
class Solution {
public:
//扩散算法
pair<int, int> expandAroundCenter(const string& s, int left, int right) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
--left;
++right;
}
return {left + 1, right - 1};//必须用{}
}
string longestPalindrome(string s) {
int start = 0, end = 0;
for (int i = 0; i < s.size(); ++i) {
//类型推导pair,开始扩散,求取最大的回文
auto [left1, right1] = expandAroundCenter(s, i, i);//必须用[]
auto [left2, right2] = expandAroundCenter(s, i, i + 1);
if (right1 - left1 > end - start) {
start = left1;
end = right1;
}
if (right2 - left2 > end - start) {
start = left2;
end = right2;
}
}
return s.substr(start, end - start + 1);
}
};
2. 接雨水
题目:给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
解题思路:使用双指针指向两端,然后分别向中间移动。
class Solution {
public:
int trap(vector<int>& height) {
int ans = 0;
int left = 0, right = height.size() - 1;//指向两端
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = max(leftMax, height[left]);//左边的最大值
rightMax = max(rightMax, height[right]);//右边的最大值
if (height[left] < height[right]) {
ans += leftMax - height[left];//左侧的高度差
++left;
} else {
ans += rightMax - height[right];//右侧的高度差
--right;
}
}
return ans;
}
};
3. 最大子序和
题目:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
解题思路:判断的前面的序列是否为正(对后续序列是否有贡献),如果为正,则dp[i] = dp[i-1]+a[i]
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int maxSum = nums[0];
for(int i = 1; i < nums.size(); i++)
{
if(nums[i - 1] > 0) nums[i] += nums[i - 1];//判断对后续是否有贡献
if(nums[i] > maxSum) maxSum = nums[i];
}
return maxSum;
}
};
4. 不同路径
题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?
解题思路:
- 状态转移:dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
- 如果在边上则dp[i][j] = 1;
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int> > vec(m,vector<int>(n,0)); //初始化row * col二维动态数组,初始化值为0
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
{
if(i == 0 || j == 0){vec[i][j] = 1;}//在边上的只有一条路径
else
{
vec[i][j] = vec[i - 1][j] + vec[i][j - 1];
}
}
return vec[m - 1][n - 1];
}
};
5. 不同路径 II
题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用 1 和 0 来表示。
解题思路:先遍历首行首列,然后遍历中间。细节就是要判断上一状态和当前状态是否有障碍物。
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
if(obstacleGrid[m - 1][n - 1] == 1) return 0;
vector<vector<int>> vec(m, vector<int>(n, 0));
for(int i = 0; i < m; i++)//遍历边上
{
if(obstacleGrid[i][0] == 1)
{
while(i < m){
vec[i][0] = 0;
i++;
}
break;
}
vec[i][0] = 1;
}
for(int j = 0; j < n; j++)//遍历边上
{
if(obstacleGrid[0][j] == 1)
{
while(j < n){
vec[0][j] = 0;
j++;
}
break;
}
vec[0][j] = 1;
}
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
{
if(i == 0 || j == 0)continue;//当前位置有障碍物
else if(obstacleGrid[i - 1][j] == 1 && obstacleGrid[i][j - 1] == 1)
{
vec[i][j] = 0;
}
else if(obstacleGrid[i - 1][j] == 1)
{
vec[i][j] = vec[i][j - 1];
}
else if(obstacleGrid[i][j - 1] == 1)
{
vec[i][j] = vec[i - 1][j];
}
else
{
vec[i][j] = vec[i - 1][j] + vec[i][j - 1];
}
}
return vec[m - 1][n - 1];
}
};
6. 最小路径和
题目:给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
解题思路:
- 对于非首行首列dp[i][j] = min(dp[i-1][j], dp[i][j-1]);
- 首行首列则只有一条路。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
if(grid.size() < 0) return 0;
int m = grid.size();
int n = grid[0].size();
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
{
if(i == 0 && j == 0)continue;//第一个数字
else if(i == 0) grid[i][j] += grid[i][j - 1];//首行
else if(j == 0) grid[i][j] += grid[i - 1][j];//首列
else grid[i][j] += min(grid[i - 1][j], grid[i][j - 1]);//中间
}
return grid[m - 1][n - 1];
}
};
7. 爬楼梯
题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
解题思路:dp[i] = dp[i - 1] + dp[i - 2];
class Solution {
public:
int climbStairs(int n) {
if(n < 3) return n;//第1,2个元素
int a = 1;
int b = 2;
for(int i = 3; i <= n; i++)//从第3个元素开始
{
int tmp = a + b;
a = b;
b = tmp;
}
return b;
}
};
8. 编辑距离
题目:给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
解题思路:总体下来只有两种操作,
一种是插入,一种是修改
。(因为删除操作,也可以是对另一个字符串进行插入),所以动态规划的转移方程为:
- 如果word1[i] == word2[j],也就是最后一个字符相同,则
dp[i][j] = dp[i - 1][j - 1]
.- 如果不相同,则为三种情况的最小值+1:
- 知道"abcd"变成"fgh"多少步(假设X步),那么从"abcde"到"fgh"就是"abcde"->“abcd”->“fgh”。(一次删除,加X步,总共X+1步)->
dp[i][j] = dp[i-1][j] + 1
- 知道"abcde"变成“fg”多少步(假设Y步),那么从"abcde"到"fgh"就是"abcde"->“fg”->“fgh”。(先Y步,再一次添加,加X步,总共Y+1步) ->
dp[i][j] = dp[i][j-1]+1
- 知道"abcd"变成“fg”多少步(假设Z步),那么从"abcde"到"fgh"就是"abcde"->“fge”->“fgh”。->
dp[i][j] = dp[i-1][j-1] + 1
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.length();
int m = word2.length();
// 有一个字符串为空串
if (n * m == 0) return n + m;
// DP 数组
int D[n + 1][m + 1];
// 边界状态初始化
for (int i = 0; i < n + 1; i++) {
D[i][0] = i;
}
for (int j = 0; j < m + 1; j++) {
D[0][j] = j;
}
// 计算所有 DP 值,所有元素都从1开始
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < m + 1以上是关于leetcode刷题动态规划-第1篇的主要内容,如果未能解决你的问题,请参考以下文章