【算法】动态规划与那些绕不清的题目们

 

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

动态规划的特点:

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

动态规划的本质,是对问题状态的定义状态转移方程的定义(状态与状态之间的递推关系),动规一般都需要和数组搭配使用来保存中间结果。

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

  1. 状态定义(子问题)
  2. 状态间的转移方程定义
  3. 状态的初始化
  4. 返回结果

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

适用场景:最大/最小值 、可行不可行、是不是、方案个数等

实践出真知:

一、斐波那契数列

现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。

  1. 状态定义:F(i):斐波那契第 i 项的值
  2. 状态间的转移方程定义:F(i) = F(i-1) + F(i-2)
  3. 状态的初始化:F(0)=0,F(1)=1,F(2)=1
  4. 返回结果:F(n):斐波那契第 n 项的值
public class Solution {
    public int Fibonacci(int n) {
//保存中间值
//        if(n==0) return 0;
//        if(n==1 || n==2) return 1;
//        int[] f=new int[n+1];//因为从第0项开始,所以给个n+1的空间
//        f[1]=1;//状态初始化
//        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 || n==2) return 1;
        int one=0;
        int two=1;
        int three=1;
        for(int i=2;i<=n;i++){
            three=one+two;
            one=two;
            two=three;
        }
        return three;
    }
}

二、变态跳台阶

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

  1. 状态定义:F(i):跳上第 i 级台阶需要的方法数
  2. 状态间的转移方程定义:F(i)=F(i-1)*2
  3. 状态的初始化:F(1)=1
  4. 返回结果:F(n):跳上第 n 级台阶需要的方法数
public class Solution {
    public int JumpFloorII(int target) {
        if(target<=0)  return 0;
        int f1 = 1;
        int ret = 1;
        for(int i=2;i<=target;i++){
            ret*=2;
        }
        return ret;
    }
}

三、矩形覆盖

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

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

  1. 状态定义:F(i):用 i 个 2*1 的小矩形无重复覆盖一个 2*i 的矩形的方法个数
  2. 状态间的转移方程定义:F(i) = F(i-1) + F(i-2)
  3. 状态的初始化:F(1) = 1,F(2) = 2
  4. 返回结果:F(n):方法个数
public class Solution {
    public int RectCover(int target) {
        if(target < 3) return target;
        int f1=1,f2=2,fn=0;
        for(int i=3;i<=target;i++){
            fn=f1+f2;
            f1=f2;
            f2=fn;
        }
        return fn;
    }
}

四、牛妹的蛋糕

第一天牛妹吃掉蛋糕总数三分之一(向下取整)多一个,第二天又将剩下的蛋糕吃掉三分之一(向下取整)多一个,以后每天吃掉前一天剩下的三分之一(向下取整)多一个,到第n天准备吃的时候只剩下一个蛋糕。牛妹想知道第一天开始吃的时候蛋糕一共有多少呢?

假设今天有 x 个,昨天有 y 个,则可得 x = y-(y/3+1),即 y = 3(x+1)/2。

public class Solution {
   public int cakeNumber (int n) {
        int ret=1;
        for(int i=n;i>1;i--){
            ret=(ret+1)*3/2;
        }
        return ret;
    }
}

五、连续子数组的最大和

在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)、

  1. 状态定义:F(i):以第 i 个元素结尾的最大连续和
  2. 状态间的转移方程:
  3. 状态的初始化:F(0)=array[0]
  4. 返回结果:max(F(i)),其中 i < n
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int curSum=array[0],maxSum=array[0];
        for(int i=1;i<array.length;i++){
            curSum=Math.max(array[i],curSum+array[i]);
            maxSum=Math.max(curSum,maxSum);
        }
        return maxSum;
    }
}

六、单词分割

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

import java.util.Set;
public class Solution {
    public boolean wordBreak(String s, Set<String> dict) {
        if(s.length()==0) return false;
        boolean[] ret=new boolean[s.length()+1];
        ret[0]=true;
        for(int i=1;i<=s.length();i++){
            for(int j=i-1;j>=0;j--){
               if(ret[j]&&dict.contains(s.substring(j,i))){
                    ret[i]=true;
                    break;
                }
            }
        }
        return ret[s.length()];
    }
}

 七、三角形最小路径和

给出一个三角形,计算从三角形顶部到底部的最小路径和,每一步都可以移动到下面一行相邻的数字,

例如,给出的三角形如下:

[[2],[3,4],[6,5,7],[4,1,8,3]]

最小的从顶部到底部的路径和是2 + 3 + 5 + 1 = 11。

自顶向下求解: 求出每个位置的路径和,最后遍历一遍二维数组最后一行找出最小值即可。每个位置的路径和等于它本身加上上面元素的和。这个位置列坐标 j 有如下可能:

  1. j = 0,则 path[i][0] = path[i-1][0] + triangle[i][0]
  2. i == j,则 path[i][j] = path[i-1][j-1] + triangle[i][i]
  3. 剩余情况:path[i][j] = min(path[i-1][j-1] ,path[i-1][j] ) + triangle[i][j]

