动态规划四个步骤:1、确认最后一步,以及子问题 2、转移方程 3、初始值,边界条件 4、遍历方向(从左到右,从上到下)
最大子序和
给定一个数组,找到一个具有最大和的连续数组,返回其最大值。首先是一段连续的数组,这就涉及到了该位置上的数字选还是不选,要是最大的和,如果dp[i-1]>0的话,那么dp[i]=dp[i-1] + num[i],如果都dp[i-1] < 0 的2话,num[i]加上一个负数是小于它本身的,所以dp[i[ = num[i],而这道题我们需要知道前面一位的dp值,那么我们应该初始化第一位dp[0]=num[0],这里只需要循环到n-1,即可。所以申请空间也只需要n个,最后循环遍历dp数组获取最大值。时间复杂度O(n)。
func maxSubArray(nums []int) int { max := nums[0] for i := 1; i < len(nums); i++ { if nums[i]+nums[i-1] > nums[i] { nums[i] += nums[i-1] } if nums[i] > max { max = nums[i] } } return max }
1 class Solution { 2 public: 3 int maxSubArray(vector<int>& nums) { 4 int ans = nums[0]; 5 for (int i = 1; i < nums.size(); i++) { 6 if (nums[i-1] > 0) { 7 nums[i] = nums[i] + nums[i-1]; 8 } 9 ans = max(ans, nums[i]); 10 } 11 return ans; 12 } 13 };
按摩师
根据题意,给定一个数组。如果选择第i位数,那么他的前面后面的数都不能选,找到最大值的一个集合,返回最大值。这道题同样的方式,确定最终问题,也就是最后一个数,我们有两种情况,①如果我们选择了最后一位,所以就存在 dp[i][0]=d[i-1][1]+num[i],②如果我们不选那就是dp[i][1] = max(dp[i-1][0],dp[i-1][1]),对于子问题同样的存在选与不选两种情况。所以需要一个初始值,dp[0][0] = num[0], dp[0][1] = 0,(0表示选,1表示不选)。因为求最大值,遍历方向就是从左到右,顺序遍历。
1 class Solution { 2 public: 3 int massage(vector<int>& nums) { 4 int n = (int)nums.size(); 5 if (n == 0) { 6 return 0; 7 } 8 int dp0 = nums[0], dp1 = 0; 9 for (int i = 1; i < n; i++) { 10 int tp0 = dp1 + nums[i]; 11 int tp1 = max(dp0, dp1); 12 dp0 = tp0; 13 dp1 = tp1; 14 } 15 return max(dp0, dp1); 16 } 17 };
func massage(nums []int) int { n := len(nums) dp := make([][]int, n) if n == 0 { return 0 } for index := range dp { dp[index] = make([]int, 2) } dp[0][0] = nums[0] dp[0][1] = 0 max := func(a, b int) int{ if a > b { return a }else { return b } } for i :=1; i < n; i++ { dp[i][0] = dp[i-1][1] + nums[i] dp[i][1] = max(dp[i-1][0], dp[i-1][1]) } return max(dp[n-1][0],dp[n-1][1]) }
除数博弈
一种博弈题,在最优解下先手是否必赢,通过规律可以发现,当数为偶数时,爱丽丝必赢,当为奇数时,鲍勃必赢。因为只有2才能最后到达1,而需要到达4,当爱丽丝面临着偶数情况时,可以选择最优解快速让自己到达2,而面对奇数时,能被奇数求余为0的只能是奇数,而当奇数减去奇数时,变为了偶数,然后当鲍勃开始时,则面临着和爱丽丝一样的情况,偶数时可以选择最优情况快速到达2,所以当数为偶数是返回true,当为奇数时返回false。
func divisorGame(N int) bool { return N%2 == 0 }
买卖股票的最佳时机
根据题意,你只能买进和卖出一次,求出能获得的最大利润。对于最后一天,我们应该判断是买进还是卖出,因为是求出最大值,如果我们这时候卖出则需要与前一次卖出进行对比,得到最大值,即dp[i][0] = (dp[i-1][0],dp[i-1][1]+num[i]),如果我们买进则需要选择在这之前最小的即dp[i][1] = min(dp[i-1][1],num[i]),因为我们要算获利多少,所以买进的时候应该为负数,此时我们就需要买进时的最大值。dp[i][1] = min(dp[i-1][1],-num[i]。前一次同样面临着相同的子问题。再来是初始值问题,dp[0][0] = 0,dp[0][1] = -num[i],第一次卖出是0,第一次买入为-num[0]。最后返回答案dp[len-1][0].
func maxProfit(prices []int) int { n := len(prices) if n < 2 { return 0 } dp := make([][]int, n) for index := range dp { dp[index] = make([]int, 2) } dp[0][0] = 0 dp[0][1] = -prices[0] max := func(a, b int) int { if a < b { return b } return a } for i := 1; i < n; i++ { dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i]) dp[i][1] = max(dp[i-1][1],-prices[i]) } return dp[n-1][0] }
1 class Solution { 2 public: 3 int maxProfit(vector<int>& prices) { 4 int n = prices.size(); 5 if (n == 0) { 6 return 0; 7 } 8 int dp0 = 0, dp1 = -prices[0]; 9 for (int i = 1; i < n; i++) { 10 int tp0 = max(dp0, dp1+prices[i]); 11 int tp1 = max(dp1, -prices[i]); 12 dp0 = tp0; 13 dp1 = tp1; 14 } 15 return dp0; 16 } 17 };
使用最小花费爬楼梯
根据题意可以知道,我们每次爬楼梯可以爬一步,也可以爬两步,初始位置可以在索引0和1上,那么我们考虑到达最后的花费有两种情况dp[i-1]+cost[i-1], dp[i-2]+cost[i-2], 我们需要求最小值所以dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]),初始值就是dp[0] = 0,dp[1] = 1。注意这里是要到最顶层,即最小值为dp[n]。
func minCostClimbingStairs(cost []int) int { n := len(cost) ans := make([]int, n+1) ans[0] = 0 ans[1] = 0 if n == 0 { return 0 } if n == 1{ return cost[0] } min := func(a, b int) int { if a > b { return b } return a } if n == 2 { return min(cost[0], cost[1]) } for i :=2; i <= n; i ++ { ans[i] = min(ans[i-1]+cost[i-1], ans[i-2]+cost[i-2]) } return ans[n] }
1 class Solution { 2 public: 3 int minCostClimbingStairs(vector<int>& cost) { 4 int n = cost.size(); 5 if (n <= 1) { 6 return 0; 7 } 8 vector<int> dp(n + 1, 0); 9 dp[0] = 0; 10 dp[1] = 0; 11 for(int i = 2; i <= n; i++) { 12 dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]); 13 } 14 return dp[n]; 15 } 16 };