62. 不同路径
题目描述:
一个机器人位于一个 m x n
网格的左上角 ,机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。问总共有多少条不同的路径?
思路:
其实就是问(0,0)->(m-1,n-1)一共有几条路。
第一个方法:数学上排列组合
第二个方法:
动态规划。抓住语句:机器人每次只能向下或者向右移动一步,所以动态规划转移方程为dp[i][j]=dp[i-1][j]+dp[i][j-1];考虑边界情况,dp[0][j],从(0,0)->(0,j)一共只有1条路(横着走)dp[i][0]同理只能竖着走。
所以代码如下:
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m, vector<int>(n, 1)); // 初始化二维数组并将所有值初始化为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];
}
}
return dp[m-1][n-1];
}
};
本题over
152. 乘积最大子数组
题目描述:
给你一个整数数组 nums
,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
思路:
哲哲这道题典型动态规划的背包问题。用dp[i][j]表示从i到j的最大结果,要求连续,可以重新开始,也可以是上一个的结果乘nums[j],来吧状态转移方程:dp[i][j]=max(nums[j],dp[i][j-1]*nums[j])。最后的结果就是返回dp数组里的最大值就可以。但还记得之前有一篇也是类似的做法,后来采用临时变量+max的方法,将复杂度降低么?所以这次依然尝试。
但是!!!显然我忘记了一件事负负得正这个问题。这样会存在跳过的情况。所以方案pass
但还记得之前有一篇也是类似的做法,后来采用临时变量+max的方法,将复杂度降低么?所以这次依然尝试。(但这句话依然有效)
重新分析:
1.连续想乘,什么情况下会变小?1.*0 直接=0;2.*负数;但负数,会有一个负负得正的情况。所以我们不妨将负数的结果也存下来。
2.转移方程dp[i][j]=max(nums[j],dp[i][j-1]*nums[j])。设置为临时变量+max
于是有了方法:
在计算最大乘积时,我们同时考虑当前位置的值、当前位置乘上前一个位置的最大乘积、当前位置乘上前一个位置的最小乘积这三种情况。
我们可以使用两个变量 max_prod
和 min_prod
来分别记录当前位置之前的最大乘积和最小乘积。然后我们遍历数组,对于每个位置的元素,更新 max_prod
和 min_prod
,并根据当前位置的值、当前位置乘上前一个位置的最大乘积、当前位置乘上前一个位置的最小乘积这三者之间的关系来更新这两个变量。
具体的代码逻辑如下:
- 用
max_prod
和min_prod
分别记录当前位置之前的最大乘积和最小乘积,初始化为第一个元素nums[0]
。 - 从第二个元素开始遍历数组,对于每个位置的元素,如果该元素是负数,则交换
max_prod
和min_prod
,因为乘以负数会导致最大值变成最小值,最小值变成最大值。 - 更新
max_prod
和min_prod
,分别取当前位置的值、当前位置乘上前一个位置的最大乘积、当前位置乘上前一个位置的最小乘积中的最大值和最小值。 - 在更新
max_prod
的过程中,不断维护全局的最大乘积result
。
通过这种方法,我们在遍历数组的过程中不断更新 max_prod
、min_prod
和 result
,最终得到的 result
就是乘积最大的子数组的乘积。
顺便举个例子:
假设给定数组 nums = [2, 3, -2, 4],我们来计算乘积最大的连续子数组。
初始时,我们有:
max_prod = 2 (以第一个元素结尾的最大乘积)
min_prod = 2 (以第一个元素结尾的最小乘积)
result = 2 (全局最大乘积)
接下来,我们依次遍历数组中的元素:
对于第二个元素 3:
更新 max_prod = max(3, 2 * 3) = 6,min_prod = min(3, 2 * 3) = 3
更新 result = max(result, 6) = 6
对于第三个元素 -2:
因为是负数,交换 max_prod 和 min_prod
更新 max_prod = max(-2, 6 * -2) = -2,min_prod = min(-2, 6* -2) = -12
更新 result = max(result, -2) = 6
对于第四个元素 4:
更新 max_prod = max(4, -2 * 4) = 4,min_prod = min(4, -12 * 4) = -48
更新 result = max(result, 4) = 6
最终得到的结果是 6,即数组中乘积最大的连续子数组的乘积为 6。
代码如下:
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
if (n == 0) return 0;
int max_prod = nums[0];
int min_prod = nums[0];
int result = nums[0];
for (int i = 1; i < n; i++) {
if (nums[i] < 0) {
swap(max_prod, min_prod);
}
max_prod = max(nums[i], max_prod * nums[i]);
min_prod = min(nums[i], min_prod * nums[i]);
result = max(result, max_prod);
}
return result;
}
};
198. 打家劫舍
题目:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
思路:
动态规划,当前一个偷完之后,下一个一定不能偷,但下下一个不一定。………………这样思考比较麻烦,不妨倒过来思考。
假设,我到第k个房间,当前的值记为dp[k];则有两种情况:1,上一个房间我投过了,这个房间我不能投;2,上一个房间我没投,我投这个房间。所以dp就有以下表达式
dp[k]=max(dp[k-1],dp[k-2]+nums[k-1])
边界情况:
dp[0]=0;
dp[1]=nums[0];dp[2]=max(nums[0],nums[1]);
//dp[2]=max(nums[0],dp[2-2]+nums[1]);
推荐题解:. - 力扣(LeetCode)
好,于是代码如下:(这个用常量优化有点难,所以先写不优化的)
class Solution {
public:
int rob(vector<int>& nums) {
int n=nums.size();
if(n==0) return 0;
if(n==1) return nums[0];
vector<int>dp(n+1);
dp[0]=0;//dp.push_back(0);//
dp[1]=nums[0];
dp[2]=max(nums[0],dp[0]+nums[1]);
for(int i=3;i<=n;i++){
dp[i]=max(dp[i-1],dp[i-2]+nums[i-1]);
}
return dp[n];
}
};
性能太差,优化一下:
优化方法:两个常量+max;(前面两道题也是这样做的,要学会)
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if (n == 0)
return 0;
if (n == 1)
return nums[0];
int prev1 = 0; // 用于存储偷窃到当前房屋的最大价值
int prev2 = 0; // 用于存储偷窃到前一间房屋的最大价值
for (int i = 0; i < n; ++i) {
int temp = prev1; // 临时变量存储偷窃到当前房屋的最大价值
prev1 =max(prev2 + nums[i], prev1); // 更新当前房屋的最大偷窃价值
prev2 = temp; // 更新前一间房屋的最大偷窃价值
}
return prev1; // 返回最后一间房屋的偷窃价值即为最终答案
}
};
今日结束ocver
总结一下动态规划的模板吧
动态规划问题的一般解题思路可以总结为以下几个步骤:
定义状态:明确定义
dp
数组的含义,即每个元素dp[i]
代表的是什么状态,可以是最大值、最小值等。初始化:根据问题的要求对
dp
数组进行初始化,将初始条件赋给dp
数组中相应的元素。状态转移方程:找出状态之间的转移关系,即如何通过已知的状态推导出未知的状态。这是动态规划问题的核心,也是最难的部分。
递推计算:通过循环遍历或者递归的方式填充
dp
数组,根据状态转移方程更新每个位置的值。返回结果:根据问题的要求从
dp
数组中得到最终的结果,可能是dp
数组的最后一个元素,也可能是整个dp
数组中的最大/最小值等。代码:
// 初始化dp数组 vector<int> dp(n, initial_value); // 处理边界条件 dp[0] = initial_condition; // 状态转移方程计算dp数组的值 for (int i = 1; i < n; ++i) { // 根据状态转移方程更新dp[i] dp[i] = update_function(dp, i, other_parameters); } // 返回最终结果 return final_result;
说在最后,
其实动态规划最核心的就是找出地推关系式。可以抽象的想第k个状态,往前倒退,找出关系式,用1,2,临街情况测试状态方程是否正确。最后再讲dp数组改进为常量,节省空间。