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

动态规划

分析:(五步)

  1. 状态表示

状态表示是什么?

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

状态表示怎么来的?

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

编写代码: (四步)

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

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

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

做法:

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

注意:

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

2.下标的映射关系

一.第n个泰波那契数

第n个泰波那契数

  1. 状态表示
    dp表 表示第n个泰波那契数
  2. 状态转移方程
    dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1]
  3. 初始化
    dp[0] = 0 dp[1] = 1 dp[2] = 1
  4. 填表顺序
    从左往右
  5. 返回值
    返回dp[n]
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. 状态表示
    dp[i] 表示到达i位置时, 一共有多少种方法
  2. 状态转移方程
    以i位置的状态, 最近的一步来划分问题
    1.从 i - 1 到 i dp[i - 1]
    2.从 i - 2 到 i dp[i - 2]
    3.从 i - 3 到 i dp[i - 3]
    dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1]
  3. 初始化
    dp[1] = 1 dp[2] = 1 dp[3] = 4
  4. 填表顺序
    从左往右
  5. 返回值
    返回dp[n]
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. 状态表示
    dp[i] 表示到达i位置时, 最小花费
  2. 状态转移方程
    以i位置的状态, 最近的一步来划分问题
    1.先到达 i - 1 的位置, 然后支付cost[i - 1] 走一步 到 i dp[i - 1] + cost[i - 1]
    2.从先到达 i - 2 的位置, 然后支付cost[i - 2] 走两步 到 i dp[i - 2] + cost[i - 2]
  • dp[i] =min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
  1. 初始化
    dp[0] = dp[1] = 0
  2. 填表顺序
    从左往右
  3. 返回值
    返回dp[n]
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. 状态表示
    dp[i] 表示从i位置, 到达顶点的最小花费
  2. 状态转移方程
    以 i 位置的状态, 最近的一步来划分问题
    1.先支付cost[i] 走一步, 从 i 位置到达 i + 1 的位置, 然后到终点 dp[i + 1] + cost[i]
    2.先支付cost[i] 走两步, 从 i 位置到达 i + 2 的位置, 然后到终点 dp[i + 2] + cost[i]
  • dp[i] =min(dp[i + 1] + cost[i], dp[i + 2] + cost[i])
  1. 初始化
    dp[n] = cost[n], dp[n - 1] = cost[n - 1]
  2. 填表顺序
    从右往左
  3. 返回值
    返回min(dp[0], dp[1])
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. 状态表示
    dp[i] 表示以 i 位置结尾, 解码方法的总数
  2. 状态转移方程

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

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

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

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

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

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

  • 成功条件: dp[i] =dp[i - 1] + dp[i - 2]
  1. 初始化
    dp[0] 一个字符的解码总数: 成功为1, 失败为0
    dp[1] 一个字符: 成功为1, 失败为0, 两个字符: 成功为1, 失败为0, 所以dp[1]可能为1, 2, 0
  2. 填表顺序
    从左往右
  3. 返回值
    返回dp[n - 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];
    }
}

优化:

此题中, 虚拟节点dp[0]中的值, 应该为1

因为当dp[1]和dp[2]两个字符一起看的时候, 如果能映射成字符, 那么dp[2] = dp[2 - 2] = dp[0], 此时应该算作一个结果, 应该为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];
    }
}
相关推荐
TangKenny1 小时前
计算网络信号
java·算法·华为
景鹤1 小时前
【算法】递归+深搜:814.二叉树剪枝
算法
iiFrankie1 小时前
SCNU习题 总结与复习
算法
Dola_Pan2 小时前
C++算法和竞赛:哈希算法、动态规划DP算法、贪心算法、博弈算法
c++·算法·哈希算法
小林熬夜学编程2 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
阿洵Rain3 小时前
【C++】哈希
数据结构·c++·算法·list·哈希算法
格里菲斯8583 小时前
算法练习记录
算法
阿芯爱编程3 小时前
平衡二叉树
java·后端·算法
幼儿园园霸柒柒4 小时前
第七章: 7.3求一个3*3的整型矩阵对角线元素之和
c语言·c++·算法·矩阵·c#·1024程序员节