leetcode之动态规划刷题总结6

Posted nuist__NJUPT

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode之动态规划刷题总结6相关的知识,希望对你有一定的参考价值。

leetcode之动态规划刷题总结6

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

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

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

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

1-戳气球
题目链接:题目链接戳这里!!!

思路:动态规划
用dp[i][j]表示填满区间(i,j)能得到的最多硬币,用val数组改造原来的数组nums,在原数组的首尾各添加一个1,如果是戳气球,可能使得气球的相对位置发生改变,故我们倒过来看,从后往前添加气球,状态转移方程如下:

class Solution 
    public int maxCoins(int[] nums) 
        int n = nums.length ;
        int [] val = new int [n+2] ;
        int [][] dp = new int [n+2][n+2] ; //填完区间所能得到的最多硬币
        val[0] = val[n+1] = 1 ;
        for(int i=1; i<=n; i++) //将原数组改造成两端各有一个1的数组
            val[i] = nums[i-1] ;
        
        for(int i=n-1; i>=0; i--) //从后向前遍历,防止相邻元素被改动
            for(int j=i+2; j<n+2; j++)
                for(int k=i+1; k<j; k++)
                    int sum = val[i] * val[k] * val[j] ;
                    sum += dp[i][k] + dp[k][j] ;
                    dp[i][j] = Math.max(sum, dp[i][j]) ;
                
            
        
        return dp[0][n+1] ;
    


思路2:记忆化搜索

自顶向下的记忆化搜索,每次枚举填充的中间位置,然后记忆化搜索即可。
我们定义方法 dfs,令 dfs(i,j) 表示将开区间(i,j) 内的位置全部填满气球能够得到的最多硬币数。由于是开区间,因此区间两端的气球的编号就是 i 和 j,对应着 val[i] 和 val[j]。

class Solution 
    int [] val ;
    int [][] ans ;
    public int maxCoins(int[] nums) 
        //自顶向下,记忆化搜索
        int n = nums.length ;
        val = new int [n+2] ;
        ans = new int [n+2][n+2] ;
        val[0] = val[n+1] = 1 ;
        for(int i=1; i<=n; i++)
            val[i] = nums[i-1] ;
        
        for(int i=0; i<ans.length; i++)
            Arrays.fill(ans[i], -1) ;
        
        return dfs(0,n+1) ;
    
    public int dfs(int left, int right)
        if(left>=right-1) //出口
            return 0 ;
        
        if(ans[left][right]!=-1) //记忆化
            return ans[left][right] ;
        

        for(int i=left+1; i<right; i++)
            int sum = val[left] * val[i] * val[right] ;
            sum += dfs(left,i) + dfs(i,right) ; 
            ans[left][right] = Math.max(ans[left][right], sum) ;
        
        return ans[left][right] ;
    


2-交错字符串
题目链接:题目链接戳这里!!!

思路:动态规划
f(i,j)表示s1的前i个元素和s2的前j个元素能否交错组成s3的i+j个元素,我们可以发现
如果 s1 的第 i 个元素和 s3 的第 i+j 个元素相等,那么 s1的前 i 个元素和 s2 的前 j 个元素是否能交错组成 s3的前 i+j 个元素取决于 s1的前 i−1 个元素和 s2 的前 j 个元素是否能交错组成 s3 的前i+j−1 个元素,即此时f(i,j) 取决于f(i−1,j),在此情况下如果 f(i−1,j) 为真,则 f(i,j) 也为真,同理,如果 s2的第 j个元素和 s3的第 i+j 个元素相等并且f(i,j−1) 为真,则f(i,j) 也为真。

class Solution 
    public boolean isInterleave(String s1, String s2, String s3) 
        //f[i][j]表示s1的前i个元素和s2的前j个元素能否交错组成s3的前i+j个元素
        int n = s1.length(), m = s2.length(), t = s3.length() ;
        if(m+n != t)
            return false ;
        
        boolean [][] f = new boolean[n+1][m+1] ;
        f[0][0] = true ;
        for(int i=0; i<=n; i++)
            for(int j=0; j<=m; j++)
                if(i>0)
                    f[i][j] = f[i][j] || (f[i-1][j] && s1.charAt(i-1) == s3.charAt(i+j-1)) ;
                
                if(j>0)
                    f[i][j] = f[i][j] || (f[i][j-1] && s2.charAt(j-1) == s3.charAt(i+j-1)) ;
                
            
        
        return f[n][m] ;
    


3-乘积最大子数组
题目链接:题目链接戳这里!!!

思路:动态规划
需要维护两个数组,一个数组maxF[i]表示以i结尾的最大子数组的乘积,minF[i]表示以i结尾的最小子数组的乘积。

class Solution 
    public int maxProduct(int[] nums) 
        //maxF[i]表示以i结尾的最大子数组的乘积,minF[i]表示以i结尾的最小子数组的乘积
        int n = nums.length ;
        int [] maxF = new int [n] ;
        int [] minF = new int [n] ;

        for(int i=0; i<n; i++)
            maxF[i] = nums[i] ;
            minF[i] = nums[i] ;
        
        for(int i=1; i<n; i++)
            maxF[i] = Math.max(maxF[i-1]*nums[i], Math.max(nums[i], minF[i-1]*nums[i])) ;
            minF[i] = Math.min(minF[i-1]*nums[i], Math.min(nums[i], maxF[i-1]*nums[i])) ;
        
        int ans = maxF[0] ;
        for(int i=1; i<n; i++)
            ans = Math.max(ans, maxF[i]) ;
        
        return ans ;
    


