LeetCode53.最大子序列和
https://leetcode-cn.com/problems/maximum-subarray/
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
动态规划:
1.递推关系
(1).明确dp[i]表示什么:
dp[i] 表示以nums[i]为结尾的最大子序列和
假设nums数组长度为size,最终结果为:max{ dp[i] },其中0 <= i <= (size - 1)
(2).根据题意导出递推关系:
if (dp[i - 1] > 0): dp[i] = d[i - 1] + nums[i]
if (dp[i - 1] <= 0): dp[i] = nums[i]
(3).确认初始条件:
dp[0] = nums[0]
2.题解
class Solution
{
public:
//动态规划
int maxSubArray(vector<int>& nums)
{
int size = nums.size();
if (size == 0) return 0;
vector<int> dp(size);
dp[0] = nums[0];
for (int i = 1;i < size;++i)
{
if (dp[i - 1] > 0)
{
dp[i] = dp[i - 1] + nums[i];
}
else
{
dp[i] = nums[i];
}
}
return *max_element(dp.begin(), dp.end());
}
};
3.时间复杂度
假设数组nums长度为N,则时间复杂度为O(N)
4.空间复杂度
假设数组nums长度为N,则空间复杂度为O(N)
优化:
1.基本思想
因为只需要找子序列和中最大的那个值,所以我们可以在计算以每一个数组元素为结尾的子序列和的过程中,不断去更新最大的那个值。
2.题解
class Solution
{
public:
//优化
int maxSubArray(vector<int>& nums)
{
int size = nums.size();
if (size == 0) return 0;
int pre = nums[0];
int result = nums[0];
for (int i = 1;i < size;++i)
{
if (pre > 0)
{
pre = pre + nums[i];
}
else
{
pre = nums[i];
}
result = max(result, pre);
}
return result;
}
};
3.时间复杂度
假设数组nums长度为N,则时间复杂度为O(N)
4.空间复杂度
空间复杂度为O(1)
Leetcode1143.最长公共子序列
https://leetcode-cn.com/problems/longest-common-subsequence/
定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
若这两个字符串没有公共子序列,则返回 0。
输入:text1 = “abcbdab”, text2 = “bdcaba”
输出:4
解释:最长公共子序列是 “bcba”,它的长度为 4。
动态规划:
1.递推关系
(1).明确dp[i] [j]表示什么:
dp[i] [j]表示长度为i的字符串1和长度为j的字符串2的最长公共子序列的长度
假设text1的长度为size1,text2的长度为size2,最终结果为dp[size1] [size2]
(2).根据题意导出递推关系:
当text1[i - 1] == text2[j - 1]时,说明该字符是公共子序列的一部分。
而当text1[i - 1] != text2[j - 1]时,说明这两个字符中至少有一个字符不是公共子序列的一部分
所以:
if (text1[i - 1] == text2[j - 1]):dp[i] [j] = dp[i - 1] [j - 1] + 1
if (text1[i - 1] != text2[j - 1]):dp[i] [j] = max{ dp[i - 1] [j], dp[i] [j - 1] }
(3).确认初始条件:
dp[0] [j] = 0,其中1 <= j <= size2
dp[i] [0] = 0,其中1 <= i <= size1
2.题解
class Solution
{
public:
int longestCommonSubsequence(string text1, string text2)
{
int size1 = text1.size();
int size2 = text2.size();
vector<vector<int>> dp(size1 + 1, vector<int>(size2 + 1, 0));
for (int i = 1;i < size1 + 1;++i)
{
for (int j = 1;j < size2 + 1;++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[size1][size2];
}
};
3.时间复杂度
假设字符串text1长度为M,字符串text2长度为N,则时间复杂度为O(MN)
4.空间复杂度
假设字符串text1长度为M,字符串text2长度为N,则空间复杂度为O(MN)
LeetCode64.最小(大)路径和
https://leetcode-cn.com/problems/minimum-path-sum/
给定一个包含非负整数的 m x n
网格 grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
动态规划:
1.递推关系
(1).明确dp[i] [j]表示什么:
dp[i] [j]表示走到网格的第i行,第j列的时候,路径和的最小值
假设网格有m行,n列,最终结果为dp[m - 1] [n - 1]
(2).根据题意导出递推关系:
因为在仅在第一行或第一列移动时,路径是唯一的,所以初始条件为:
dp[0] [j] = dp[0] [j - 1] + grid[0] [j],其中0 <= j <= (n - 1)
dp[i] [0] = dp[i - 1] [0] +grid[i] [0] ,其中0 <= i <= (m - 1)
而不在第一行和第一列移动时,移动到一个格子可能是从上面来的,也可能是从左边来的,这时就需判断上面来的和左面来的那个路径和比较小,所以:
dp[i] [j] = min{ dp[i - 1] [j], dp[i] [j - 1]} + grid[i] [j]
2.题解
class Solution
{
public:
int minPathSum(vector<vector<int>>& grid)
{
if (grid.size() == 0 || grid[0].size() == 0)
{
return 0;
}
int m = grid.size();
int n = grid[0].size();
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] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[m - 1][n - 1];
}
};
3.时间复杂度
假设网格行数为M,列数为N,则时间复杂度为O(MN)
4.空间复杂度
假设网格行数为M,列数为N,则空间复杂度为O(MN)
扩展.最小(大)路径和2
给定一个m * n的二维数组。现在从左上走到右下再回到左上,中间不走重复的点,求所走单元的和的最大值。
动态规划:
1.思路
(1).分析问题:
设二维数组grid行数为M,列数为N。
首先,将问题转换为求两个对象同时从(0, 0)出发到(M - 1, N - 1),途中两者不能相遇。
使用一个三维数组dp[step] [row1] [row2],step代表两者当前步数,row1,row2分别代表两者当前所在的行,相应的两者当前所在的列可以通过step - row1 -1, step - row2 -1计算得到。两者同时从起点出发,两者走到同一位置也肯定是同时到达:如果一起走到了终点,则返回终点的值;如果一起走到了除终点外的同一位置,则返回0 。
(2)递推公式:
dp [step] [row1] [row2] =
max(两者同时向下,一个向下,另一个向右,一个向右,另一个向下,两者同时向右) +
grid[row1] [step - row1 -1] + grid[row2] [step - row2 -1]
2.源码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int maxPathSum2(vector<vector<vector<int>>>& dp,
const vector<vector<int>>& grid,
int step, int row1, int row2)
{
int m = grid.size();
int n = grid[0].size();
//两者同时走到右下
if (step == m + n -1)
{
return grid[row1][step - row1 - 1];
}
//两者在非起点非终点位置相遇
if (row1 == row2 && step != 1)
{
return 0;
}
//使用dp[step][row1][row2]存储记忆化搜索
if (dp[step][row1][row2] != -1)
{
return dp[step][row1][row2];
}
int result = 0;
//两者同时向下
if (row1 < m - 1 && row2 < m - 1)
{
result =
max(result, maxPathSum2(dp, grid, step + 1, row1 + 1, row2 + 1));
}
//一个向下,另一个向右
if (row1 < m - 1 && step - row2 - 1 < n - 1)
{
result =
max(result, maxPathSum2(dp, grid, step + 1, row1 + 1, row2));
}
//一个向右,另一个向下
if (step - row1 - 1 < n - 1 && row2 < m - 1)
{
result =
max(result, maxPathSum2(dp, grid, step + 1, row1, row2 + 1));
}
//两者同时向右
if (step - row1 - 1 < n - 1 && step - row2 - 1 < n - 1)
{
result =
max(result, maxPathSum2(dp, grid, step + 1, row1, row2));
}
result =
result + grid[row1][step - row1 -1] + grid[row2][step - row2 -1];
dp[step][row1][row2] = result;
return result;
}
int main()
{
vector<vector<int>> grid = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
vector<vector<vector<int>>> dp
(5, vector<vector<int>>(3, vector<int>(3, -1)));
cout << "二维数组为: " << endl;
for (int i = 0;i < grid.size();++i)
{
for (int j = 0;j < grid[0].size();++j)
{
cout << grid[i][j] << " ";
}
cout << endl;
}
cout << "所走单元之和最大值为: " << maxPathSum2(dp, grid, 1, 0, 0) - grid[0][0] <<endl;
return 0;
}
扩展.ski
Michael 想知道在一个区域中最长的滑坡。 区域由一个二维数组给出,数组的每个数字代表点的高度。当且仅当高度减小,一个人可以从某个点滑向上下左右相邻四个点之一。
动态规划:
1.递推关系
(1).明确dp[i] [j]表示什么:
dp[i] [j]表示从二维数组height的第i行,第j列那个点出发的最长滑波的长度
假设二维数组height有m行,n列,最终结果为:
max{ dp[i] [j] } + 1,其中0 <= i <= m - 1, 0 <= j <= n - 1
(2).根据题意导出递推关系:
只要一个点的高度大于四周任意一点的高度,那么就能从这个点向四周滑行一个单位,所以:
dp[i] [j] = max{ dp[i-1] [j], dp[i] [j-1], dp[i+1] [j], dp[i] [j+1] } + 1
2.源码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//滑行数组
vector<int> dx = {1, -1, 0, 0};
vector<int> dy = {0, 0, 1, -1};
//辅助函数,判断是否越界
bool inBound(int rows, int columns, int i,int j)
{
return (i >= 0 && i < rows && j >=0 && j <columns);
}
int dpSki(const vector<vector<int>>& height,
vector<vector<int>>& dp, int i, int j)
{
int rows = height.size();
int columns = height[0].size();
//使用dp[i][j]存储记忆化搜索
if (dp[i][j] != 0)
{
return dp[i][j];
}
for (int k = 0;k < 4;++k)
{
//未越界
if (inBound(rows, columns, i + dx[k], j + dy[k]))
{
//四周比中间低
if (height[i + dx[k]][j + dy[k]] < height[i][j])
{
int temp = dpSki(height, dp, i + dx[k], j + dy[k]);
dp[i][j] = max(dp[i][j], temp + 1);
}
}
}
return dp[i][j];
}
int main()
{
int rows;
int columns;
cout << "请输入行数和列数: " << endl;
cin >> rows >> columns;
cout << "请输入高度矩阵: " << endl;
vector<vector<int>> height(rows, vector<int>(columns, 0));
for (int i = 0;i < rows;++i)
{
for (int j = 0;j <columns;++j)
{
cin >> height[i][j];
}
}
vector<vector<int>> dp(rows, vector<int>(columns, 0));
int maxDist = 0;
for (int i = 0;i < rows;++i)
{
for (int j = 0;j <columns;++j)
{
int temp = dpSki(height, dp, i, j);
maxDist = max(maxDist, temp);
}
}
cout << "最长滑行距离为:" << maxDist + 1 << endl;
return 0;
}
3.时间复杂度
假设二维数组height行数为M,列数为N,则时间复杂度为O(MN)
4.空间复杂度
假设二维数组height行数为M,列数为N,则空间复杂度为O(MN)