动态规划

2023-02-12  本文已影响0人  bowen_wu

概述

Fibonacci

求 Fibonacci 第 n 项

递归

缺点:存在重复计算

public class Fibonacci {
  // 时间复杂度:O(1.618^n)
  public int fibonacci(int n) {
    if (n < 2) {
      return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

解决重复计算

自顶向下 DP

import java.util.Arrays;

public class Fibonacci {
  // 时间复杂度:O(n)
  public int fibonacci(int n) {
    if (n < 2) {
      return n;
    }
    int[] memo = new int[n + 1];
    Arrays.fill(memo, -1);
    memo[0] = 0;
    memo[1] = 1;

    return fibonacci(n, memo);
  }

  private int fibonacci(int n, int[] memo) {
    if (memo[n] != -1) {
      return memo[n];
    }
    int result = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
    memo[n] = result;
    return result;
  }
}

自底向上 DP

public class Fibonacci {
  // 时间复杂度:O(n)
  public int fibonacci(int n) {
    if (n < 2) {
      return n;
    }
    int[] memo = new int[n + 1];
    memo[0] = 0;
    memo[1] = 1;

    for (int i = 2; i <= n; i++) {
      memo[i] = memo[i - 1] + memo[i - 2];
    }

    return memo[n];
  }
}

滚动数组

public class Fibonacci {
  // 时间复杂度:O(n)
  public int fibonacci(int n) {
    if (n < 2) {
      return n;
    }
    int associationNumber = 2;
    int[] memo = new int[associationNumber];
    memo[1] = 1;

    for (int i = 2; i <= n; i++) {
      memo[i % associationNumber] = memo[(i - 1) % associationNumber] + memo[(i - 2) % associationNumber];
    }

    return memo[n % associationNumber];
  }
}

动态规划

设计 DP

  1. 刻画一个最优解的结构特征 => 最优子结构性质 => 问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解
  2. 递归地定义最优解的值 => 状态转移方程
  3. 计算最优解的值,通常采用自底向上的方法
  4. 利用计算出的信息构造一个最优解

设计 DP 模板

  1. 定义状态(State) => 刻画一个最优解的结构特征
  2. 定义状态转移方程(Function) => 状态之间的联系与状态转移 => 核心难点
  3. 初始条件与边界条件(Condition) => 基本条件是什么?最小状态是什么?
  4. 最优解求解(Solution)

Fibonacci

  1. state => f(n) 表示斐波那契数列的第 n 项
  2. status function => f(n) = f(n - 1) + f(n -2) => n >= 2
  3. condition => f(0) = 0,f(1) = 1
  4. solution => 求 f(n)

应用场景

通常用于最优化问题

  1. 求方案总数
  2. 方案可行性
  3. 求最优化解
  4. DP 不适合输出所有可行方案的题目

适合 DP 求解的条件

  1. 最优子结构性质 => 一个问题的最优解包含其子问题的最优解
  2. 子问题的重叠性 => 问题的递归算法会反复求解相同的子问题,而不是一直生成新的子问题
  3. 无后效性 => 对于一个确定的状态,不必关心这个状态是怎么出现的,也不必考虑这个状态的前一个状态是什么 => case: fibonacci,f(n) 是一个确定的状态,不需要关心第 n 项是怎么来的,也不考虑 f(n - 1) 的状态。但是不关心不代表不依赖

DP vs 贪心

子问题图

结论

  1. 子问题图 G = (V, E) 规模可确定 DP 的时间复杂度,算法运行时间通常与顶点和边的数量呈线性关系
  2. 自底向上 DP 按逆拓扑序列处理子问题顶点
  3. 自顶向下 DP 按照 DFS 顺序处理子问题顶点

单序列 DP

斐波那契系列

打家劫舍系列

最长上升子序列(LIS)系列

最大子数组和系列

带维度(状态)的单序列

双序列 DP

二维矩阵 DP - 无额外状态系列

背包 DP - Knapsack DP

01 背包

空间优化

  1. 滚动数组 => 两个数组
  2. 一个数组 => 一维问题 => 二维数组空间优化到两个数组,之后继续空间优化到一个数组
    • state => dp[j] 占用了 j 空间能获得的最大价值
    • status function => dp[j] = max(dp[j], dp[j - v[i]] + w[i]) => 需要从后往前计算(j => V -> 0)
    • condition => 无
    • solution => max(dp[i])

完全背包

总结

  1. 物品只能取一次 => 01背包
  2. 物品能够无限次取 => 完全背包
  3. 顺序不同的序列是相同的组合 => 先遍历硬币
  4. 顺序不同的序列是不同的组合 => 先遍历金额

多重背包

知识点

  1. DP 定位技巧
  2. 矩形 => 使用左上角坐标和右下角坐标
  3. 正方形 => 使用右下角坐标
上一篇 下一篇

猜你喜欢

热点阅读