leetcode之动态规划刷题总结8

leetcode之动态规划刷题总结8

动态规划(英语:Dynamic programming,简称 DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。

动态规划常常适用于有重叠子问题和最优子结构性质的问题,并且记录所有子问题的结果,因此动态规划方法所耗时间往往远少于朴素解法。

动态规划有自底向上和自顶向下两种解决问题的方式。自顶向下即记忆化递归,自底向上就是递推。

使用动态规划解决的问题有个明显的特点,一旦一个子问题的求解得到结果,以后的计算过程就不会修改它,这样的特点叫做无后效性,求解问题的过程形成了一张有向无环图。动态规划只解决每个子问题一次,具有天然剪枝的功能,从而减少计算量。

1-鸡蛋掉落
题目链接:题目链接戳这里!!!

思路:动态规划思想
dp[i][j]表示有i+1枚鸡蛋,验证从j层楼扔下的时候的最小操作次数。dp[0][j]表示只有枚鸡蛋,那需要尝试j次,即dp[0][j]=j,如果有两枚鸡蛋,则dp[1][j]分为两种情况,一种是在第k层摔碎了,则dp[1][j]=dp[0][k-1]+1,另一种是没有摔碎,则dp[1][j]=dp[1][j-k]+1,因为是最坏情况的最优策略,故递推表达式如下:
dp[1][j] = Math.min(dp[1][j],Math.max(dp[0][k-1]+1,dp[1][j-k]+1))

class Solution {
    public int twoEggDrop(int n) {
        //dp[i][j]表示有i+1枚鸡蛋时候,验证从j层楼扔下的最小操作次数
        int [][] dp = new int [2][n+1] ;
        for(int i=0; i<2; i++){
            Arrays.fill(dp[i],Integer.MAX_VALUE) ;
        }
        dp[0][0] = dp[1][0] = 0 ;
        for(int j=1; j<=n; j++){
            dp[0][j] = j ;
        }
        for(int j=1; j<=n; j++){
            for(int k=1; k<=j; k++){
                dp[1][j] = Math.min(dp[1][j],Math.max(dp[0][k-1]+1,dp[1][j-k]+1)) ;
            }
        }
        return dp[1][n] ;
    }
}

在这里插入图片描述
2-最大子序列交替和
题目链接:题目链接戳这里!!!

思路:dp[i][0]表示当前索引i时候长度为奇数的最大交替和;dp[i][1]表示当前索引i时候长度为偶数的最大交替和;对于当前i索引,如果长度为奇数,则可以不选择当前,可以选择当前作为一个新序列,可以选择当前接在偶数序列的后面;对于当前索引,如果长度为偶数,可以不选择当前值,也可以选择当前值接在奇数序列的后面。递推式如下:

dp[i][0] = Math.max(dp[i-1][0],Math.max(nums[i], dp[i-1][1] + nums[i])) ;
dp[i][1] = Math.max(dp[i-1][1],dp[i][0]-nums[i]) ;

class Solution {
    public long maxAlternatingSum(int[] nums) {
        //dp[i][0]表示当前索引i时候长度为奇数的最大交替和
        //dp[i][1]表示当前索引i时候长度为偶数的最大交替和
        long [][] dp = new long [nums.length][2] ;
        dp[0][0] = nums[0] ;
        dp[0][1] = Integer.MIN_VALUE ;

        for(int i=1; i<nums.length; i++){
            dp[i][0] = Math.max(dp[i-1][0],Math.max(nums[i], dp[i-1][1] + nums[i])) ;
            dp[i][1] = Math.max(dp[i-1][1],dp[i][0]-nums[i]) ;
        }
        return dp[nums.length-1][0] ;
    }
}

在这里插入图片描述
3-统计字典序元音字符串的数目
题目链接:题目链接戳这里!!!

思路:dp[i][04]表示长度为i的以au结尾的字符串的个数。
初始化长度为1的以a~u结尾的字符串的个数都是1,然后可以地推出递推表达式。

class Solution {
    public int countVowelStrings(int n) {
        int [][] dp = new int [n+1][5] ;
        for(int i=0; i<5; i++){
            dp[1][i] = 1 ;
        }
        for(int i=2; i<=n; i++){
            dp[i][4] = dp[i-1][4] + dp[i-1][3] + dp[i-1][2] + dp[i-1][1] + dp[i-1][0];
            dp[i][3] = dp[i-1][3] + dp[i-1][2] + dp[i-1][1] + dp[i-1][0] ;
            dp[i][2] = dp[i-1][2] + dp[i-1][1] + dp[i-1][0];
            dp[i][1] = dp[i-1][1] + dp[i-1][0] ;
            dp[i][0] = dp[i-1][0] ;
        }
        return dp[n][4] + dp[n][3] + dp[n][2] + dp[n][1] + dp[n][0];
    }
}

在这里插入图片描述
其实一行代码也能通过,不过这个要经历大量的推导,推导出一个恒等式,然后一行代码即可。

class Solution {
    public int countVowelStrings(int n) {
    return (n+1)*(n+2)*(n+3)*(n+4) / 24; 
    }
}

4-剪绳子
题目链接:题目链接戳这里!!!

思路:就是将一个整数进行拆分成一些整数之和,拆分后的乘积最大。我们考虑两种情况:
第一:将整数i拆分成j和i-j,并且i-j不在拆分
第二:将整数i拆分成j和i-j,并且i-j继续拆分
故可以得到递推表达式如下:
curMax = Math.max(curMax, Math.max(j*(i-j),j*dp[i-j])) ;

class Solution {
    public int cuttingRope(int n) {
        int [] dp = new int [n+1] ;
        for(int i=2; i<=n; i++){
            int curMax = 0 ;
            for(int j=1; j<=i; j++){
                curMax = Math.max(curMax, Math.max(j*(i-j),j*dp[i-j])) ;
            }
            dp[i] = curMax ;
        }
        return dp[n] ;
    }
}

在这里插入图片描述
5-连续子数组的最大和
题目链接:题目链接戳这里!!!

思路:对于每个数,如果当前数字大于0,则加到后面一个数,如果后面的数大于max,则更新max,最终的max就算连续子数组的最大和 。

class Solution {
    public int maxSubArray(int[] nums) {
        int max = nums[0] ;
        for(int i=0; i<nums.length-1; i++){
            if(nums[i] > 0){
                nums[i+1] += nums[i] ;
            }
            if(nums[i+1]>max){
                max = nums[i+1] ;
            }
        }
        return max ;
    }
}

在这里插入图片描述
6-丑数
题目链接:题目链接戳这里!!!

思路:动态规划+三指针
定义dp[i]表示第i个丑数,定义三个指针index1,index2,index3表示下一个丑数是当前指针指向的丑数乘以对应的质因数,每一次取三个数的最小值,如果等于第i个丑数,则对应的指针需要向后移动一位,最后得到的dp[n]就是第n个丑数。

class Solution {
    public int nthUglyNumber(int n) {
        //dp[i]表示第i个丑数,定义三个指针表示下一个丑数是当前指针指向的丑数乘以对应的质因数
        int [] dp = new int [n+1] ;
        int index1, index2, index3 ;
        dp[1] = 1 ;
        index1 = index2 = index3 = 1 ;
        for(int i=2; i<=n; i++){
            int num1 = dp[index1] * 2 ;
            int num2 = dp[index2] * 3 ;
            int num3 = dp[index3] * 5 ;
            dp[i] = Math.min(num3,Math.min(num1,num2)) ;
            if(dp[i]==num1){
                index1 ++ ;
            }
            if(dp[i]==num2){
                index2 ++ ;
            }
            if(dp[i]==num3){
                index3 ++ ;
            }
        }
        return dp[n] ;
    }
}

在这里插入图片描述
7-最长斐波那契数列
题目链接:题目链接戳这里!!!

思路:动态规划
dp[i][j]表示以arr[i]为结尾的前一个数字是arr[j]的最长斐波那契数列,如果数字元素少于3个,则肯定不能组成斐波那契数列,如果当前数和上一个数之差存在,则可以组成一个斐波那契数列,否则不可以组成斐波那契数列。

class Solution {
    public int lenLongestFibSubseq(int[] arr) {
        //dp[i][j]表示以arr[i]为结尾的前一个数字是arr[j]的最长斐波那契序列
        int n = arr.length ;
        if(n<3){ //小于3,不可能满足
            return 0 ;
        }
        int [][] dp = new int [n][n] ;
        Map<Integer, Integer> map = new HashMap<>() ;
        for(int i=0; i<n; i++){ //存储每个数字对应位置
            map.put(arr[i],i) ;
        }
        int max = 0 ;
        for(int i=0; i<n; i++){
            for(int j=0; j<i; j++){
                if(map.containsKey(arr[i]-arr[j]) && map.get(arr[i]-arr[j])<j){
                    int k = map.get(arr[i]-arr[j]) ; //如果上一个数字和这两个可以组成斐波那契
                    dp[i][j] = Math.max(dp[i][j], dp[j][k]+1) ;
                    }else{
                        dp[i][j] = 2 ; //等待组合
                    }
                max = Math.max(max, dp[i][j]) ;
            }
        }
        max =  (max > 2 ? max : 0) ;
        return max ;
    }
}

8-翻转字符
题目链接:题目链接戳这里!!!

思路:模拟法,使用两个变量记录0和1个数,指针从前向后遍历,每次如果当0的个数大于1的个数,则翻转1,并重新开始记录0和1的个数,如果最后翻转了1,则不需要翻转0,否则需要翻转0。

class Solution {
    public int minFlipsMonoIncr(String s) {
        int zero=0, one = 0, ans = 0 ;
        int p = 0 ;
        while(p<s.length()){
            if(s.charAt(p)=='0'){
                zero ++ ;
            }else if(s.charAt(p)=='1'){
                one ++ ;
            }
            if(zero>one){
                ans += one ;
                one = zero = 0 ;
            }
            p ++ ;
        }
        ans += zero ;
        return ans ;

    }
}

在这里插入图片描述

9-最长公共子序列
题目链接:题目链接戳这里!!!

思路:dp[i][j]表示字符串下标0…i与字符串下标0…j的最长公共子序列,我们主要对比两个字符串对应i和j位置的字符是否相等,如果相等,则dp[i][j]=dp[i-1][j-1]+1,如果不相等,则dp[i][j]=max(dp[i-1][j],dp[i][j-1]).

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        //哈哈,又要水一个重复题了,嘻嘻嘻
        //dp[i][j]表示字符串下标0..i与字符串下标0...j的最长公共子序列
        int n = text1.length();
        int m = text2.length() ;
        int [][] dp = new int [n+1][m+1] ;
        for(int i=1; i<=n; i++){
            for(int j=1; j<=m; j++){
                if(text1.charAt(i-1)==text2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1] + 1 ;
                }else{
                    dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]) ;
                }
            }
        }
        return dp[n][m] ;
    }
}

