浅谈动态规划

碎碎念部分

  • 动态规划其实就是把一个大的问题,像剥洋葱一样一层一层地分解,把复杂的问题简单化的过程。我认为动态规划有点像分治法和贪心算法的结合,采用分治法分解问题的思想,再加上贪心算法最优解的思想,在一定比例调和后就是现在见到的动态规划啦。
  • 动态规划解决问题的方式是让每个子问题的解都达到全局最优,并且记录该子问题的答案。在需要重复计算时,直接调用前面记录过的子问题答案,可以更快速的解决问题,这也就是我们常说的用空间换时间(用储存子问题答案的空间换重复计算子问题答案的时间)。
  • 在记录子问题答案时,我们通常会新设置一个数组(我们习惯性称呼为dp数组)来储存子问题的答案。这个数组可以是一维的、二维的,在有些题目下甚至可以简化为是一个变量,并没有强制要求(差不多内意思就行)。

贪心算法和动态规划

在这里我们将用一道例题来讲述贪心和动规的区别:
三角形最小路径和(LeetCode120)
题目描述

  • 若采用动态规划的思想,则是新建一个二维数组(相当于复制了一个一样的空三角形),在对应的位置上同步记录到达原三角形上该结点的最小路径和,最后只需要取出最后一行中最小的值即可。(详细解题过程移步另一篇博客)
    LeetCode 120.三角形最小路径和——详解
  • 若使用贪心算法,则是移动到下一步两个结点中较小的那个并记录路径和就好了
    肉眼看两个思路没什么差别,但是对于一些特别的例子可就行不通了,比如这个:
[[-1],[2,3],[1,-1,-3]]
-1
 2	 3
 1	-1 	-3

这个案例用贪心算法的思想,结果应该为0(-1 + 2 + -1 = 0)
但是正确答案却是-1(-1 + 3 + -3 = -1)
所以这种情况明显不适合用贪心
综上所述,可以说贪心算法是动态规划的一种特殊情况
(分治法和动态规划孩子懒得写了 大家自己悟吧)

题目练习

在这部分,我们将分步骤解析每一道题,平时在练习过程中也可使用这个顺序来分析题目:

  1. 分解问题
  2. dp数组(包括形式及含义)
  3. 递推公式
  4. 分析边界、排除特殊情况

三角形最小路径和(LeetCode120)

题目描述

题目描述看上一部分

题目分析

1.分解问题: 将自顶向下的最小路径和分解为上一个位置到当前位置的最小路径(注意不能只看当前最小值,防止陷入贪心算法的误区)
2. dp数组分析:dp[i][j]存放由顶到位置[i][j]的路径最小和
3. 递推公式dp[i][j] = dp[i - 1][j] + triangle[i][j]
(当前结点dp值=正上方或者左上方的dp值+当前结点值)
4. 边界条件:当当前位置为每行两端的位置时,最左端只有正上方,最右端只有左上方(特殊情况要单独if出来,不然要越界的)

代码实现
int minimumTotal(int** triangle, int triangleSize, int* triangleColSize) {
	int a = triangleColSize[triangleSize - 1];
	int dp[200][200] = { 0 };
	int i = 0, j = 0;
	int min = 0;
	for (i = 0; i < triangleSize; i++) {
		for (j = 0; j < triangleColSize[i]; j++) {
			if (j == 0) {
				if (i == 0) {
					dp[i][j] = triangle[i][j];
					printf("dp[%d][%d] = %d\n", i, j, dp[i][j]);
				}
				else {
					dp[i][j] = dp[i - 1][j] + triangle[i][j];
					printf("dp[%d][%d] = %d\n", i, j, dp[i][j]);
				}
			}
			else if (j == triangleColSize[i] - 1) {
				dp[i][j] = dp[i - 1][j - 1] + triangle[i][j];
				printf("dp[%d][%d] = %d\n", i, j, dp[i][j]);
			}
			else {
				if (dp[i - 1][j - 1] < dp[i - 1][j]) {
					dp[i][j] = dp[i - 1][j - 1] + triangle[i][j];
				}
				else {
					dp[i][j] = dp[i - 1][j] + triangle[i][j];
				}
				printf("dp[%d][%d] = %d\n", i, j, dp[i][j]);
			}
		}
	}
	min = dp[triangleSize - 1][0];
	printf("min = %d\n", min);
	printf("triangleColSize[triangleSize - 1] = %d", triangleColSize[triangleSize - 1]);
	for (j = 0; j < triangleColSize[triangleSize - 1]; j++) {
		if (dp[triangleSize - 1][j] < min) {
			min = dp[triangleSize - 1][j];
		}
	}
	return min;
}

最大子数组和 (LeetCode53)

题目描述

在这里插入图片描述

题目分析
  1. 分解问题:该问题可以把问题简化为前序结点和是否要加上当前结点
  2. dp数组:dp[i]表示由数组头到下标i这一段中,子数组最大和
  3. 递推公式:dp[i] = nums[i] + dp[i - 1]
  4. 边界条件:当新加的结点会使子数组和变小时,不把该结点值计入数组和内。
代码实现
int maxSubArray(int* nums, int numsSize) {
	if (numsSize == 0) {
		return nums[0];
	}
	int dp[10001] = { 0 };
	int i = 0;
	int max = nums[0];
	dp[0] = nums[0];
	for (i = 1; i < numsSize; i++) {
		if ((nums[i] + dp[i - 1]) < nums[i]) {
			dp[i] = nums[i];
		}
		else {
			dp[i] = nums[i] + dp[i - 1];
		}
		if (dp[i] > max) {
			max = dp[i];
		}
	}
	return max;
}

其他方式及题目详细分析移步另一篇博客:
LeetCode 53.最大子数组和——详解

最长回文子串 (LeetCode5)

题目描述

在这里插入图片描述

题目分析
  1. 分解问题:可以把i到j是否为回文串简化为[i-1]到[j+1]是否为回文串
  2. dp数组:dp[i][j]为0表示i与j之间不是回文串,为1表示i与j间是回文串
  3. 递推公式if (dp[i - 1][j + 1] == 1) dp[i][j] = 1;
  4. 边界条件:最小回文单元为aa或aba,即( i - j ) == 1或2 且 s[i] = s[j]
代码实现

char* longestPalindrome(char* s) {
	int i = 0, j = 0;
	int left = 0, right = 0;
	int dp[1001][1001] = { 0 };
	int len = strlen(s);
	int max_len = 0;
	for (i = 0; i < len; i++) {
		for (j = i; j >= 0; j--) {//因为数组的第一个元素下标为0,如果>0 在遍历时会忽略第一个字符
			if (s[i] == s[j]) {
				if ((i - j + 1) == 1 || (i - j + 1) == 2) {
					dp[i][j] = 1;
					if ((i - j + 1) > max_len) {
						max_len = i - j + 1;
						left = j;
						right = i;
					}
				}
				else if (dp[i - 1][j + 1] == 1) {
					dp[i][j] = 1;
					if ((i - j + 1) > max_len) {
						max_len = i - j + 1;
						left = j;
						right = i;
					}
				}
				/*这个if和else if的顺序 不能反,要先排除i = j = 0的情况,不然先判断【dp[i - 1][j + 1] == 1】
				的时候数组会越界*/
				
			}
		}
	}
	s[right + 1] = '\0';
	return s + left;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值