动态规划 之 斐波那契数列模型 算法专题

动态规划

分析:(五步)

  1. 状态表示

状态表示是什么?

dp表里面的值锁表示的含义

状态表示怎么来的?

  • 题目要求
  • 经验 + 题目要求
  • 线性状态表经验: 以i位置为结尾 以i位置为起点
  • 分析问题的过程中, 发现重复子问题
  1. 状态转移方程
    dpi 等于什么(一个公式)
    用之前或者之后的状态, 推导出dpi的值
  2. 初始化
    保证填表的时候不越界
  3. 填表顺序
    为了填写当前状态的时候, 所需要的状态已经计算过了
  4. 返回值
    题目要求 + 状态表示

编写代码: (四步)

  1. 创建dp表
  2. 初始化
  3. 填表
  4. 确定返回值

优化: 处理边界问题以及初始化问题的技巧

如果初始化和填表的逻辑类似, 我们可以把初始化塞到填表逻辑中

做法:

dp多开一个空间, 将之前的dp表整体向后移动, 前面的一个位置称为虚拟节点

注意:

1.虚拟结点里面的值, 要保证是正确的 一般情况下设置为0, 但还是要具体分析

2.下标的映射关系

一.第n个泰波那契数

第n个泰波那契数

  1. 状态表示
    dp表 表示第n个泰波那契数
  2. 状态转移方程
    dpi = dpi - 3 + dpi - 2 + dpi - 1
  3. 初始化
    dp0 = 0 dp1 = 1 dp2 = 1
  4. 填表顺序
    从左往右
  5. 返回值
    返回dpn
java 复制代码
class Solution {
    public int tribonacci(int n) {

        //处理边界情况
        if(n == 0) return 0;
        if(n == 1 || n == 2) return 1;

        //1. 创建dp表
        int[] dp = new int[n + 1];
        //2. 初始化
        dp[0] = 0;
        dp[1] = dp[2] = 1;
        //3. 填表
        for(int i = 3; i <= n; i++){
            //状态转移方程
            dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1];
        }
        //4. 确定返回值
        return dp[n];
    }
}

空间优化: 用滚动数组来实现(此题使用变量即可)

java 复制代码
class Solution {
    public int tribonacci(int n) {

        //处理边界情况
        if(n == 0) return 0;
        if(n == 1 || n == 2) return 1;

        //空间优化
        int a = 0, b = 1, c = 1, d = 0;
        for(int i = 3; i <= n; i++){
            d = a + b + c;
            a = b;
            b = c;
            c = d;
        }
        //确定返回值
        return d;
    }
}

二. 三步问题

三步问题

  1. 状态表示
    dpi 表示到达i位置时, 一共有多少种方法
  2. 状态转移方程
    以i位置的状态, 最近的一步来划分问题
    1.从 i - 1 到 i dpi - 1
    2.从 i - 2 到 i dpi - 2
    3.从 i - 3 到 i dpi - 3
    dpi = dpi - 3 + dpi - 2 + dpi - 1
  3. 初始化
    dp1 = 1 dp2 = 1 dp3 = 4
  4. 填表顺序
    从左往右
  5. 返回值
    返回dpn
java 复制代码
class Solution {
    public int waysToStep(int n) {
        // 1. 创建dp表
        // 2. 初始化
        // 3. 填表
        // 4. 确定返回值
        
        int MOD = (int)1e9 + 7;

        if(n == 1 || n == 2) return n;
        if(n == 3) return 4;

        int[] dp = new int[n + 1];
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 4;
        for(int i = 4; i <= n; i++){
            dp[i] = ((dp[i - 3] + dp[i - 2]) % MOD + dp[i - 1]) % MOD;//每次做加法都有可能越界
        }
        return dp[n];
    }
}

三. 使用最小花费爬楼梯

使用最小花费爬楼梯

方法一: 以i位置结尾

  1. 状态表示
    dpi 表示到达i位置时, 最小花费
  2. 状态转移方程
    以i位置的状态, 最近的一步来划分问题
    1.先到达 i - 1 的位置, 然后支付costi - 1 走一步 到 i dpi - 1 + costi - 1
    2.从先到达 i - 2 的位置, 然后支付costi - 2 走两步 到 i dpi - 2 + costi - 2
  • dpi =min(dpi - 1 + costi - 1, dpi - 2 + costi - 2)
  1. 初始化
    dp0 = dp1 = 0
  2. 填表顺序
    从左往右
  3. 返回值
    返回dpn
java 复制代码
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        // 1. 创建dp表
        // 2. 初始化
        // 3. 填表
        // 4. 确定返回值

        int n = cost.length;
        int[] dp = new int[n + 1];

        for (int i = 2; i <= n; i++) {
            dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }
        return dp[n];
    }
}

方法二: 以i位置开始

  1. 状态表示
    dpi 表示从i位置, 到达顶点的最小花费
  2. 状态转移方程
    以 i 位置的状态, 最近的一步来划分问题
    1.先支付costi 走一步, 从 i 位置到达 i + 1 的位置, 然后到终点 dpi + 1 + costi
    2.先支付costi 走两步, 从 i 位置到达 i + 2 的位置, 然后到终点 dpi + 2 + costi
  • dpi =min(dpi + 1 + costi, dpi + 2 + costi)
  1. 初始化
    dpn = costn, dpn - 1 = costn - 1
  2. 填表顺序
    从右往左
  3. 返回值
    返回min(dp0, dp1)
