动态规划属于不好理解的计算机基本算法之一。
需要经过多次实践,才能体会其精妙之处。
其精妙的地方在于:降低运算量。
下面通过实例理解动态规划解题思路。
实例一:求数组的最大连续和子数组。参考文章
用动态规划来解,首先得考虑状态和状态转移方程。如果我们把题述数组看成序列,那么是不是可以用序列DP来考虑呢?
我们不妨考虑一个这样的序列:1,-3,5,-2,4
a[i]表示这个序列的第 i 个元素,dp[i]表示最后一个元素是a[i]的最大连续和(此乃状态,是不是跟LIS的DP解法有点类似),于是:
dp[0] : a[0] ; ( 1 )
dp[1] : max(dp[0] + a[1] , a[1]) ; ( -2 )
dp[2] : max(dp[1] + a[2] , a[2]) ; ( 5 )
dp[3] : max(dp[2] + a[3] , a[3]) ; ( 3 )
dp[4] : max(dp[3] + a[4] , a[4]) ; ( 7 )
所以:ans = 7 (dp数组的最大值)
于是,我们可以得到状态转移方程:dp[i+1] = max(dp[i]+a[i+1] , a[i+1])
写成代码的话,我们可以忽略掉dp数组,直接用一个变量sum来记录 i 之前的最大增量(因为如果这个增量为负,则变为0)
java源码如下:动态规划求解复杂度为o(n)
//动态规划方法dp
public int maxSubArray3(int[] array) {
int sum = 0;
int ret = Integer.MIN_VALUE;
for (int i = 0; i < array.length; i++) {
sum += array[i];
if (sum > ret) {
ret = sum;
}
if (sum < 0) {
sum = 0;
}
}
return ret;
}
public static void main(String[] ars) {
int[] a = { -2, 1, -3, 4, -1, 2, 1, -5, 4};
MaxSubArray array = new MaxSubArray();
int max = array.maxSubArray3(a);
System.out.println(max);
}
注意:状态转移方程没有在代码中提现出来。
求子数组最大和问题,也可以直接遍历求解。复杂度为o(n^2)
java代码如下:
//直接遍历求解
public int maxSubArray4(int[] array) {
int max = Integer.MIN_VALUE;
int sum = 0;
for (int i = 0; i < array.length; i++) {
sum = 0;
for (int j = i; j < array.length; j++) {
sum += array[j];
if (sum > max) {
max = sum;
}
}
}
return max;
}
实例二:数组分割成两个数组,且两子数组和的差最小。
动态规划解题思路:
1、原数组所有元素之和为sum.
2、子数组和的差最小,则子数组和尽量接近sum/2.
3、问题转化为,求原数组中的子数组,子数组之和尽量接近sum/2,最好等于sum/2.这样差就会为0。
4、转化为01背包问题:sum/2为背包容量。
状态转移方程为(参考文章2):背包容量是SUM/2. 每个物体的体积是数的大小,然后尽可能的装满背包。
dp方程:f[i][V] = max(f[i-1][V-v[i]]+v[i], f[i-1][V] )
f[i][V]表示用前i个物体装容量为V的背包能够装下的最大值,f[i-1][V-v[i]]+v[i]表示第i个物体装进背包的情况,f[i-1][V]表示第i件物品不装进背包的情况。
java源码如下:
package test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class SplitArray {
static int[][] result = new int[20][20];
static int split(int[] array) {
int sum = 0;
int len = array.length;
for (int i = 1; i < len; i++){
sum += array[i];
}
sum >>= 1;
//动态规划
for (int i = 1; i < len; i++) { //循环数组
for (int j = 0; j <= sum; j++) { //背包容量
if (j >= array[i]) { //背包容量能放下array[i]
result[i][j] = Math.max(result[i - 1][j], result[i - 1][j - array[i]] + array[i]);
} else //不能放下array[i]
result[i][j] = result[i - 1][j];
}
}
//查找出被
int j = sum;
List<Integer> listA = new ArrayList<Integer>();
List<Integer> listB = new ArrayList<Integer>();
for (int i = len - 1; i > 0; i--) {
if (result[i][j] > result[i - 1][j]) { //找出被选中的元素
listA.add(new Integer(array[i]));
j -= array[i];
} else {
listB.add(new Integer(array[i]));
}
}
//显示划分结果
Object[] ret = listA.toArray();
System.out.println(Arrays.toString(ret));
ret = listB.toArray();
System.out.println(Arrays.toString(ret));
//给出差值
int sum1 = 0;
int sum2 = 0;
for (Integer integer : listA) {
sum1 += integer;
}
for (Integer integer : listB) {
sum2 += integer;
}
int diff = Math.abs(sum1 - sum2);
return diff;
}
public static void main(String args[]) {
int[] array = { 0, 3, 5, 2, 1, 4 };
int diff = split(array);
System.out.println("the diff is: " + diff);
}
}
运行结果如下:
[2, 5]
[4, 1, 3]
the diff is: 1
实例三:经典背包问题温习(文考文章3)
01背包问题的动态规划转移方程为:
f[i][v] = max{ f[i-1][v] , f[i-1][v - c[i]] + v[i]}
参考文章
1、http://www.mamicode.com/info-detail-511949.html
2、http://www.tuicool.com/articles/ZF73Af
3、http://www.cnblogs.com/bourbon/archive/2011/08/23/2151044.html