动态规划系列(一)

动态规划系列(一)

留个坑,慢慢补吧,太累了,太浪费时间了啊啊啊啊啊啊啊啊啊啊啊!!!

动态规划无非分为以下几步:找到 ”状态“ 和 ”选择“ -> 明确dp数组/函数定义 -> 寻找”状态“之间的关系

1、动态规划设计:最长递增子序列

最长递增子序列(Longest Increasing Subsequence,简写为 LIS

:“子序列” 和 “子串” 这两个名词的区别,子串一定是连续的,而子序列不一定是连续的。

分析
dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度
base case: dp[i] 初始值为1,因为以 nums[i] 结尾的最长递增子序列起码要包含它自己。

代码

#include <iostream>
#include <vector>
using namespace std;

//最长递增子序列 
int lengthOfLIS(vector<int> nums){
	int n = nums.size();
	vector<int> dp(n,1);
	for(int i = 0; i < n; i++){
		for(int j = 0; j < i; j++){
			if(nums[j] < nums[i]){
				dp[i] = max(dp[i], dp[j]+1);
			}
		}
	}
	int res = 0;
	for(int i = 0; i < n; i++){
		res = max(res, dp[i]);
	}
	return res;
}

int main(){
	int n,tn;
	cin>>n;
	vector<int> nums;
	for(int i = 0; i < n; i++){
		cin>>tn;
		nums.push_back(tn);
	}
	int res = lengthOfLIS(nums);
	cout<<res;
	return 0;
}

2、二维递增子序列:信封嵌套问题

题目: 给出一些信封,每个信封用宽度和高度的整数对形式 (w,h) 表示。当一个信封 A 的宽度和高度都比另一个信封 B 大的时候,则 B 就可以放进 A 中,如同 “俄罗斯套娃” 一样。请计算最多有多少个信封能组成一组“俄罗斯套娃”信封(即最多能套几层)。

分析:信封嵌套问题实际上是最长递增子序列问题上升到二维,其解法就需要先按特定的规则排序,之后转换为一个一维的最长递增子序列问题。
每次合法的嵌套是大的套小的,相当于找一个最长递增的子序列,其长度就是最多能嵌套的信封个数。

先对宽度 w 进行升序排序,如果遇到 w 相同的情况,则按照高度 h 降序排序,之后把所有的 h 作为一个数组,在这个数组上计算出的 LIS 的长度就是答案。
因为两个 w 相同的信封不能相互包含, w 相同时将 h 逆序排序,则这些逆序 h 中最多只会有一个被选入递增子序列,保证了最终的信封序列中不会出现 w 相同的情况。

反过来也是一样的。

代码

#include <iostream>
#include <vector>
using namespace std;

//最长递增子序列 
int lengthOfLIS(vector<int> nums){
	int n = nums.size();
	vector<int> dp(n,1);
	for(int i = 0; i < n; i++){
		for(int j = 0; j < i; j++){
			if(nums[j] < nums[i]){
				dp[i] = max(dp[i], dp[j]+1);
			}
		}
	}
	int res = 0;
	for(int i = 0; i < n; i++){
		res = max(res, dp[i]);
	}
	return res;
}

//信封嵌套问题
int maxEnvelopes(vector<vector<int> > envelopes){
	int m = envelopes.size();
	vector<int> height;
	int w, h;
	for(int i = 0; i < m-1; i++){
		for(int j = 0; j < m-1-i; j++){
			if(envelopes[j][0] > envelopes[j+1][0]){
				w = envelopes[j+1][0];
				h = envelopes[j+1][1];
				envelopes[j+1][0] = envelopes[j][0];
				envelopes[j+1][1] = envelopes[j][1];
				envelopes[j][0] = w;
				envelopes[j][1] = h;
			}else if(envelopes[j][0] == envelopes[j+1][0]){
				if(envelopes[j][1] < envelopes[j+1][1]){
					h = envelopes[j+1][1];
					envelopes[j+1][1] = envelopes[j][1];
					envelopes[j][1] = h;
				}
			}
		}
	}
	for(int i = 0; i < m; i++){
		height.push_back(envelopes[i][1]);
	}
	return lengthOfLIS(height);
} 

int main(){
	int m;
	cin>>m;
	vector<vector<int> > envelopes(m,vector<int>(2,0));
	int w,h;
	for(int i = 0; i < m; i++){
		cin>>w>>h;
		envelopes[i][0] = w;
		envelopes[i][1] = h;
	}
	int res2 = maxEnvelopes(envelopes);
	cout<<res2<<endl;
	return 0;
}


3、最大子数组问题

题目:输入一个整数数组 nums,请你在其中找一个和最大的子数组,返回这个子数组的和。

分析dp 数组定义: 以 nums[i] 为结尾的“最大子数组和” 为 dp[i]。在这种定义下,想得到整个 nums 数组的“最大子数组和”,不能直接返回 dp[n-1], 而需要遍历整个 dp 数组。

代码

#include <iostream>
#include <vector>
using namespace std;

//动态规划 
int maxSubArray(vector<int> nums){
	int n = nums.size();
	if(n == 0){
		return 0;
	}
	vector<int> dp;
	dp.push_back(nums[0]);
	for(int i = 1; i < n; i++){
		dp.push_back(max(nums[i],dp[i-1]+nums[i]));
	}
	int res = INT_MIN;
	for(int i = 0; i < n; i++){
		res = max(res, dp[i]);
	}
	return res;
}

//对空间进行优化
int maxSubArray1(vector<int> nums){
	int n = nums.size();
	if(n == 0){
		return 0;
	}
	int pre = nums[0];
	int cur;
	int res = pre;
	for(int i = 1; i < n; i++){
		cur = max(nums[i], pre + nums[i]);
		pre = cur;
		res = max(res, cur);
	}
	return res;
} 

int main(){
	int n;
	cin>>n;
	vector<int> nums(n,0);
	for(int i = 0; i < n; i++){
		cin>>nums[i];
	}
	int res = maxSubArray(nums);
	cout<<res<<endl;
	int res1 = maxSubArray1(nums);
	cout<<res1<<endl;
	return 0;
}

总结

以上呢,也讲解说明了三种不同内容的动态规划问题,但其实他们的本质是一样的,只是穿了不一样的衣服罢了。就如五颜六色的小旗子一样,颜色再怎么不同,他们最终也都是小旗子呀!

对于动态规划的 最优子结构dp遍历方向 问题再进行一番说明。

最优子结构

最优子结构:可以从子问题的最优结果推出更大规模问题的最优结果。
想满足最优子结构,子问题之间必须相互独立

注:最优子结构并不是动态规划独有的一种性质,能求最值的问题大部分都具有这个性质;但反过来,最优子结构性质作为动态规划问题的必要条件,一定是让我们求最值的。所以以后碰到最值题,先思考以下暴力穷举的复杂度,如果复杂度“爆炸”的话,思路就往动态规划想就对了,这个就是套路。

dp 数组的遍历方向

就拿二维 dp 数组来说有时候是正向遍历,有时候是反向遍历,有时候也可能是斜向遍历。

但是只有把握住两点就可以啦:

1、遍历的过程中,所需状态必须是已经计算出来的。

2、遍历的终点必须是存储结果的那个位置。

ps && os:动态规划第一篇,over!!!开心!!!同时,祝大家跨年快乐,新的一年万事顺利,心想事成,平安喜乐!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值