Leetcode 动态规划 - 简单
1. 最大子序和 (53)
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
解题思路-1:
当子数组和为负数时,对后面的元素来说,其没有增益(负数会让所有与其相加的元素的和变小),舍去。从下一个元素重新开始计算连续字数组的和。定义一个最终值的存储变量存储所有子序列的最大值。
1 class Solution { 2 public: 3 int maxSubArray(vector<int>& nums) { 4 int maxEnd = nums[0],conSum=nums[0]; 5 6 for(int i=1;i<nums.size();i++){ 7 if(conSum<0){ 8 conSum=nums[i]; 9 }else{ 10 conSum+=nums[i]; 11 } 12 maxEnd = (maxEnd<conSum?conSum:maxEnd); 13 } 14 return maxEnd; 15 } 16 };
解题思路-2:
根据题目关键词,“最大”“连续”,可以判断是一道动态规划。
1)定义一个函数f(n),以第n个数为结束点的子数列的最大和,存在一个递推关系f(n) = max(f(n-1) + A[n], A[n]);
2)将这些最大和保存下来后,取最大的那个就是,最大子数组和。因为最大连续子数组 等价于 最大的以n个数为结束点的子数列和
1 class Solution { 2 public: 3 int maxSubArray(vector<int>& nums) { 4 if(nums.size() == 0) return NULL; 5 int res = INT_MIN; 6 int f_n = -1; 7 for(int i = 0; i < nums.size(); ++i){ 8 f_n = max(nums[i], f_n + nums[i]); 9 res = max(f_n, res); 10 } 11 return res; 12 } 13 };
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
2. 买卖股票的最佳时机 (121)
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4] 输出: 5 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1] 输出: 0 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
解题思路:
动态规划 前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}
#include<iostream> #include<string> #include<map> #include<bits/stdc++.h> using namespace std; class Solution { public: int maxProfit(vector<int>& prices) { if(prices.size()<=1) return 0; int minV = prices[0],maxV = prices[1]-prices[0]; //maxV = prices[0] 不能初始化位第一个值 for(int i=1;i<prices.size();i++){ minV = min(minV,prices[i]); maxV = max(maxV,prices[i]-minV); } return maxV; } }; int main(){ Solution obj; int temp; vector<int> prices; while(cin>>temp){ prices.push_back(temp); } cout<<obj.maxProfit( prices)<<endl; }
注意:
编写程序的时候注意minV与maxV的初始取值。maxV = prices[0]是错误的,不能初始化为第一个值,应该初始化为插值
3. 打家劫舍 (198)
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 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] = max(dp[i-1], dp[i-2] + nums[i]);
对于这类求极值的问题首先考虑动态规划Dynamic Programming来解,我们维护一个一位数组dp,其中dp[i]表示到i位置时不相邻数能形成的最大和。
状态方程怎么求解:
比如:nums为{3, 2, 1, 5},首先dp[0]=3,由于3比2大,所以我们抢第一个房子的3,当前房子的2不抢,所以dp[1]=3。那么再来看dp[2],由于不能抢相邻的,所以我们可以用再前面的一个的dp值加上当前的房间值,和当前房间的前面一个dp值比较,取较大值当做当前dp值,所以我们可以得到状态转移方程dp[i] = max(num[i] + dp[i - 2], dp[i - 1]), 由此看出我们需要初始化dp[0]和dp[1],其中dp[0]即为num[0],dp[1]此时应该为max(num[0], num[1])。
代码
1 class Solution { 2 public: 3 int rob(vector<int>& nums) { 4 if(nums.size()<=1) 5 return (nums.empty() ? 0: nums[0]); //可以用num.empty() ?作为三元运算符的判断 6 //初始化前两个元素 7 vector<int> res = {nums[0],max(nums[0],nums[1])};//可以只初始化前两个元素 8 9 for(int i=2;i<nums.size();i++) 10 res.push_back(max(res[i-1],res[i-2]+nums[i])); 11 //注意前面res只初始化了两个元素,因此接下来的元素存储,应该用push_back而不是res[i], 12 //当前res[i]并不存在 13 return res.back(); 14 15 } 16 };
4. 使用最小花费爬楼梯(746)
数组的每个索引做为一个阶梯,第 i
个阶梯对应着一个非负数的体力花费值 cost[i]
(索引从0开始)。
每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。
您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。
示例 1:
输入: cost = [10, 15, 20] 输出: 15 解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。
示例 2:
输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] 输出: 6 解释: 最低花费方式是从cost[0]开始,逐个经过那些1,跳过cost[3],一共花费6。
注意:
cost
的长度将会在[2, 1000]
。- 每一个
cost[i]
将会是一个Integer类型,范围为[0, 999]
。
解题思路:
用dp[i]表示爬到第i层的最小花费
每爬上一个楼梯后你可以选择爬一级或两级楼梯,因此每次都是从前面一级或者是前面两级的位置过来的。
使用方程:dp[i] = min( dp[i-2], dp[i-1] ) + cost[i]; 则dp[0]=cost[0],dp[1]=cost[1];
//dp[i] = min(dp[i-1],dp[i-2])+cost[i]; class Solution { public: int minCostClimbingStairs(vector<int>& cost) { int n = cost.size(); if(n<=1) return 0; if(n==2) return min(cost[0],cost[1]); //vector<int> dp(n) = {cost[0],cost[1]}; //error 不能这样初始化? vector<int> dp(n,0); dp[0] = cost[0]; dp[1] = cost[1]; for(int i=2;i<n;i++){ dp[i] = min(dp[i-1],dp[i-2])+cost[i]; } return min(dp[n-2], dp[n-1]); } };
使用方程:dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
则dp[0]=0,dp[1]=0;
//dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]); class Solution { public: int minCostClimbingStairs(vector<int>& cost) { int n = cost.size(); if(n<=1) return 0; if(n==2) return min(cost[0],cost[1]); vector<int> dp(n+1,0); //需要n+1个数字去存储!! for(int i=2;i<=n;i++){ //要有=n,否则是错误的!! dp[i] = min(dp[i-2]+cost[i-2], dp[i-1]+cost[i-1]); //dp[i]中存储的是i之前的最小代价,并没有计算i的代价,想获得i的代价,要求解i+1。 } return dp[n]; } };
5. 区域和检索 - 数组不可变 (303)
给定一个整数数组 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
说明:
- 你可以假设数组不可变。
- 会多次调用 sumRange 方法。
解题思路:
dp[i]存储从0到i的子数组元素和,当求解从2-5,用dp[5]-dp[1]即可。
class NumArray { public: NumArray(vector<int>& nums) { int n = nums.size(); if(n==0) //!!! 必须要有这个判断,否则会报错(执行错误) return; //vector<int> dp(n,0); 私有变量已经定义过了 dp.push_back(nums[0]); for(int i=1;i<n;i++) dp.push_back(dp[i-1]+nums[i]); } int sumRange(int i, int j) { if(i>j || i<0 ||j>=dp.size()) //异常值 return 0; if(i==0) return dp[j]; return dp[j]-dp[i-1]; } private: vector<int> dp; }; /** * Your NumArray object will be instantiated and called as such: * NumArray* obj = new NumArray(nums); * int param_1 = obj->sumRange(i,j); */
编写程序的时候注意:
1. 构造函数里,必须要判断nums是否为空,因为下面会访问nums[0]。为空直接返回。否则会报错。
2. 私有变量dp定义的时候没有定义元素的个数,因此添加元素要使用push_back();