赞
踩
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
动态规划的 【穷举】 中,会产生很多很多重复计算,所以,我们可能需要一个“备忘录”保存重复计算的结果,同时,我们需要一个DP table来优化穷举的过程,记录子问题的结果,相关内容在递归算法篇章的斐波那契数列中见到过。
动态规划的三要素如下:
下面我们对经典的动态规划问题进行介绍~
题目: 给定一个无序的序列,求解它的【最长递增子序列】的长度。方法签名:int lengthOfLIS(int[] nums)
。
注意:【子序列】和【子串】是不一样的,【子序列】是可以不连续的,但是子串必须是连续的。
举例: nums[] = {3,·1,4,1,5,9,2,6,5}
的最长递增子序列长度为4,结果返回4即可,此时的为子序列:1,4,5,9。
在使用【动态规划】方案解决该问题的时候,我们需要首先思考的是,怎么去设计一个dp数组,用来存放各个子问题的结果。
在这道题中,我们想:
基于以上考虑,我们需要抽离一个共有的方法,就是【查找子序列】。
代码如下:
public class LengthOfLIS { public static void main(String[] args) { int i = lengthOfLIS(new int[]{1, 4, 2, 4, 6, 7, 8,4,23}); System.out.println(i); } public static int lengthOfLIS(int[] nums){ if(nums.length <= 0){ return 0; } int[] dp = new int[nums.length]; // 对给定数组进行逐一遍历,确定每个位置上的最长递增子序列 for (int i = 0; i < nums.length; i++) { // 先给dp数据当前位置初始化,因为最短的长度就是1 dp[i] = 1; // 确定当前的长度时,需要对前边已经计算的结果进行扫描 for (int j = 0; j < i; j++){ // 如果遇到比前边某一个位置的数字大,说明在之前的位置上,递增序列会被延长 if(nums[i] > nums[j]){ // 同时比较当前位置和最大子序列的长度,目的是取最大值 if(dp[i] < dp[j] +1){ dp[i] = dp[j] + 1; } } } } Arrays.sort(dp); return dp[dp.length-1]; } }
题目: 给你 k 种面值的硬币,面值分别为 c1, c2 … ck,每种硬币的数量无限,再给一个总金额 amount,问你最少需要几枚硬币凑出这个金额,如果不可能凑出,算法返回 -1 。算法的函数签名:int coinChange(int[] coins, int amount);
(coins 中是可选硬币面值,amount 是目标金额)。
比如说 k = 3,面值分别为 1,2,5,总金额 amount = 11。那么最少需要 3 枚硬币凑出,即 11 = 5 + 5 + 1。
你认为计算机应该如何解决这个问题?显然,就是把所有肯能的凑硬币方法都穷举出来,然后找找看最少需要多少枚硬币。
首先,这个问题是【动态规划问题】,因为它具有【最优子结构】的。要符合「最优子结构」,子问题间必须互相独立。啥叫相互独立?
回到凑零钱问题,为什么说它符合最优子结构呢?
我们想知道总金额11需要最少几枚硬币,只需知道总结额为 10,9,6(总额减去一枚硬币面额的值)这几种情况下所需要的硬币的数量,然后找一个小的加1即可,这个过程是可以进行递归处理的,如下图:
重叠子问题: 当总额为0,1…amount时,分别最少需要多少枚硬币,因为当amount=0,时结果必为0。
符合最优子结构: 子问题amount所需硬币的最小值就是我们要的结果。
状态转移方程:
f
(
n
)
=
{
0
,
n
=
0
−
1
,
n
<
0
m
i
n
(
f
(
n
−
c
o
i
n
)
)
1
,
n
0
f(n)=
f(n)={0,n=0 −1,n<0min(f(n−coin))+1,n>0
代码如下:
/\*\* \* 计算出能组成总金额的最少硬币数量 \* @param coins 给定的硬币的面额 \* @param amount 给定的总金额 \* @return 最少的硬币数量 \*/ public static int coinChange(int[] coins, int amount){ if(amount == 0) return 0; if(amount < 0) return -1; // 核心: // 1、求总金额为16的结果 【1,3,5】 // 2、【1】找到15的最优解+1 ---- 【3】找到13的最优解+1 ----- 【5】找到11的最优解+1 // 3、取最小值 int result = Integer.MAX\_VALUE; for (int i = 0; i < coins.length; i++) { int subMin = coinChange(coins, amount - coins[i]); // 如果最优解不存在 -1 继续 if (subMin == -1) continue; if(subMin + 1 < result){ result = subMin +1; } } return result == Integer.MAX\_VALUE ? -1 : result; }
在此过程中,我们确实会出现很多的重复子问题计算,我们需要使用一个memo备忘录进行记录。
private static int changeCoin2(int[] coins,int amount,int[] memo){ if (amount == 0) return 0; if (amount < 0) return -1; int res = Integer.MAX\_VALUE; for (int i = 0; i < coins.length; i++) { // 遍历子问题,假设amount为10元,如果有了一个2元,剩下的8元最少需要几个呢?,子问题就是8元所需要的个数 // 同理8元所需要的个数可以使用递归完成 int subProblem = Integer.MAX\_VALUE; if(amount-coins[i] >= 0 && memo[amount-coins[i]] != 0){ subProblem = memo[amount-coins[i]]; } else { subProblem = changeCoin2(coins,amount-coins[i],memo); } // 子问题有误解的时候,比如子问题的金额小于硬币的最小金额 if(subProblem == -1) continue; // 我们的最优结果就是,最优的子问题的最优解+1 res = Math.min(res,1 + subProblem) != Integer.MAX\_VALUE ? Math.min(res,1 + subProblem):-1; } memo[amount] = res; return res; }
最后比较一下有【备忘录】和没有【备忘录】的性能差异:
public class Change { ... public static void main(String[] args) { long start = System.currentTimeMillis(); System.out.println(changeCoin2(new int[]{1,2,3},100,new int[100+1])); long end = System.currentTimeMillis(); System.out.println(end - start); start = System.currentTimeMillis(); System.out.println(changeCoin(new int[]{1,2,3},100)); end = System.currentTimeMillis(); System.out.println(end -start); } }
题目: 有一个容量为 V 的背包,和一些物品。这些物品分别有两个属性,体积 w 和价值 v,每种物品只有一个。要求用这个背包装下价值尽可能多的物品,求该最大价值,背包可以不被装满。
0-1背包问题: 在最优解中,每个物品只有两种可能的情况,即在背包中或者不在背包中(背包中的该物品数为0或1),因此称为0-1背包问题。
子问题: 子问题必然是和物品有关的,对于每一个物品,有两种结果:能装下或者不能装下。
因此,子问题确定为背包容量为j时,求前i个物品所能达到最大价值。
确定状态: 由上述分析,“状态”对应的“值”即为背包容量为j时,求前i个物品所能达到最大价值,设为dp[i][j]
。初始时,dp[0][0]
为0,没有物品也就没有价值。
确定状态转移方程: 由上述分析,第i个物品的体积为w,价值为v,则状态转移方程为
f
(
n
,
v
)
=
{
f
(
n
−
1
,
v
)
,
w
e
i
g
h
t
[
i
]
j
(
装
不
下
)
M
a
t
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
h
t
[
i
]
j
(
装
不
下
)
M
a
t
[外链图片转存中…(img-WIgyNqFc-1715817105201)]
[外链图片转存中…(img-O3vZMC0e-1715817105202)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。