动态规划java_动态规划入门1

漫画:什么是动态规划? (写得很不错,推荐看)

动态规划 dynamic programming 通俗称为dp

核心思想1:大事化小,小事化了。

核心思想2:记住原来计算过的问题。(也就是重叠子问题,加快计算速度)

最快理解动态规划的例子:

斐波那契数列 0,1,1,2,3,5,8....

规律就是F(n)=F(n-1)+F(n-2)

最初始的边界为F(0)=0,F(1)=1;

我们第一时间能想到的应该是这个计算方法:

public static int fibSum(int n) {

if (n == 0 || n == 1) {

return n;

} else {

return fibSum(n - 1) + fibSum(n - 2);

}

}图片来自上面小灰的链接,侵权立删

然后我们来看这中计算方法的时间复杂度,这个计算过程就是一颗二叉树,它的深度为n-1,由二叉树的性质可知它有接近2的n-1次幂个节点。而每个节点就是调用一次这个方法,所以时间复杂度可判定为2的n次方。

然后我们看到这个计算过程中有大量重复计算过程,左子树的子树里有右子树,所以就进行了大量的重复计算。所以这个时候我们就知道利用key的唯一性使用hashmap进行存储,

private static final HashMap hm = new HashMap();

private static int fibSum(int n) {

if (n == 0 || n == 1) {

return n;

} else if (hm.containsKey(n)) {

return hm.get(n);

} else {

int temp = fibSum(n - 1) + fibSum(n - 2);

hm.put(n, temp);

return temp;

}

}

这个时候我们可以利用java的System.currentTimeMillis();计算这个方法运行所花的时间,比较前面两种写法的差异,时间减少了很多。然后我们来分析一下它的时间复杂度:由于hashmap当中已经把f(n-1)以下的都记录下来,所以都是直接取,画个二叉树就知道它的时间复杂度为O(n)。

然后,我们由前面这个例子来分析这个动态规划的模型:

三个重要概念:1.最优子结构 2.边界 3.状态转移公式

在前面的例子中 边界就是F(0)=0,F(1)=1,也就是递归出来的条件

状态转移方程式是F(n)=F(n-1)+F(n-2),也就是递归表达式。

最优子结构就是比如 F(8)=F(7)+F(6),这个例子其实不太好理解这个最优子结构,你可以理解为最优的解的意思,在背包问题中理解这个最优子结构是最容易的。

然后我们来看真正的动态规划是怎么实现的:

private static int fibSum(int n) {

if (n == 0 || n == 1) {

return n;

}

int a = 0, b = 1, temp = 0;

for (int i = 2; i < n; i++) {

temp = a + b;

a = b;

b = temp;

}

return a + b;

}

这里没有使用递归,而是使用迭代的方法实现。关键在于那个循环。这种方法时间复杂度依然为O(n),而空间复杂度变为了O(1),前面使用hashmap的空间复杂度为O(n).。这种方法只保存了虽然计算了所有的状态,但是只计算值的时候只使用了前面的两个状态,所以它只保存了前面的两个状态,然后迭代下去。用小灰的说法就是自底而上。我看过《算法图解》里对背包问题的分析,所以很容易理解这种思想。

这就是动态规划,实现了时间和空间上的最优。

这里的动态规划是有两种形式,一种是前面说的自底而上的模式,一种是我们说的第二种:用hashmap存储已经计算过的表达式,他们说是自顶而下的备忘录模式。为了避免误解,我觉得把两种观点都写出来比较好,反正都是一种思路,学会了就好。

欢迎交流和讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值