java 复制代码
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        // 1. 创建dp表
        // 2. 初始化
        // 3. 填表
        // 4. 确定返回值
        int n = cost.length;
        int[] dp = new int[n];
        dp[n - 2] = cost[n - 2];
        dp[n - 1] = cost[n - 1];
        for(int i = n - 3; i >= 0; i--){
            dp[i] = Math.min(dp[i + 1] + cost[i], dp[i + 2] + cost[i]);
        }
        return Math.min(dp[0], dp[1]);
    }
}

四. 解码方法

解码方法

  1. 状态表示
    dpi 表示以 i 位置结尾, 解码方法的总数
  2. 状态转移方程

1.只解码 i 位置, 可以分成成功和失败两种情况

成功, si 一定是>= 1 && <= 9, 说明从头开始到i位置, 都是可以正常解码的, 只需在解码成功的字符串后面加上一个字符, 那么dpi = dpi - 1

失败 说明不管前面解码是否成功, i位置失败了, 就不算解码成功, dpi = 0

2.解码 i 位置和 i - 1 位置, 也可以分成成功和失败两种情况

成功, si - 1 * 10 + si 一定是>= 10 && <= 26, 说明从头开始到i位置, 都是可以正常解码的, 只需在解码成功的字符串(dpi - 2)后面加上一个字符, 那么dpi = dpi - 2

失败 说明不管前面解码是否成功, i位置失败了, 就不算解码成功, dpi = 0

  • 成功条件: dpi =dpi - 1 + dpi - 2
  1. 初始化
    dp0 一个字符的解码总数: 成功为1, 失败为0
    dp1 一个字符: 成功为1, 失败为0, 两个字符: 成功为1, 失败为0, 所以dp1可能为1, 2, 0
  2. 填表顺序
    从左往右
  3. 返回值
    返回dpn - 1
java 复制代码
class Solution {
    public int numDecodings(String ss) {
        // 1. 创建dp表
        // 2. 初始化
        // 3. 填表
        // 4. 返回值

        char[] s = ss.toCharArray();
        int n = s.length;
        int[] dp = new int[n];
        //初始化dp[0]
        if (s[0] != '0')
            dp[0] += 1;
        //注意字符长度
        if (n == 1)
            return dp[0];
        //初始化dp[1]
        if (s[1] != '0' && s[0] != '0')
            dp[1] += 1;
        int t = (s[0] - '0') * 10 + s[1] - '0';
        if (t >= 10 && t <= 26)
            dp[1] += 1;
        //填表
        for (int i = 2; i < n; i++) {
            
            if (s[i] != '0')
                dp[i] += dp[i - 1];

            int tt = (s[i - 1] - '0') * 10 + s[i] - '0';
            if (tt >= 10 && tt <= 26)
                dp[i] += dp[i - 2];
        }
        return dp[n - 1];
    }
}

优化:

此题中, 虚拟节点dp0中的值, 应该为1

因为当dp1和dp2两个字符一起看的时候, 如果能映射成字符, 那么dp2 = dp2 - 2 = dp0, 此时应该算作一个结果, 应该为1

java 复制代码
class Solution {
    public int numDecodings(String ss) {
        // 1. 创建dp表
        // 2. 初始化
        // 3. 填表
        // 4. 返回值

        char[] s = ss.toCharArray();
        int n = s.length;
        int[] dp = new int[n + 1];
        // 初始化dp[0]
        dp[0] = 1;// 虚拟节点
        // 初始化dp[1]
        if (s[0] != '0')
            dp[1] = 1;

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

            if (s[i - 1] != '0')
                dp[i] += dp[i - 1];

            int tt = (s[i - 2] - '0') * 10 + s[i - 1] - '0';
            if (tt >= 10 && tt <= 26)
                dp[i] += dp[i - 2];
        }
        return dp[n];
    }
}
相关推荐
To_OC11 小时前
LC 994 腐烂的橘子:人人都说是 BFS 入门题,我却写了三遍才过
javascript·算法·leetcode
金銀銅鐵14 小时前
[Python] 扩展欧几里得算法
python·数学·算法
To_OC17 小时前
LC 200 岛屿数量:经典 DFS 入门题,我第一次写居然连方向都搞错了
javascript·算法·leetcode
To_OC1 天前
LC 128 最长连续序列:别上来就排序,O (n) 解法才是这题的灵魂
javascript·算法·leetcode
05Kevin2 天前
lk每日冒险题--数据结构6.27
算法
To_OC2 天前
从一次栈溢出报错说起,我把递归彻底扒明白了
javascript·算法·程序员
千纸鹤安安3 天前
千问Qwen-AgentWorld来了:一个语言模型搞定七大Agent场景,GPT-5.4都输了
算法
七牛开发者3 天前
MCP 到底是什么?为什么 Agent 都想接上它
算法·aigc·agent