分享经典的动态规划问题(二)

摘要:进一步训练动规的基本套路.

1.正文:以前我没有经过自省和学习,一直有误以为动态规划跟我们高中数学课上学的线性规划有某种关联,后来通过科学的敲打后逐渐明白了两者就没啥必然关系。若强行说它们有什么联系,只能说公共点在于都是用于最优值求解上的。经典的动态规划,很适用于计算机的计算上,一是通过大量重复的迭代操作求得收敛值就是计算机最擅长的操作,二是思路本身就带有空间换时间的理念(线性表存储计算结果可以复用),三是内存空间往往可以自行控制到最优(可以自己程序申请的内存空间,大小自己可控制,不像递归等方法直接交由系统去做申请),以下通过几道经典例题进阶训练一下。

复习一下:

  (1)题意分析;

  (2)基于分析数学建模;

  (3)判定是否可以符合使用动规的两大前置条件(最优子结构和无后效性),是则下一步,否则终止(非动规可以解决的问题,另寻他法);

  (4)动规基本三步曲:

    1)结合题意根据模型选择计算出比较合适的状态转移方程,归约初始的状态值,推导出终止(最终收敛)条件;

    2)迭代验证;

    3)选择合适的迭代次序实现状态转移方程的迭代和收敛;

  (5)编程实现。

2.题目:

  子数组:对于给定数组a[0..n],其中a[i..j](0<=i<=j<=n)为该数组的子数组.

 1.最大子数组和问题:给定数组a[0..n]求其所有子数组中所有元素最大的子数组的元素和为多少?
  
2.最长公共子数组问题:给定数组a[0..n]和b[0..m],求两者公共子数组中长度最长的子数组的长度为多少?

 

3.输入输出示例:

1.最大子数组和问题

输入:

[-1, 2, 3, -2, 5]

输出:  

8

2.最长公共子数组问题:

输入:

[3, 2, 1, 4, 7]
[1, 2, 3, 2, 1]

输出:  3

 

4.例程: 


package algorithm.mathsolution;

