学习总结录 学习总结录
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
  • 设计模式

  • 算法思想

    • 前缀和数组
    • 差分数组
    • 原地修改数组
    • 二分搜索
    • 动态规划
      • 动态规划套路
      • 斐波那契数
      • 零钱兑换
      • 参考
    • 回溯算法套路
    • BFS算法套路
    • 双指针技巧套路-链表
    • 双指针技巧套路-数组
    • 滑动窗口
  • 编码规范

  • 技术思想
  • 算法思想
旭日
2023-03-27
目录

动态规划

# 动态规划套路

509. 斐波那契数 (opens new window)

322. 零钱兑换 (opens new window)

动态规划这类问题一般形式就是求最值。而求最值的核心问题就是穷举。因为只要知道所有的情况,然后在其中找到最值即可。

只要是最值就要想着最值的问题。

动态规划这类问题存在三大要素:

  • 重叠子问题
  • 最优子结构
  • 正确的'状态转移方程'

# 斐波那契数

题目:

image-20220306125002799

思想:

这题是一道比较简单又比较经典的问题,更是一个递归的好应用:

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

但是这种递归效率特别低,比如当求N=20,则需求N=18,N=19,而当N=21,则需求N=20,N=19,可以看到N=19,被多次计算,这就是所谓的重叠子问题

优化:

重叠子问题:穷举一般来说效率比较低下,比如要从100个数中找到最小的数字,一个一个比较,就有太多不必要的计算了,所以重叠子问题主要就是避免不必要的计算。

既然存在太多不必要的计算,我们优化的方式当然就是避免这些不必要的计算。

优化1-备忘录:

我们每次记录下某个子问题的答案,当遇到这个子问题的时候就去备忘录中查询并返回,如果没有这个子问题,就求出这个子问题并记录。

class Solution {
    public int fib(int n) {
        if(n == 0) return 0;
        int[] nums = new int[n + 1]; 
        return helper(nums, n);
    }
    int helper(int[] nums, int n) {
        if (n == 1 || n == 2) return 1;
        if(nums[n] != 0) {
            return nums[n];
        }
        nums[n] = helper(nums, n - 1) + helper(nums, n - 2);
        return nums[n];
    }
}

优化2-dp

备忘录的方法实质上还是递归,这是我们避免了在递归中计算已经计算过的子问题。

优化1是自顶向下的,现在我们来实现自底向上。

class Solution {
    public int fib(int n) {
        if(n == 0) return 0;
        if(n == 1 || n == 2) return 1;
        int[] nums = new int[n + 1];
        nums[1] = nums[2] = 1;
        for(int i = 3; i <= n; i++) {
            nums[i] = nums[i - 1] + nums[i - 2];
        }
        return nums[n];
    }
}

通过自底向上的实现,大致能总结出一个规律:

image-20220306133229470

而这个就是我们的状态转移方程。

套路:

  • 使用暴力解法得到状态转移方程
  • 利用备忘录和DP table优化暴力解法

最终代码:

class Solution {
    public int fib(int n) {
        if (n == 0) {
            return 0;
        }
        if (n == 2 || n == 1) {
            return 1;
        } 
        int pre = 1, next = 1;
        for(int i = 3; i <= n; i++) {
            int sum = pre + next;
            pre = next;
            next = sum;
        }
        return next;
    }
}

# 零钱兑换

题目:

image-20220306150744423

思想:

image-20220306151729152

代码:

class Solution {
    int[] memo;

    public int coinChange(int[] coins, int amount) {
        memo = new int[amount + 1];
        // dp 数组全都初始化为特殊值,为特殊值说明此刻拼凑amount数量的问题还没有解
        Arrays.fill(memo, -10);
        return dp(coins, amount);
    }

    int dp(int[] coins, int amount) {
        if (amount == 0) return 0;
        if (amount < 0) return -1;
        // 防止重复计算
        if (memo[amount] != -10)
            return memo[amount];
        int res = Integer.MAX_VALUE;
        for (int coin : coins) {
            // 计算子问题的结果,减去coin,相当于少用一枚硬币
            int subProblem = dp(coins, amount - coin);
            // 子问题无解则跳过
            if (subProblem == -1) continue;
            // 在子问题中选择最优解,然后加一
            res = Math.min(res, subProblem + 1);
        }
        // 把计算结果存入备忘录
        memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res;
        return memo[amount];
    }
}

# 参考

动态规划解题套路框架 (opens new window)

上次更新: 2024/06/29, 15:13:44
二分搜索
回溯算法套路

← 二分搜索 回溯算法套路→

最近更新
01
基础概念
10-31
02
Pytorch
10-30
03
Numpy
10-30
更多文章>
Theme by Vdoing | Copyright © 2021-2024 旭日 | 蜀ICP备2021000788号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式