【初窥算法】初识动态规划(Dynamic programming)

前言

动态规划Dynamic Programming,简称DP)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。这种方法通常用于求解最优化问题,特别是当问题可以分解为重叠的子问题时,动态规划尤为有效。但是作为初学者来说,理解动态规划并不是那么容易。本文,让我们一起来探索动态规划算法。

梦开始的地方

首先,让我们回到梦开始的地方——509. 斐波那契数。该题目,是所有动态规划算法中必提的一个问题,因为它足够的简单,可以让我们不受题意干扰,专注于动态规划本身。

斐波那契数列第一项为 0 ,第二项为 1 ,第三项为 1 ,第四项为 2 ,第五项为 3 … 以此类推,之后的每一项都是前两项之和。所以可以得到递推公式为:F(n) = F(n-1) + F(n-2)。

🙋那么我们如何求出斐波那契数列第 n 项呢?

这个问题可以有以下几种解法:

(1)递归

class Solution {
    public int fib(int n) {
        if (n == 0)
            return 0;
        if (n == 1)
            return 1;
        return fib(n - 1) + fib(n - 2);
    }
}

进一步,我们来分析该解法的执行过程,以 n = 5 为例。
fib(5)
将该解法提交到力扣后,会发现时间复杂度很低
在这里插入图片描述

为什么会这么低呢?进一步分析可以发现在计算 fib(5) 时, fib(2) 被重复调用 3 次, fib(3) 被重复调用两次。 当 n 逐渐变大,重复计算会越来越多,进而使得时间复杂度很低,时间复杂度为 O ( 2 N ) O(2^N) O(2N)
分析时间复杂度
(2)自顶而下的动态规划
该解法使用一 map 去存储已经计算出来的 F(n) ,key 为 i 表示 F(i) 。每次在计算之前,会先检查 map 中是否有值。如果有,则直接取值;如果没有,则使用递推公式进行计算。

class Solution {
    HashMap<Integer,Integer> map = new HashMap<>();
    public int fib(int n) {
        if (n == 0)
            return 0;
        if (n == 1)
            return 1;
        // 判断当前n的结果是否已经被计算,如果map存在n则代表该结果已经计算过了
        if (map.containsKey(n))
            return map.get(n);
        int value = fib(n - 1) + fib(n - 2);
        map.put(n, value);
        return value;
    }
}

本解法其实是自顶而下的动态规划——记忆化搜索。在该例子中,问题的规模是逐级递减的。

(3)自底向上的动态规划
既然有自顶向下的动态规划,那想必也有自底向上的动态规划。自底向上指的是根据一个已知小问题的答案一步一步地去解出一个大问题。
代入斐波那契问题中,我们已知 f(1),f(2) 的值,这个相当于小问题的答案。而我们要求的 f(n) 的值可以视为需要被求解的大问题。两者之间的关系,就是数列递推公式。因此,我们可以设置一个数组,数组中已知 f(0) = 0,f(1) = 1,然后一步一步去求 f(2)、f(3)…直至求出数组中第 n 项的值,即我们要求的大问题的答案。
在上述问题中,我们引入了几个概念:

  • 状态:用于描述问题的当前状态,它通常是一个或多个变量的组合,这些变量能够完整地表示问题的当前进展。在斐波那契问题中是 f(i)。
  • 状态转移:用于描述了从一个状态转移到另一个状态的方式,以及转移过程中可能产生的最优解。在斐波那契问题中是根据 f(n-1) 和 f(n-2) 求出 f(n) 的过程。f(n)=f(n-1)+f(n-2) 为状态转移方程。

本解法的时间复杂度为 O ( n ) O(n) O(n),空间复杂度也为 O ( n ) O(n) O(n)
🙋那么我们真的需要这个数组嘛?
数组在斐波那契问题中,可以是被替换的。可以使用三个变量也可以达到效果。

class Solution {
    HashMap<Integer,Integer> map = new HashMap<>();
    public int fib(int n) {
        if (n == 0)
            return 0;
        if (n == 1)
            return 1;
        // 使用变量a,b来保存上次迭代和上上次迭代的结果
        int a = 0;
        int b = 1;
        int temp = 0;
        for (int i = 2; i <= n; i++) {
            temp = a + b;
            a = b;
            b = temp;
        }
        return temp;
    }
}

使用该方法,就可以将空间复杂度优化至 O ( 1 ) O(1) O(1)

求解一般步骤

(1)分析题目,定义 dp 数组并且确定 dp[i] 的含义。
(2)找到递推公式
(3)dp 数组初始化
(4)确定遍历顺序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值