/**
* @Project: taxet
* @Author: yh
* @Package: algorithm.mathsolution
* @Version: V1.0
* @Title: 分享经典的动态规划问题(二)
* @Tag:
* @Description:
* 经典的子数组类的动规问题
* 1.最大子数组和问题
* 2.最长公共子数组问题
* @Date: 2020/6/2 23:30
*/
public class SubArraySolution {

/**
* 1.最大子数组和问题
* 1)显然不需建模
* 2)根据题意分析所有情况得出最大值:
* 思路由浅入深:
* (1)显然一个数组的子数组a[i,j]由i,j决定的,即有subArray = f(i,j),最大值的直接定义为:max{sum(f(i,j))}
* (2)两个自变量显然要二重遍历,考虑降维,重定义迭代子变量,考虑到可以通过归类同一元素a[k]为末尾的子数组为一个子范围的最大值来得到整个数组的最大值,有:
* a. max[k] = max[k - 1] > 0 ? max[k - 1] + a[k] : a[k];
* b. max{sum(f(i,j))} = max{max[k]}
* 边界值推导:初始值显然有max[i] = a[i], 终止条件为一次正向遍历到最后的元素。
* 其中max[k]是以a[k]为末尾元素的子数组和的最大值,各个情况互斥的子范围得到的和最大值比较必然得到整个数组的最大子数组和。
* 注意的是,虽然得到两个子方程,但可以被认为状态转移方程的只有第一个,因为它才存在相邻状态(max[k],max[k - 1])发生转化的过程。
* 3)校验:[1,2,3] =>
* 根据方程a有max[0] = 1, max[1] = max[0] + a[1] = 3, max[2] = max[1] + a[2] = 6
* 根据方程b有max = 6;(本身也是一个子数组,符合预期结果)
* 4)编程实现。
* @param a
* @return
*/
private static long maxSumSubArraySum(int[] a) {
if (null == a) {
return -1;
}
long maxSum = 0L;
if (a.length == 0) {
return maxSum;
}
//构造被填的数组
long[] max = new long[a.length];
//赋初始值
for (int i = 0; i < a.length; i++) {
max[i] = a[i];
}
//迭代状态
maxSum = max[0];
for (int i = 1; i < max.length; i++) {
if (max[i - 1] > 0) {
max[i] += max[i - 1];
}
maxSum = Math.max(max[i], maxSum);
}
//返回结果
return maxSum;
}

/**
* 2.最长公共子数组问题
* 1)显然也不用建模
* 2)根据题意分析出也是一个有两个自变量,能不能像上面那样直接通过重定义降维?
* 答案是:不能的。
* 首先要认识到“最长”的概念当然可以应用在一个数据结构的某种特征数据,
* 可是“公共”就意味着至少是两个数据结构之间的特征数据了(当然本题只考虑两个的情况,三个及其以上都是与此类推),
* 当然在某些都可以将两个数据结构合并成一个以两者间的某种特征数据作为数组元素的数组再做处理,也是一种思路,但至少这道题里不允许,
* 因为子数组的定义与原有数组的强关联,一旦不在原有的数据结构操作,寻找子数组将会是一个难题。
* 综述,不能降维,否则成本会很大。
* 既然不降维,则显然是二维的状态转移方程的常规寻找了。
* 继续类似上题那样定义max[i][j]:a[0 .. i]与b[0 .. j]的各自以a[i],b[j]为末尾元素的子数组之间的最长公共子数组长度。
* max[i][j]的迭代子可以有(max[i][j - 1], max[i - 1][j], max[i - 1][j - 1]),
* 可是根据题意“公共”的约束和max[i][j]的定义,max[i][j - 1],max[i - 1][j]没有考虑意义,只与max[i - 1][j - 1],有方程:
* max[i][j] = a[i] == b[j] ? max[i - 1][j - 1] + 1 : 0;
* 3)校验:
* 输入:
* A: [1,2,3,2,1]
* B: [3,2,1,4,7]
* 输出:
* 3
* 抽样,显然max[3][1] = 2;
* max[4][2] = 3 = max[3][1] + 1
* 4)编程实现
* @param a
* @param b
* @return
*/
private static int maxLenCommonSubArray(int[] a, int[] b) {
if (null == a || null == b) {
return -1;
}
int maxLen = 0;
if ((a.length | b.length) == 0) {
return maxLen;
}
int[][] max = new int[a.length][b.length];
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < b.length; j++) {
if (a[i] == b[j]) {
max[i][j] = 1;
if (i > 0 && j > 0) {
max[i][j] += max[i - 1][j - 1];
}
}
maxLen = Math.max(maxLen, max[i][j]);
}
}
return maxLen;
}

public static void mainSum(String[] strings) {
int[] arr = {-1, 2, 3, -2, 5};
System.out.println(maxSumSubArraySum(arr));
}

public static void mainLen(String[] strings) {
int[] a = {1, 2, 3, 2, 1};
int[] b = {3, 2, 1, 4, 7};
System.out.println(maxLenCommonSubArray(a,b));
}
}

该题帮你建好模了,经典的例题,比较简单,不再赘述,不懂的地方请认真看看注释能否释疑,有问题欢迎留言。

5.总结:

 事实上,真正刷题的时候更应该的是发散思维去考虑更多的好解法而非只有动规的思路的,但博主希望近期就先把由浅至深的动规题都大致捋一遍再展开新方法的讨论,所以对动规部分有问题可以尽早提出来。当然,要经过自己的思考再发问,之后哪怕别人也不知道或者其他情况获取不到有效答案的,你也要自己开拓新的渠道寻找答案(博主常规解决方案),切忌自己主动放弃求知欲,不了了之。

posted @ 2020-06-05 01:42  StrongKit  阅读(237)  评论(0编辑  收藏  举报