动态规划1:概念及简单例题[入门篇]


一、概念篇

1.定义

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

2.特点

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

3.本质

动态规划的本质,是对问题状态的定义状态转移方程的定义(状态以及状态之间的关系)

4.步骤

动态规划问题一般从以下四个角度考虑:
1.状态定义
2.状态间的转移方程定义
3.状态的初始化
4.返回结果
状态定义的要求:定义的状态一定要形成递推关系

5.适用场景

求最大值/最小值,判断可不可行,判断是不是,求方案个数

二、用例篇

1.斐波那契数列

通常我们斐波那契会采用递归的方式求解,这种方式时间复杂度为2n,当n值很大时,会产生很多的重复计算,性能就会很差,可能会导致栈溢出,计算很慢。
实际上斐波那契数列是很容易用动态规划的。其状态转移方程都是很明显的。

状态F(i):第i项的值
状态转移方程:F(i)=F(i-1)+F(i-2)
初始状态:F(0)=0,F(1)=1
返回结果:F(n)

可以创建一个数组,保存中间状态的解。
程序如下:

class Solution {
    public int fib(int n) {
        if(n==0){
            return 0;
        }
        if(n==1){
            return 1;
        }
        //创建数组保存中间状态
        int[] array=new int[n+1];
        //初始状态
        array[0]=0;
        array[1]=1;
        for(int i=2;i<n+1;i++){
            //状态转移方程
            array[i]=array[i-1]+array[i-2];
        }
        return array[n];
    }
}

但其实我们会发现:空间复杂度可以更优化,因为这里的数组我们只是用到前两个,最前面的元素用不到,所以我们只需要给两个临时变量动态更新,需要更新中间状态。优化程序如下:

class Solution {
    public int fib(int n) {
        if(n==0){
            return 0;
        }
        if(n==1){
            return 1;
        }
        //定义初始状态
        int fb0=0;
        int fb1=1;
        int fb=0;
        for(int i=2;i<n+1;i++){
            //状态转移方程
            fb=fb0+fb1;
            //更新中间状态
            fb0=fb1;
            fb1=fb;
        }
        return fb;
    }
}

2.单词拆分

题目:

给定一个字符串 s,和一组单词 dict,判断 s 是否可以用空格分割成一个单词序列,使得单词序列中所有的单词都是 dict 中的单词(序列可以包含一个或多个单词)
示例:
给定 s = “leetcode”,dict = [“leet”,“code”]
返回 true,因为 “leetcode” 可以被分割成 “leet”,“code”

我们可以以示例来引导:F(4):前四个字符是否可以被分割:true
在F(4)已经为true的前提下:
F(8):F(4)&&[5,8]是否可以在词典中找到:true
在没有F(4)已经为true的前提下:
F(8):要分别查找一下这几个,直到找到条件为true则break。
在这里插入图片描述
推导出状态转移方程为:F(i):j<i&&F(j)&&[j+1,i]是否可以在词典中找到

状态F(i):字符串前i个字符是否可以被分割
状态转移方程:F(i):j<i&&F(j)&&[j+1,i]是否可以在词典中找到
初始状态:F(0):true 辅助状态,不代表实际意义

F(0)在这里的含义是空字符串是否在词典中存在,答案当然是不存在的,但这里的F(0)只是一个辅助状态,没有实际意义
当F(0)为false时,如果整个字符串在词典中存在,答案false&&true仍然为false,这是不合理的。
所以为true。

返回结果:F(字符串长度):F(s.length())

代码如下:

public boolean wordBreak(String s, Set<String> dict){
    // 创建一个数组用来保存状态
    boolean[] canBreak = new boolean[s.length() + 1];
    // 初始状态
    canBreak[0] = true;
    for (int i = 1; i <= s.length(); i++) {
        // 状态转移方程F(i):(j < i) && F(j) && [ j+1,i ]
        for (int j = 0; j < i; j++) {
            //由于substring是左开右闭区间,所以这里直接写j而不是j+1
            if(canBreak[j] && dict.contains(s.substring(j,i))){
                canBreak[i] = true;
                break;
            }
        }
    }
    return canBreak[s.length()];
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

dhdhdhdhg

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

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

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

打赏作者

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

抵扣说明:

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

余额充值