import java.util.ArrayList;
public class Solution {
    public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {
         int row=triangle.size();
        int[][] minPath=new int[row][row];
        minPath[0][0]=triangle.get(0).get(0);
        for(int i=1;i<row;i++){
            for(int j=0;j<=i;j++){
                if(j==0) minPath[i][j]=triangle.get(i).get(0)+minPath[i-1][0];
                else if(i==j) minPath[i][j]=triangle.get(i).get(i)+minPath[i-1][j-1];
                else minPath[i][j]=triangle.get(i).get(j)+Math.min(minPath[i-1][j],minPath[i-1][j-1]);
            }
        }
        int min=minPath[row-1][0];
        for(int i=1;i<row;i++){
            min=Math.min(minPath[row-1][i],min);
        }
        return min;
    }
}

自底向上求解:

import java.util.ArrayList;
public class Solution {
    public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {
         int row=triangle.size();
        int[][] minPath=new int[row][row];
        for(int i=0;i<row;i++){// 初始化最后一行
            minPath[row-1][i]=triangle.get(row-1).get(i);
        }
        for(int i=row-2;i>=0;i--){
            for(int j=0;j<=i;j++){
                minPath[i][j]=triangle.get(i).get(j)+Math.min(minPath[i+1][j],minPath[i+1][j+1]);
            }
        }
        return minPath[0][0];
    }
}

八、带权值的最小路径和

给定一个由非负整数填充的m x n的二维数组,现在要从二维数组的左上角走到右下角,请找出路径上的所有数字之和最小的路径。注意:你每次只能向下或向右移动。

import java.util.*;

public class Solution {
    public int minPathSum (int[][] grid) {
        int row=grid.length,col=grid[0].length;
        int[][] arr=new int[row][col];
        
        for(int i=0;i<row;i++){
            for(int j=0;j<col;j++){
                if(i==0&&j==0){
                    arr[0][0]=grid[0][0];
                }else if(i==0){
                    arr[i][j]=grid[i][j]+arr[i][j-1];
                }else if(j==0){
                    arr[i][j]=grid[i][j]+arr[i-1][j];
                }else{
                    arr[i][j]=grid[i][j]+Math.min(arr[i-1][j],arr[i][j-1]);
                }
            }
        }
        return arr[row-1][col-1];
    }
}

九、unique-path(1)

一个机器人在m×n大小的地图的左上角(起点,下图中的标记“start"的位置)。机器人每次向下或向右移动。机器人要到达地图的右下角。(终点,下图中的标记“Finish"的位置)。可以有多少种不同的路径从起点走到终点?

import java.util.*;
public class Solution {
    public int uniquePaths (int m, int n) {
        int[][] arr=new int[m][n];
        for(int i=1;i<n;i++){//两个for循环进行初始化
            arr[0][i]=1;
        }
        for(int i=0;i<m;i++){
            arr[i][0]=1;
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                arr[i][j]=arr[i-1][j]+arr[i][j-1];//状态转移方程,这一步可以有前面这两部走过来
            }
        }
        return arr[m-1][n-1];//返回结果
    }
}

 unique-path(2)

如果在图中加入了一些障碍,有多少不同的路径?分别用0和1代表空区域和障碍:

import java.util.*;


public class Solution {
    /**
     * 
     * @param obstacleGrid int整型二维数组 
     * @return int整型
     */
    public int uniquePathsWithObstacles (int[][] obstacleGrid) {
        if(obstacleGrid.length==0) return 0;
        int row=obstacleGrid.length;
        int col=obstacleGrid[0].length;
        int[][] path=new int[row][col];
        for(int i=0;i<row;i++){
            if(obstacleGrid[i][0]==0) path[i][0]=1;
            else break;//从(i,0)后面的点都走不到
        }
        for(int j=0;j<col;j++){
            if(obstacleGrid[0][j]==0) path[0][j]=1;
            else break;//从(j,0)后面的点都走不到
        }
        for(int i=1;i<row;i++){
            for(int j=1;j<col;j++){
                if(obstacleGrid[i][j]==0)
                    path[i][j]=path[i-1][j]+path[i][j-1];
            }
        }
        return path[row-1][col-1];
    }
}

十、背包问题

有 n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值。问最多能装入背包的总价值是多大?

  1. A[i], V[i], n, m 均为整数
  2. 你不能将物品进行切分
  3. 你所挑选的要装入背包的物品的总大小不能超过 m
  4. 每个物品只能取一次

 

public class Solution {
    public int backPackII(int m, int[] A, int[] V) {
        int size=A.length;
        int[][] arr=new int[size+1][m+1];
        for(int i=1;i<=size;i++){
            for(int j=1;j<=m;j++){
                if(A[i-1]>j){
                    arr[i][j]=arr[i-1][j];
                }else{
                    arr[i][j]=Math.max(arr[i-1][j],arr[i-1][j-A[i-1]]+V[i-1]);
                }
            }
        }
        return arr[size][m];
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值