最近学习动态规划有点吃力,感觉这个东西虽然说是有套路的,但是刚开始的时候还是觉得难。现在网上也有很多讲动态规划的原理以及做法的,我就不多说了,主要还是以例子来记录一下最近的心得。
- 首先就是leetcode上的打家劫舍这道题目,原题如下
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,
如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
对于这道题目,是一个明显的动态规划的题目,后面的决策不会影响到前面的决策,前面的决策怎么得来的这个并不重要。
- 首先设计状态,标准的我们可以定义dp[i][j];i表示第i家,j只有2个状态{0,1},0表示不偷第i家,1表示偷第i家;
- 接下来写出状态状态转移方程;
(1)dp[i][0]表示不偷第i家,那么i-1家可能是偷了,也可能是没偷,所以需要判断偷与不偷哪一个获得的金额是最大的;
所以dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]);
(2)dp[i][1]表示偷了第i家,那么第i-1家一定是没偷的,所以直接加上今天偷的金额就可以;
所以dp[i][1] = dp[i-1][0] + nums[i]; - 最后就是定义初始状态啦~显然每天偷与不偷都是取决于前一天的状态,所以定义dp[0][0] = 0,dp[0][1] = nums[0];
class Solution {
public int rob(int[] nums) {
if(nums.length == 0){
return 0;
}
if(nums.length == 1){
return nums[0];
}
int len = nums.length;
//dp[i][0]表示不偷第i家用户所获得的最大金额
//dp[i][1]表示偷了第i家用户所获得的最大金额
int[][] dp = new int[2][2];
dp[0][0] = 0;
dp[0][1] = nums[0];
for(int i=1;i<len;i++){
//这里我用了&来进行空间的优化,因为当前状态只和前一天的状态有关,所以可以只设置2个大小的数组空间;i&1相当于i%2;只是因为%的开销比较大,所以换成了位运算
dp[i & 1][0] = Math.max(dp[(i-1) & 1][0],dp[(i-1) & 1][1]);
dp[i & 1][1] = dp[(i-1) & 1][0] + nums[i];
}
return Math.max(dp[(len-1) & 1][0],dp[(len-1) & 1][1]);
}
}
当然这道题还有另外一种解法:
class Solution {
public int rob(int[] nums) {
if(nums.length == 0){
return 0;
}
if(nums.length == 1){
return nums[0];
}
int len = nums.length;
//dp[i]有两种状态
//1.偷了第i家,那么第i-1家一定没偷,但是第i-1家又包括了偷了的状态,所以应该从i-2开始;
//2.没偷第i家,那么就直接从i-1状态转移过来;
int[] dp = new int[3];
dp[0] = nums[0];;
dp[1] = Math.max(nums[0],nums[1]);
for(int i=2;i<len;i++){
//同样的这里也是优化了空间,只是我还没想到i%3如何用位运算来表示,如果大家知道的话,不妨评论区指导一下,谢谢啦~
dp[i%3] = Math.max(dp[(i-1)%3],dp[(i-2)%3] +nums[i]);
}
return dp[(len-1) % 3];
}
}
这道题目和leetcode上的另外一道题目按摩师类似,大家也可以去看一看练习一下。
- 另外一道动态规划的题目如下:
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
这道题我刚开始的写的时候还是比较绕的,简单来说要求最大子序列和,那第一步我们就定义一个最大子序列和sum,在每次遍历的时候都获取最大的那个值max_value;
- 如果sum>0,说明这个结果对我们有增益效果,保留;
- 但是sum<0,说明这个结果没有增益效果,应当直接剔除;
- 每次比较sum和max_value的值的大小即可。
class Solution {
public int maxSubArray(int[] nums) {
if(nums.length == 0){
return 0;
}
int maxSum = nums[0];
int sum = 0;
for(int i=0;i<nums.length;i++){
if(sum > 0){
sum += nums[i];
}else{
sum = nums[i];
}
maxSum = Math.max(sum,maxSum);
}
return maxSum;
}
}