类型三(简单)
① 走格子 (二维数组):link
题目描述
一个机器人在m×n大小的地图的左上角(起点,下图中的标记“start"的位置)。
机器人每次向下或向右移动。机器人要到达地图的右下角。(终点,下图中的标记“Finish"的位置)。
可以有多少种不同的路径从起点走到终点?
分析:
当path只有一列或者一行的时候,从start到finish只有一种走法;
当path为m*n的path时,可以看做 m-1 行 增加一行 + n-1 列 增加一列;
即 dp[i][j] = dp[i-1][j] + dp[i][j-1]
int uniquePaths(int m, int n) {
int num = 1;
vector<vector<int>>dp(m,vector<int>(n,1)); //当m或者n等于1时,只有一条路径通往终点,所以为了方便将所有的值初始化为1
if(m==0 || n == 0)
return 0;
for(int i = 1;i<m;i++)
{
for(int j = 1;j<n;j++)
{
dp[i][j] = dp[i][j-1] + dp[i-1][j] ; // 通向第[i,j]的路径有两条,分别为[i][j-1] 和 [i-1][j]
}
}
return dp[m-1][n-1];
}
②最小路径和(走格子升级版):link
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
分析:(dp[i][j]为走到第[i,j]格子上数字之和)
思想同上,第[i,j]格子 可以看成 第[i-1,j]格子向右走一步 或者 第[i,j-1]格子向下走一步 ,取其最小值
int min(int a ,int b)
{
return a<b?a:b;
}
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
if(n == 0||m == 0)
return 0;
vector<vector<int>>dp(m,vector<int>(n,0));
dp[0][0] = grid[0][0];
for(int i = 1 ;i<n;i++) //边界,最上
{
dp[0][i] = dp[0][i-1] + grid[0][i];
}
for (int i = 1;i<m;i++) //边界,最左
{
dp[i][0] = dp[i-1][0] + grid[i][0];
}
for(int i = 1;i<m;i++)
{
for(int j = 1;j<n;j++)
{
dp[i][j] = min(dp[i-1][j]+grid[i][j],dp[i][j-1]+grid[i][j]); // 第[i-1,j]格子向右走一步 或者 第[i,j-1]格子向下走一步 的最小值
}
}
return dp[m-1][n-1];
}
类型四
① 最大子序列之和:link
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
if dp[i-1] > 0
dp[i] = dp[i-1] + nums[i] //若dp[i-1]>0,则nums[i]带上前面的子序列
else
dp[i] = nums[i] //若dp[i-1]<0,则nums[i]舍弃前面的子序列,从nums[i]重新开始计算
分析:(dp[i]表示下标为i时连续数组的最大和)
当第i个数 + 前i-1个数中连续数组的最大和 的值 小于 第i个数时,说明 前i-1个数中连续数组的最大和 为负数,因此舍去,从第i个数开始累加;即,dp[i] = max(dp[i-1]+nums[i] , nums[i])
int maxval(int a,int b)
{
return a>b?a:b;
}
int maxSubArray(vector<int>& nums) {
int n = nums.size();
vector<int>dp(n,0);
if(n == 0)
return 0;
dp[0] = nums[0];
int max = dp[0];
for(int i = 1;i<n;i++)
{
dp[i] = maxval((nums[i]+dp[i-1]),nums[i]); // 当dp[i-1]<0,dp[i]=nums[i],相当于之前的序列和清零,从第i个数开始重新计算
max = max > dp[i] ? max :dp[i];
}
return max;
}
② 最大子序列和 升级版 ---- 乘积最大子序列:link
给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。
示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
分析:
由于考虑到 正数 * 负数 = 负数(最大值可能变为最小值)
负数 * 负数 = 正数(最小值可能变为最大值)
所以要用两个dp数组来存储过程中产生的最大值和最小值,防止0产生影响,因此在对比过程中要带上原来的数nums[i];
即,动态方程为:
dpmin[i] = min(min(dpmax[i-1]*nums[i],dpmin[i-1]*nums[i]),nums[i]);
dpmax[i] = max(max(dpmax[i-1]*nums[i],dpmin[i-1]*nums[i]),nums[i]);
int max(int a, int b)
{
return a>b?a:b;
}
int min(int a,int b)
{
return a<b?a:b;
}
int maxProduct(vector<int>& nums) {
int n = nums.size();
vector<int>dpmax(n,0);
vector<int>dpmin(n,0);
dpmax[0] = nums[0];
dpmin[0] = nums[0];
int maxnum = nums[0];
for(int i = 1;i<n;i++)
{
dpmin[i] = min(min(dpmax[i-1]*nums[i],dpmin[i-1]*nums[i]),nums[i])
dpmax[i] = max(max(dpmax[i-1]*nums[i],dpmin[i-1]*nums[i]),nums[i]);
maxnum = maxnum > dpmax[i] ? maxnum:dpmax[i];
}
return maxnum;
}
③ 区域和检索 - 数组不可变:link
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
示例:
给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
分析:(可利用dp[i]来存储前i项的和)
索引i到j元素和 = dp[j] - dp[i-1] //这里用dp[i-1]是因为不包括第i个数
vector<int>dp; //设置全局变量
NumArray(vector<int>& nums) {
int n = nums.size();
if(n == 0) // 若数组为空,则返回
return ;
for(int i = 0;i<n;i++) //初始化dp
{
dp.push_back(0);
}
dp[0] = nums[0];
for(int k = 1;k<n;k++)
{
dp[k] = dp[k-1] + nums[k];
}
}
int sumRange(int i, int j) {
if(i == 0)
return dp[j];
else
return dp[j] - dp[i-1];
}