4-最大平均值和分组
题目链接:题目链接戳这里!!!

思路:记忆化搜索
通过记忆化搜索枚举每一种分割情况来得到最终的最大值。使用memo[index][k]:用于记忆以index为起始分割点分割k个子集的最大平均值。

class Solution 
    double [][] memo ;
    public double largestSumOfAverages(int[] nums, int k) 
        this.memo = new double[nums.length+1][k+1] ;
        return dfs(nums,k,0) ;
    
    public double dfs(int [] nums, int k, int index)
        if(k==0)
            return 0.0 ;
        
        if(memo[index][k] != 0.0)
            return memo[index][k] ;
        
        if(k==1)
            double sum = 0 ;
            for(int i=index; i<nums.length; i++)
                sum += nums[i] ;
            
           memo[index][k] =  sum / (nums.length-index) ;
           return memo[index][k] ;
        

        double ans = 0.0, sum = 0.0 ;
        for(int i=index; i<=nums.length-k; i++)
            sum += nums[i] ;
            double avg = sum / (i - index + 1) ;
            memo[i+1][k-1] = dfs(nums,k-1,i+1) ;
            ans = Math.max(ans, avg+memo[i+1][k-1]) ;
        
        memo[index][k] = ans ;
        return ans ;
    

5-数组中的最长山脉
题目链接:题目链接戳这里!!!

思路:中心扩张法,每次找到一个比左右两侧都大的数,然后依次向两侧扩张。

class Solution 
    public int longestMountain(int[] arr) 
        //中心扩张法
        if(arr.length<3)
            return 0 ;
        
        int ans = 0 ;
        for(int i=1; i<arr.length-1; i++)
            if(arr[i]>arr[i-1] && arr[i]>arr[i+1])
                int l = i - 1 ;
                int r = i + 1 ;
                while(l>0 && arr[l-1]<arr[l])
                    l -- ;
                
                while(r<arr.length-1 && arr[r+1]<arr[r])
                    r ++ ;
                
                ans = Math.max(ans, r-l+1) ;
            
        
        return ans ;
    

6-最长等差数列
题目链接:题目链接戳这里!!!

思路:动态规划
dp[i][d]:表示当前下标为i时的等差为d的等差数列个数。

class Solution 
    public int longestArithSeqLength(int[] nums) 
        int [][] dp = new int [1001][1001] ;
        int ans = 0 ;
        for(int i=0; i<nums.length; i++)
            for(int j=0; j<i; j++)
                int d = nums[i] - nums[j] + 500 ;
                dp[i][d] = Math.max(dp[i][d], dp[j][d]+1) ;
                ans = Math.max(ans,dp[i][d]) ;
            
        
        return ans + 1 ;
    


7-将字符串翻转到单调递增
题目链接:题目链接戳这里!!!

思路1:指针模拟法,从头开始,依次记录0的个数和1的个数,当0的个数大于1的个数则翻转1,同时重新从该位置开始记录0和1的个数,最后结束后翻转0.

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


思路2:动态规划
定义dp[i][0], dp[i][0]表示前i个元素递增且第i个元素为0的最小翻转次数,
定义dp[i][1], dp[i][1]表示前i个元素递增且第i个元素为1的最小翻转次数。
由定义可知,如果前i个元素最后以0结尾且满足单调递增,那么前i个元素必须全部为0,由此可得 dp[i][0] 的状态转移如下:
dp[i][0] = dp[i-1][0] + (s.charAt(i)‘0’ ? 0 : 1);
由定义可知, dp[i][1]只要满足最后一个元素为1就行,那么前i-1个元素既可以为0,也可以为1,因此dp[i][1]的状态转移如下:
dp[i][1] = min(dp[i-1][1] , dp[i-1][0]) + (s.charAt(i)
‘1’ ? 0: 1);
最后取dp[i][0],dp[i][1]中的较小的即可。

class Solution 
    public int minFlipsMonoIncr(String s) 
        //dp[i][0]表示前i个元素以0结尾的最小翻转使得单调递增次数
        //dp[i][1]表示前i个元素以1结尾的最小翻转使得单调递增函数
        int n = s.length() ;
        int [][] dp = new int [n][2] ;

        dp[0][0] = (s.charAt(0)=='0' ? 0 : 1) ;
        dp[0][1] = (s.charAt(0)=='1' ? 0 : 1) ;

        for(int i=1; i<n; i++)
            dp[i][0] = dp[i-1以上是关于leetcode之动态规划刷题总结6的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode刷题笔记-动态规划-day6

LeetCode刷题笔记-动态规划-day6

LeetCode刷题笔记-动态规划-day6

leetcode之动态规划刷题总结8

leetcode之动态规划刷题总结7

leetcode之动态规划刷题总结4