序列型动态规划
动态规划dp[i]中的下标i表示前i个元素a[0],a[1],...,a[i-1]的某种性质
初始化中,dp[0]表示空序列的性质
坐标型动态规划的初始条件dp[0]就是指以a[0]为结尾的自子序列的性质
题目1:LintCode 516 Paint House II
dp[i][1]....dp[i][k] :尤其前i栋房子 并且i-1是颜色1~k 的最小花费
dp[i][1] = min{dp[i-1][2]+cost[i-1][1],.....,dp[i-1][k]+cost[i-1][1]}
dp[i][2] = min{dp[i-1][1]+cost[i-1][2],.....,dp[i-1][k]+cost[i-1][2]}
...
dp[i][k] = min{dp[i-1][1]+cost[i-1][k],.....,dp[i-1][k-1]+cost[i-1][k]}
dp[i][j] = min k≠j {dp[i-1][k]} + cost[i-1][j];
i从0到N
j从1到k
k从1到k
算法的时间复杂度:O(NK^2)
加快计算:
dp[i][j] = min k≠j {dp[i-1][k]} + cost[i-1][j];
min k≠j {dp[i-1][k]} :每次需要求f[i-1][1],...,f[i-1][k]中出了一个元素之外其他元素的最小值
重复计算
抽象:求出除了一个元素的之外的序列中的最小值
比如序列:95 88 75 98 90
75<88<90<95<95
最小值、最小值
在线维护最小值、次小值,就两个值。
假如最小值dp[i-1][a],次小值为dp[i-1][b]
则对于j=1,2,3,...,a-1,a+1,...,k dp[i][j] = dp[i][a]+cost[i-1][j];
dp[i][a] = dp[i-1][b] + cost[i-1][a];
从此时间复杂度就降维O(NK)
1 class Solution { 2 public: 3 /** 4 * @param costs: n x k cost matrix 5 * @return: an integer, the minimum cost to paint all houses 6 */ 7 int minCostII(vector<vector<int>> &A) { 8 // write your code here 9 int n = A.size(); 10 if(n==0) 11 return 0; 12 13 const int INF = 0x3f3f3f3f; 14 15 int k = A[0].size(); 16 17 int dp[n+1][k]; // 注意数组申请 18 int min1, min2; 19 int j1, j2; 20 21 // 初始化 22 for(int j=0; j<k; j++){ // dp数组第一列初始化为0 23 dp[0][j] = 0; 24 } 25 26 // 从1开始 第一个房子 到 第n个房子 27 for(int i=1; i<=n; i++){ 28 29 // 求最小值和次小值 每一次都要求 30 min1 = min2 = INF; 31 for(int j=0; j<k; j++){ 32 if(dp[i-1][j] < min1){ 33 // 把最小值给次小值 34 min2 = min1; 35 j2 = j1; 36 min1 = dp[i-1][j]; 37 j1 = j; 38 }else{ // 这里也可以用else if 39 if(dp[i-1][j] < min2){ 40 min2 = dp[i-1][j]; 41 j2 = j; 42 } 43 } 44 } 45 46 // dp计算 47 for(int j=0; j<k; j++){ 48 if(j != j1){ 49 dp[i][j] = dp[i-1][j1] + A[i-1][j]; 50 }else{ 51 // j == j1 52 dp[i][j] = dp[i-1][j2] + A[i-1][j]; 53 } 54 } 55 } 56 57 int res = INF; 58 for(int j=0; j<k; j++){ 59 res = min(res, dp[n][j]); 60 } 61 62 return res; 63 64 } 65 };
题目2:392 House Robber
https://www.lintcode.com/problem/house-robber/description
不能偷挨着的两家邻居
确定状态:
最后一个房子 偷 或者 不偷
偷n-1房子:需要知道前n-1栋房子中最大能偷多少金币
不偷n-1房子:前n-1房子的最优策略
(两种状态)
dp[i][0] 不偷
dp[i][1] 偷
- dp[i][0] = max {dp[i-1][0], dp[i-1][1]}
- dp[i][1] = dp[i-1][0] + A[i-1] 偷i,只能选择不偷i-1
---------------------------------------------------------------------------------------------
简化:在不偷房子i-1的前提下,前i栋房子中最大能偷度多少金币,其实就是前i-1栋房子能投多少金币
dp[i] 为窃贼在前i栋房子最多能投多少金币。
dp[i] = max {dp[i-1], dp[i-2]+A[i-1]}
初始条件:dp[0] = 0 没房子,偷0枚金币 序列性动态规划0就是空
dp[1] = A[0]
dp[2] = max{A[0],A[1]}
1 class Solution { 2 public: 3 /** 4 * @param A: An array of non-negative integers 5 * @return: The maximum amount of money you can rob tonight 6 */ 7 long long houseRobber(vector<int> &A) { 8 // write your code here 9 int n = A.size(); 10 if(n==0){ 11 return 0; 12 } 13 long dp[n+1]; 14 dp[0] = 0; 15 dp[1] = A[0]; 16 for(int i=2; i<=n; i++){ 17 dp[i] = max(dp[i-1], dp[i-2]+A[i-1]); 18 } 19 20 return dp[n]; 21 22 } 23 };
题目3:LintCode 534 House Robber ||
https://www.lintcode.com/problem/house-robber-ii/description
上题的房子换成了圈,房子0和房子n-1变成邻居,不能同时偷盗。
分情况讨论
情况1:没偷档子0 最优策略就是窃贼对于房子1~N-1的最优策略->化为House Robber
情况2:没偷档子n-1 最优策略就是窃贼对于房子0~N-2的最优策略->化为House Robber
1 class Solution { 2 public: 3 /** 4 * @param nums: An array of non-negative integers. 5 * @return: The maximum amount of money you can rob tonight 6 */ 7 8 int dp_hr(int A[], int n){ 9 int dp[n+1]; 10 dp[0] = 0; 11 dp[1] = A[0]; 12 for(int i=2; i<=n; i++){ 13 dp[i] = max(dp[i-1], dp[i-2]+A[i-1]); 14 } 15 return dp[n]; 16 } 17 18 int houseRobber2(vector<int> &A) { 19 // write your code here 20 21 int n = A.size(); 22 if(n==0){ 23 return 0; 24 } 25 26 if(n==1){ // 注意下面切分数组的时候考虑当数组长度为1,是切分不到东西的。 27 return A[0]; 28 } 29 30 int A_0[n-1]; 31 for(int i=0; i<n-1; i++){ 32 A_0[i] = A[i]; 33 } 34 35 int ans = dp_hr(A_0, n-1); 36 37 38 39 int A_1[n-1]; 40 for(int i=1; i<n; i++){ 41 A_1[i-1] = A[i]; // 注意这边减一 42 } 43 ans = max(ans, dp_hr(A_1, n-1)); 44 45 return ans; 46 } 47 };
题目4:LintCode 149 Best Time To Buy And Sell Stock
https://www.lintcode.com/problem/best-time-to-buy-and-sell-stock/description
[3,2,3,1,2]
2买入3卖出
先买后买
保底策略:
1 class Solution { 2 public: 3 /** 4 * @param prices: Given an integer array 5 * @return: Maximum profit 6 */ 7 int maxProfit(vector<int> &A) { 8 // write your code here 9 10 int min_a = A[0]; 11 int ans = 0; 12 for(int i=0; i<A.size(); i++){ 13 int temp = A[i]; 14 if(temp-min_a > ans){ 15 ans = temp-min_a; 16 } 17 min_a = min(min_a, temp); 18 } 19 return ans; 20 } 21 };
题目5:LintCode 150 Best Time To Buy And Sell Stock ||
任意多次买卖,但是任意时刻手中醉倒持有一股。
解法:贪心
买卖一个上升策略
1 class Solution { 2 public: 3 /** 4 * @param prices: Given an integer array 5 * @return: Maximum profit 6 */ 7 int maxProfit(vector<int> &a) { 8 // write your code here 9 10 int sum = 0; 11 int per_satrt = 0, index =0; 12 int per_end = 0; 13 if(a.size()==0){ 14 return 0; 15 } 16 // 贪心策略 17 // if(a.size() < 2){ 18 // return 0; 19 // }else if(a.size() == 2){ 20 // if(a[0] < a[1]){ 21 // return a[1]-a[0]; 22 // }else{ 23 // return 0; 24 // } 25 // } 26 for(int i=1; i<a.size(); i++){ 27 if(a[i] >= a[i-1]){ 28 per_end = i; 29 }else{ 30 sum = sum + (a[per_end] - a[per_satrt]); 31 // 清空变量 要不然会多加 32 per_satrt = i; 33 per_end = i; 34 } 35 } 36 sum = sum + (a[per_end] - a[per_satrt]); 37 38 return sum; 39 } 40 };
其实只要记录相邻两天的差值大于0就可,就可以得到上升子序列的所有和。
1 class Solution { 2 public: 3 /** 4 * @param prices: Given an integer array 5 * @return: Maximum profit 6 */ 7 int maxProfit(vector<int> &a) { 8 // write your code here 9 10 if(a.size() == 0){ 11 return 0; 12 } 13 14 int res = 0; 15 16 for(int i=0; i<a.size()-1; i++){ 17 if(a[i+1]-a[i] > 0){ 18 res += a[i+1]-a[i]; 19 } 20 } 21 22 return res; 23 } 24 };
题目5:LintCode 150 Best Time To Buy And Sell Stock |||
https://www.lintcode.com/problem/best-time-to-buy-and-sell-stock-iii/description
醉倒两次买了 两次卖,每次都是一只股
- 买过
- 没买过
- 买多少次
把买卖股票的过程划分:
阶段1 第一次买 阶段2 第一次卖 阶段3 第二次买 阶段4 第二次卖 阶段5
当股票在阶段5的时候就不能买了,阶段三是可以买卖依次,阶段四继续持有
dp[i][j]: 前i天结束后,在阶段j的最大获利。
阶段1,3,5 ---- 手中无股票
dp[i][j] = max{dp[i-1][j], dp[i-1][j-1]+p_i-1-p_i-2}
昨天没有持有股票 昨天持有股票,今天卖出清仓
阶段2,4 ---- 手中有股票
dp[i][j] = max{dp[i-1][j]+p_i-1-p_i-2, dp[i][j], dp[i-1][j-1]+p_i-1-p_i-2}
昨天就持有股票,继续持有并获利 昨天没有持有股票,今天买入 昨天持有上一次买的股票,今天买入并立即买入
模拟的过程:求序列的上升最大值,然后给每个子序列排序,选出2个值,这个过程的时间复杂度是O(NlongN)