【算法 动态规划】学习动态规划的思想并使用动态规划解决斐波那契数列、青蛙跳台阶、连续子数组的最大和、拆分语句等相关问题。

学习目标:

目标:学习动态规划相关知识


学习内容:

本文内容:学习动态规划的思想并使用动态规划解决斐波那契数列、青蛙跳台阶、连续子数组的最大和、拆分语句等相关问题。


一、Dynamic Programming 动态规划

1.1 动态规划的定义

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

1.2 动态规划的特点

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

1.3 使用动态规划思想解决问题

动态规划的本状质是对问题状态的定义和状态转移方程的定义(状态及状态之间的递推关系)
动态规划问题一般从以下四个角度考虑:

  1. 状态定义;
  2. 状态间的转移方程定义;
  3. 状态的初始化;
  4. 返回结果;

定义的状态一定要形成递推关系

二、Fibonacci 斐波那契数列

1.1 题目描述

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1 F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 斐波那契数列由 0 和
1 开始,之后的斐波那契数就是由之前的两数相加而得出。

示例 1:

输入:n = 2
输出:1

示例 2:

输入:n = 5
输出:5

1.2实现代码

  • 方法一:递归
public int Fibonacci(int n) {
	// 初始值
	if(n <= 0)
		return 0;
	if(n == 1 || n == 2)
		return 1;
	// F(n)=F(n-1)+F(n-2)
	return Fibonacci(n - 1) + Fibonacci(n - 2);
}

递归的方法时间复杂度为O(2^n),随着n的增大呈现指数增长,效率低下 当输入比较大时,可能导致栈溢出,在递归过程中有大量的重复计算

  • 方法二: 动态规划
    状态:F(n)
    状态递推:F(n)=F(n-1)+F(n-2)
    初始值:F(1)=F(2)=1
    返回结果:F(N)
    public int Fibonacci(int n) {
        int[] ary=new int[40];
       	//初始值
        ary[0]=0;
        ary[1]=1;
        ary[2]=1;
        for(int i=3;i<=n;i++){
        	// F(n)=F(n-1)+F(n-2)
            ary[i]=ary[i-1]+ary[i-2];
        }
        return ary[n];
    }

上述解法的空间复杂度为O(n) 其实F(n)只与它相邻的前两项有关,
所以没有必要保存所有子问题的解 只需要保存两个子问题的解就可以
下面方法的空间复杂度将为O(1)

  • 方法三:
  public int Fibonacci(int n) {
  		//初始值
        if(n==0){
            return 0;
        }
        if(n<=2){
            return 1;
        }
        int a=1;
        int b=1;
        int ret=0;
        for(int i=0;i<n-2;i++){
        	// F(n)=F(n-1)+F(n-2)
            ret=a+b;
            a=b;
            b=ret;
        }
        return ret;
    }

三、青蛙跳台阶问题

3.1 题目题目描述

一只青蛙一次可以跳上1级台阶,也可以一次跳上2级,求该青蛙跳上一个n级的台阶总共有多少种跳法。

3.2 递推关系

状态
子状态:跳上1级,2级,3级…n级台阶的跳法数
f(n):第n个台阶的跳法数
初始值
f(0)=1;
f(1)=1;
f(2)=2;
递推
n节台阶有 f(n)种跳法;
在第n级跳法可以分为两种:

  1. 在第n-1级跳1级到第n级;
  2. 在第n-2级跳两级到第n级;

所以f(n)=f(n-1)+f(n-2);

3.3 实现代码

public int Fibonacci(int n) {
	// 初始值
	 if(n<=1){
        return 1;
     }
	// F(n)=F(n-1)+F(n-2)
	return Fibonacci(n - 1) + Fibonacci(n - 2);
}

其他方法可参照斐波那契数列的做法,与之类似

四、变态青蛙跳台阶问题

4.1题目描述

一只青蛙一次可以跳上1级台阶,也可以一次跳上2级,也可以跳上n级,求该青蛙跳上一个n级的台阶总共有多少种跳法。