在这里插入图片描述

10-粉刷房子
题目链接:题目链接戳这里!!!

思路:dp[i][0~2]表示第i个房子染成红蓝绿的最小成本
很容易得到递推表达式,如果当前房子想染成红色的最小成本,则需要保证上一个染的是绿色或者蓝色的最小成本,依次类推。

class Solution {
    public int minCost(int[][] costs) {
        //dp[i][0~2]表示第i个房子染成红蓝绿的最小成本
        int n = costs.length ;
        int m = costs[0].length ;
        int [][] dp = new int [n][m] ;
        dp[0][0] = costs[0][0];
        dp[0][1] = costs[0][1] ;
        dp[0][2] = costs[0][2] ;
        for(int i=1; i<n; i++){
            dp[i][0] = Math.min(dp[i-1][1],dp[i-1][2]) + costs[i][0] ;
            dp[i][1] = Math.min(dp[i-1][0],dp[i-1][2]) + costs[i][1] ;
            dp[i][2] = Math.min(dp[i-1][0],dp[i-1][1]) + costs[i][2] ;
        }
        return Math.min(dp[n-1][2],Math.min(dp[n-1][0],dp[n-1][1])) ;
    }
}

在这里插入图片描述
11-爬楼梯的最少成本
题目链接:题目链接戳这里!!!

思路:dp[i]表示爬到第i层所需花费的最小体力,很容易得到递推表达式。

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length ;
        //dp[i]代表爬到第i个阶梯的最小体力花费
        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] ;
    }
}

在这里插入图片描述
**

为了金奖,为了国一,为了大厂,冲吧!!!

**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

nuist__NJUPT

给个鼓励吧,谢谢你

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

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

打赏作者

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

抵扣说明:

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

余额充值