今天学习了动态规划算法,整理了一些大佬的笔记。就决定写一个博客,用来给想学习动态规划算法的同学,带大家了解一下什么是动态规划。
动态规划(Dynamic Programming)(以下简称DP)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。通过把原问题分解位相对简单的问题的方式来解决复杂的问题。
动态规划不是一种具体的算法,而是一种思想:若要解一个给定问题,我们需要解其子问题,再根据子问题得出原问题的解。
可能有的人说这和分治好像,区别就在于分治算法的求解不依赖于子问题,DP是依赖于子问题的求解。
规定的是原问题与子问题之间的关系
DP要解决的都是一些问题的最优解,即从很多解决问题的方案中找出最优的那一个。当我们求解一个问题的最优解的时候,我们将其分为子问题,转换位求解子问题的最优解,最后通过对子问题的最优解进行组合来得到最终的结果。用的来说就是一个问题的最优解是由它的各个子问题的最优解来决定的。
规定的是子问题与子问题的关系
在我们递归的寻找每个子问题的最优解的时候,有可能会出现一些重复的子问题,如果只是普通的递归,则可能出现很多重复的计算,而DP可以保证每个重叠的子问题只会被求解一次。当重复的问题很多的时候,DP可以减少很多重复的计算。
例如:斐波那契问题:求f(5)
f(5) / \ f(4) f(3) / \ / \ f(3) f(2)f(2) f(1) / \ f(2) f(1)
f(3) 是重复计算的,对于DP来说我们可以建表存储子问题的答案,以避免重复计算
力扣第300题最长递增子序列
给你一个整数数组nums,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
思路:
每次减少一个:记 f(n)f(n) 为以第 nn 个数结尾的最长子序列,每次减少一个,将原问题分为 f(n-1)f(n−1), f(n-2)f(n−2), …, f(1)f(1),共 n - 1n−1 个子问题。n - 1 = 7n−1=7 个子问题以及答案如下:
[10, 9, 2, 5, 3, 7, 101] -> [2, 5, 7, 101]
[10, 9, 2, 5, 3, 7] -> [2, 5, 7]
[10, 9, 2, 5, 3] -> [2, 3]
[10, 9, 2, 5] -> [2, 5]
[10, 9, 2] -> [2]
[10, 9] -> [9]
[10] -> [10]
已经有 7 个子问题的最优解之后,可以发现一种组合方式得到原问题的最优解:f(6)f(6) 的结果 [2,5,7], 7 < 187<18,同时长度也是f(1)~f(7) 中,结尾小于 18 的结果中最长的。f(7) 虽然长度为 4 比f(6) 长,但结尾是不小于 18 的,无法组合成原问题的解。最后的结果取最长序列的个数。
以上组合方式可以写成一个式子,即状态转移方程: [10, 9, 2, 5, 3, 7, 101] -> [2, 5, 7, 101] 从这里我们可以看出->右边的序列是如何得到得呢? DP可以通过填表的方式来逐步推进,从而得到最优解 对于递归求解,我也不会,哈哈哈哈
f(n)=maxf(i)+1 其中 i动态规划求解
[10, 9, 2, 5, 3, 7] -> [2, 5, 7]
[10, 9, 2, 5, 3] -> [2, 3]
[10, 9, 2, 5] -> [2, 5]
[10, 9, 2] -> [2]
[10, 9] -> [9]
[10] -> [10]
我们从下往上看
public static int lengthOfLIS(int[] nums) {
if (nums.length==0){
return 0;
}
//定义一个等长的数组 来存储序列长度
int[] arr = new int[nums.length];
arr[0] = 1;
//最短的序列长度为1
int ans = 1;
//用来计算子序列长度
for (int i = 1;i<nums.length;i++){
//最短就是自身
arr[i] = 1;
//寻找前面比i小的数
for (int j =0;j<i;j++){
//如果找到比当前值小的值 则+1就是当 i 之前的最长序列
if (nums[j]<nums[i]){
//比较所有比他小的 找到最长的
arr[i] = Math.max(arr[j]+1,arr[i]);
}
}
//当退出for循环时 说明已经找到包含nuns[i]的最长序列了 更新ans
ans = Math.max(ans,arr[i]);
}
//返回结果即可
return ans;
}