4.2 递推关系

状态
子状态:跳上1级,2级,3级,…,n级台阶的跳法数
f(n):还剩n个台阶的跳法数
状态递推
n级台阶,第一步有n种跳法:跳1级、跳2级、到跳n级
跳1级,剩下n-1级,则剩下跳法是f(n-1)
跳2级,剩下n-2级,则剩下跳法是f(n-2)
f(n) = f(n-1)+f(n-2)+…+f(n-n)
f(n) = f(n-1)+f(n-2)+…+f(0)
f(n-1) = f(n-2)+…+f(0)
f(n) = 2f(n-1)
初始值
f(1) = 1
f(2) = 2
f(1) = 2
f(3) = 2f(2) = 4
f(4) = 2
f(3) = 8
所以它是一个等比数列
f(n) = 2^(n-1)
返回结果:f(N)

4.3 实现代码

public class Solution {
	public int JumpFloorII(int target) {
		int ret = 1;
		for(int i = 2; i <= target; ++i){
			ret *= 2;
		}
		return ret;
	}
}

五、最大连续子数组的和

5.1 题目描述

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

5.2 题目分析

原数组 array[9,5,2,7,4];
状态:
子状态:原数组的子数组长为1,2,3…n的最大和
F(i):以第i个元素为结尾的最大子数组和
递推
F(i)=max(F(i)+array[i],F(i));
初始值:F(0) = array[0]
返回结果
maxsum:所有F(i)中的最大值

5.3 实现代码

    public int FindGreatestSumOfSubArray(int[] array) {
    //初始化curSum,maxSum
        int curSum=array[0];
        int maxSum=array[0];
        for(int i=1;i<array.length;i++){
            curSum=Math.max(curSum+array[i],array[i]);//记录子数组的和
            maxSum=Math.max(curSum,maxSum);//记录当前最大值
        }
        return maxSum;
   }

六、拆分词句

6.1 题目描述

给定一个字符串s和一组单词dict,判断s是否可以用空格分割成一个单词序列,使得单词序列中所有的单词都是dict中的单词(序列可以包含一个或多个单词)。

例如:

给定s=“nowcode”;
dict=[“now”, “code”].
返回true,
因为"nowcode"可以被分割成"now ,code".

题目分析:

方法:动态规划
状态:
子状态:前1,2,3,…,n个字符能否根据词典中的词被成功分词
F(i): 前i个字符能否根据词典中的词被成功分词
状态递推
F(i): j <i && F(j) && substr[j+1,i]能在词典中找到
在j小于i中,只要能找到一个F(j)为true,并且从j+1到i之间的字符能在词典
中找到,则F(i)为true
初始值:
对于初始值无法确定的,可以引入一个不代表实际意义的空状态,作为状态的起始
空状态的值需要保证状态递推可以正确且顺利的进行,到底取什么值可以通过简单
的例子进行验证
F(0) = true
返回结果:F(n)

实现代码

    public boolean wordBreak(String s, Set<String> dict) {
        boolean[] res=new boolean[s.length()+1];//储存当前位置是否能被分割
        for(int i=1;i<=s.length();i++){
            if(dict.contains(s.substring(0,i))){
            //判断当前位置之前的字符串是否存在dict中,
            //如果存在,则在res[]中记录true,表示当前位置可以分割
                res[i]=true;
                continue;
            }
            //如果程序能够执行到这里,表示当前位置之前的字符串不存在dict中,
            //则判断是否可以继续分割成更短的字符串
            for(int j=i-1;j>0;j--){
                if(res[j]&&dict.contains(s.substring(j,i))){
                //该循环条件表示j位置之前的字符串存在dict中,并且j~i长度的字符串也存在dict中
                //则在位置就赋值为true,表示可以在此分割
                    res[i]=true;
                    break;
                }
            }
        }
        //返回res的最后一个值,
        //如果是true表示字符串s可以分割成所有子串都在dict中
        return res[s.length()];
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值