【玩转算法】(初始)动态规划

  •  本期主题:动态规划,及其相关oj题。
  • 博客主页:小峰同学
  • 分享小编的在Linux中学习到的知识和遇到的问题
  • 小编的能力有限,出现错误希望大家不吝赐

 

🍁1)DP定义

  • 动态规划是分治思想的延伸,通俗一点来说就是大事化小,小事化无的艺术。
  • 在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接 使用这些结果。

动态规划具备了以下三个特点

  • 1. 把原来的问题分解成了几个相似的子问题。
  • 2. 所有的子问题都只需要解决一次。
  • 3. 储存子问题的解。

动态规划的本质,是对问题状态的定义状态转移方程的定义

也就是(状态以及状态之间的递推关系)

动态规划问题一般从以下四个角度考虑:

  • 1. 状态定义
  • 2. 状态间的转移方程定义
  • 3. 状态的初始化
  • 4. 返回结果
  • 状态定义的要求:定义的状态一定要形成递推关系
  • 一句话概括三特点四要素两本质
  • 适用场景:最大值/最小值, 可不可行, 是不是,方案个数 

🍁2)斐波那契

牛客网_oj

方法一:递归

缺点:

  • 时间复杂度0(N^2),非常低效的一个算法。

优点:

  • 代码简单易理解。
int Fibonacci(int n ) {
    // write code here
    if(n ==0){
        return 0;
    }
    if(n == 1 || n ==2){
        return 1;
    }
    return Fibonacci(n-1) + Fibonacci(n-2);
}

方法二:动态规划

优点:

  • 相比于递归更高效一些。时间复杂读为O(N)。
  • 代码也易懂的。

缺点:

  • 其实F(n)只与它相邻的前两项有关,所以没有必要保存所有子问题的解。
  • 空间复杂度为O(N),创建了一个数组,但是空间复杂度可以优化为O(1)。
class Solution {
public:
    int Fibonacci(int n) {
    //分析
    //1.状态:F(i)第i项的值
    //2.状态转换方程:F(i) = F(i-1)+F(i-2)
    //3.初始状态:F(0) = 0; F(1) = 1
    //4.返回: F(n)

    //我们要保存中间状态的解。就需要一个数组。
    //先创建一个数组
    int* F = new int[n+1];//注意这里的 n+1;
    //初始化为初始状态
    F[0] = 0;
    F[1] = 1;
    //状态转换方程 :F(i) = F(i-1)+F(i-2)
    for(int i = 2; i<=n; i++){
        F[i] = F[i-1]+F[i-2];
    }
    return F[n];
    
//优化
    if(n==0) return 0;
    if(n==1) return 1;
    int fn,fn1 = 0,fn2 = 1;
    for(int i = 2; i<=n; i++){
        fn = fn1 + fn2;
        fn1 = fn2;
        fn2 = fn;
    }
    return fn;

//再优化
    int a = 0,b = 1;
    while(n--){
        b = a+b;
        a = b-a;
    }
    return a;
    }
};

🍁3)字符串分割

牛客网_oj

方法一:递归

优点:相对于暴力遍历来说,代码时间复杂度和空间复杂度都相对

class Solution {
public:
    bool wordBreak(string s, unordered_set<string> &dict) {
        //注意这里的被分割是指,能在词典中找到。
        // 方法:动态规划
        // 状态:
        // 子状态:前1,2,3,...,n个字符能否根据词典中的词被成功分词
        // F(i): 前i个字符能否根据词典中的词被成功分词

        // 状态递推:
        // F(i): true{j <i && F(j) && substr[j+1,i]能在词典中找到} OR false
        // 在j小于i中,只要能找到一个F(j)为true,并且从j+1到i之间的字符能在词典
        // 中找到,则F(i)为true

        // 初始值:
        // 对于初始值无法确定的,可以引入一个不代表实际意义的空状态,作为状态的起始
        // 空状态的值需要保证状态递推可以正确且顺利的进行,到底取什么值可以通过简单
        // 的例子进行验证
        // F(0) = true

        // 返回结果:F(n)

        //实现
        if(s.empty()){ return false; }
        if(dict.empty()){ return false; }

        //创建一个数组保存子状态,便于后面状态使用
        vector<bool> can_break(s.size() + 1, false);

        // 初始化F(0) = true
        can_break[0] = true;

        for (int i = 1; i <= s.size(); i++){
            for (int j = i - 1; j >= 0; j--){
            // F(i): true{j <i && F(j) && substr[j+1,i]能在词典中找到} OR false
            // 第j+1个字符的索引为j
                if (can_break[j] && dict.find(s.substr(j, i - j)) != dict.end()){
                    can_break[i] = true;
                    break;
                }
            }
        }
        return can_break[s.size()];
    }
};

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小峰同学&&&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值