Dynamic Programming(动态规划算法题总结)

暑假也是闲来无事,就想做做动态规划的题。想想在过去不久的第十届蓝桥杯国赛经历,几乎全是动态规划的题,虽然有个别题可以用深搜广搜解决,但是能过的样例不超过30%,都会超时的。因此我想总结一下。希望不会让我掉头发,这太烧脑了…

1.矩形覆盖

我们可以用2 * 1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2 * 1的小矩形无重叠地覆盖一个2 * n的大矩形,总共有多少种方法?
思路
这个题我之前也做过,深搜就可以解决,但是数据量一大必然超时。当然,在这里我们要用dp解决。

2 * n的大矩形,和n个2 * 1的小矩形
其中n * 2为大矩阵的大小
有以下几种情形:
n <= 0 大矩形为0 * 1,直接return 0;
n = 1大矩形为2 * 1,只有一种摆放方法,return1;
n = 2 大矩形为2 * 2,有两种摆放方法,return2;
n = t 分为两步考虑:
第一次摆放一块 2 * 1 的小矩阵,则摆放方法总共为f(t - 1)
在这里插入图片描述
第一次摆放一块1 * 2的小矩阵,则摆放方法总共为f(t-2)
因为,摆放了一块1 * 2的小矩阵(用√√表示),对应下方的1 * 2(用××表示)摆放方法就确定了,所以为f(t-2)
在这里插入图片描述

class Solution {
public:
	int rectCover(int number) {
		if (number <= 0)
			return 0;
		if (number <= 2)
			return number;
		int f1 = 1;
		int f2 = 2;
		int tmp;
		for (int i = 3; i <= number; i++)
		{
			tmp = f1 + f2;
			f1 = f2;
			f2 = tmp;
		}
		return tmp;
	}
};

2.连续子数组的最大和

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

class Solution {
public:
	int FindGreatestSumOfSubArray(vector<int> array) {
		int sum = array[0];
		int max = array[0];
		for (size_t i = 1; i < array.size(); i++)
		{
			sum = sum <= 0 ? array[i] : sum + array[i];
			max = max < sum ? sum : max;
		}
		return max;
	}
};
array6-3-27-15122
sum6318-7135

思考
之前我做这个题的时候,一直在想,array[i]是正数时怎么加,是负数时怎么加,这样很容易出错。动态规划的题应该紧紧抓住前面已经求解了的结果,紧紧抓住sum。
当sum是负数时,sum应该等于array[i],因为负数对sum没有贡献,是正数的时候sum+=array[i]。
再用max记录sum变化过程中的最大值就行了。

3.路径总数

一个机器人在m×n大小的地图的左上角(起点,下图中的标记“start"的位置)。
机器人每次向下或向右移动。机器人要到达地图的右下角。(终点,下图中的标记“Finish"的位置)。
可以有多少种不同的路径从起点走到终点?
在这里插入图片描述
上图是3×7大小的地图,有多少不同的路径?
备注:m和n小于等于100

class Solution {
public:
	int uniquePaths(int m, int n) {
		int *arr = new int[n];
		for (int i = 0; i < n; i++)
			arr[i] = 1;
		for (int i = 1; i < m; i++)
		{
			for (int j = 1; j < n; j++)
				arr[j] += arr[j - 1];
		}
		return arr[n - 1];
	}
};

思考
简单的dp问题,对于位置arr【i】【j】,他的路径数是arr【i-1】【j】加上arr【i】【j-1】的路径数,
而在第一行和第一列的位置,他们的路径数都为1。

4.背包问题

我之前写过背包问题的详细分析,这里直接附上链接吧背包问题

5.回文串分割

给出一个字符串s,分割s使得分割出的每一个子串都是回文串
计算将字符串s分割成回文分割结果的最小切割数
例如:给定字符串s=“aab”,
返回1,因为回文分割结果[“aa”,“b”]是切割一次生成的。

class Solution {
public:
	int minCut(string s)
	{
		if (s.empty())
			return 0;
		vector<int> maze;
		int len = s.size();
		for (int i = 0; i <= len; i++)
			maze.push_back(i - 1);
		for (int i = 1; i <= len; i++)
		{
			for (int j = 0; j < i; j++)
			{
				if (isPalindrome(s, j, i - 1))
				{
					maze[i] = maze[i] < maze[j] + 1 ? maze[i] : maze[j] + 1;
				}
			}
		}
		return maze[len];
	}
	bool isPalindrome(string s, int start, int end)
	{
		while (start < end)
		{
			if (s[start] != s[end])
				return false;
			start++;
			end--;
		}
		return true;
	}
};
字符串aleelattle
下标012345678910
初始化-10123456789
结果0122101123

初始化的内容是当没有回文字符串时,maze[i]代表前i个字符需要进行多少次分割。
当下标j到i-1所对应的字符串是回文串时,maze[i]的值便有可能更新,maze[i] = maze[i] < maze[j] + 1 ? maze[i] : maze[j] + 1;具体通过表格看例子能很快理解。最后maze[len]便是答案。
优化
上述代码在判断是否回文串时时间复杂度较高,可以动态规划一下判断回文串。

class Solution2 {
public:    
	int minCut(string s) 
	{
		if (s.empty()) 
			return 0;
		int len = s.size();        
		vector<int> cut;        
		// F(i)初始化    
		// F(0)= -1,必要项,如果没有这一项,对于重叠字符串“aaaaa”会产生错误的结果        
		for (int i = 0; i < 1 + len; ++i) 
		{   
			cut.push_back(i - 1); 
		}      
		vector<vector<bool> > mat = getMat(s);
		for (int i = 1; i < 1 + len; ++i)
		{
			for (int j = 0; j < i; ++j) 
			{  
				// F(i) = min{F(i), 1 + F(j)}, where j<i && j+1到i是回文串      
				// 从长串判断,如果从第j+1到i为回文字符串        
				// 则再加一次分割,从1到j,j+1到i的字符就全部分成了回文字符串      
				if (mat[j][i - 1]) {    
					cut[i] = cut[i] < 1 + cut[j] ? cut[i] : 1 + cut[j];
				}
			}
		}
		return cut[len];
	}
	vector<vector<bool> > getMat(string s) 
	{
		int len = s.size();       
		vector<vector<bool> > mat = vector<vector<bool> >(len, vector<bool>(len, false));    
		for (int i = len - 1; i >= 0; --i)
		{
			for (int j = i; j < len; ++j) 
			{
				if (j == i) {   
					// 单字符为回文字符串     
					mat[i][j] = true;               
				}
				else if (j == i + 1) {  
					// 相邻字符如果相同,则为回文字符串   
					mat[i][j] = (s[i] == s[j]);       
				}         
				else {         
					mat[i][j] = ((s[i] == s[j]) && mat[i + 1][j - 1]);       
				}           
			}    
		}
		return mat;
	}
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值