LeetCode动态规划经典题(一)

62. 不同路径

https://leetcode.cn/problems/unique-paths/
在这里插入图片描述

思路:对于每个位置(i,j), 它可以从两个位置过来,一个是上面,一个是左边,所以(i,j)位置可达的路径为 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j]=dp[i-1][j]+dp[i][j-1] dp[i][j]=dp[i1][j]+dp[i][j1], 边界条件包括第一行和第一列,第一行和第一列只有一条路径可以达到,初始化为1

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp=new int[m][n];
        for(int i=0;i<m;i++){//第一列只有1种路径达到
            dp[i][0]=1;
        }
        for(int j=0;j<n;j++){//第一行只有1种路径达到
            dp[0][j]=1;
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
}
//O(mn)
//O(mn)

63. 不同路径 II

https://leetcode.cn/problems/unique-paths-ii/
在这里插入图片描述

思路:同不同路径I这道题,这里的递推公式只有当当前位置没有障碍物时才满足条件,另外在初始化边界条件时,遇到障碍物就停止初始化,因为后面都是不可达到的

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m=obstacleGrid.length,n=obstacleGrid[0].length;
        int[][] dp=new int[m][n];
        for(int i=0;i<m&&obstacleGrid[i][0]==0;i++){//初始化第一列 遇到障碍物则从障碍物开始往后都不可达
            dp[i][0]=1;
        }
        for(int j=0;j<n&&obstacleGrid[0][j]==0;j++){//初始化第一行 遇到障碍物则从障碍物开始往后都不可达
            dp[0][j]=1;
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                if(obstacleGrid[i][j]==0){//位置(i,j)没有障碍物
                    dp[i][j]=dp[i-1][j]+dp[i][j-1];
                }
            }
        }
        return dp[m-1][n-1];
    }
}
//O(mn)
//O(mn)

64. 最小路径和

https://leetcode.cn/problems/minimum-path-sum/
在这里插入图片描述

思路:和不同路径这两道题目类似,首先初始化第一行和第一列,作为边界条件处理,然后对于其他的位置(i,j), 状态转移方程为: d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] + g r i d [ i ] [ j ] dp[i][j]=min(dp[i-1][j],dp[i][j-1]+grid[i][j] dp[i][j]=min(dp[i1][j],dp[i][j1]+grid[i][j], 最后返回dp[m-1][n-1]

class Solution {
    public int minPathSum(int[][] grid) {
        int m=grid.length,n=grid[0].length;
        int[][] dp=new int[m][n];
        dp[0][0]=grid[0][0];
        for(int i=1;i<m;i++){//初始化第一列
            dp[i][0]=dp[i-1][0]+grid[i][0];
        }
        for(int j=1;j<n;j++){//初始化第一行
            dp[0][j]=dp[0][j-1]+grid[0][j];
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                //当前位置右上面和左边格子中的较短路径移动而来
                dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
            }
        }
        return dp[m-1][n-1];
    }
}
//O(mn)
//O(mn)

5. 最长回文子串

https://leetcode.cn/problems/longest-palindromic-substring/
在这里插入图片描述

思路:对于一个子串s[i:j] ,当s[i:j] 是回文串时,有s[i+1:j-1]是回文串并且s[i]=s[j]

d p [ i ] [ j ] = { t r u e , if i=j s [ i ] = s [ j ] & & d p [ i + 1 ] [ j − 1 ] , dp[i][j]= \begin{cases} true, & \text{if i=j} \\ s[i]=s[j] \&\&dp[i+1][j-1], \end{cases} dp[i][j]={true,s[i]=s[j]&&dp[i+1][j1],if i=j

class Solution {
    public String longestPalindrome(String s) {
        int n=s.length();
        if(n<2){
            return s;
        }
        int start=0;
        int maxLen=1;
        boolean[][] dp=new boolean[n][n];
        for(int i=0;i<n;i++){
            dp[i][i]=true;//单个字符是回文串
        }
        for(int len=2;len<=n;len++){
            for(int i=0;i<n;i++){//i是开始位置
                int j=i+len-1;//j是结束位置
                if(j>=n){//越界
                    break;
                }
                if(s.charAt(i)!=s.charAt(j)){//s[i]!=s[j]
                    dp[i][j]=false;
                }else{//s[i]=s[j]
                    if(j-i<3){//长度为2&s[i]=s[j] 直接是回文串
                        dp[i][j]=true;
                    }else{
                        dp[i][j]=dp[i+1][j-1];
                    }
                }
                if(dp[i][j]&&j-i+1>maxLen){//s[i:j]是回文串并且串更长
                    maxLen=j-i+1;
                    start=i;
                }
            }
           
        }
        return s.substring(start,start+maxLen);
    }
    
}
//O(n^2)
//O(n^2)

剑指 Offer II 091. 粉刷房子

https://leetcode.cn/problems/JEj789/

在这里插入图片描述

思路:动态规划,dp[i][j]表示粉刷了房子0号到i号并且i号房子颜色是j(0: 红色 1:蓝色 2:绿色)
则状态转移方程为:

d p [ i ] [ 0 ] = m i n ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 2 ] ) + c o s t [ i ] [ 0 ] d p [ i ] [ 1 ] = m i n ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 2 ] ) + c o s t [ i ] [ 1 ] d p [ i ] [ 2 ] = m i n ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] ) + c o s t [ i ] [ 2 ] dp[i][0]=min(dp[i-1][1],dp[i-1][2])+cost[i][0] \\ dp[i][1]=min(dp[i-1][0],dp[i-1][2])+cost[i][1] \\ dp[i][2]=min(dp[i-1][0],dp[i-1][1])+cost[i][2] dp[i][0]=min(dp[i1][1],dp[i1][2])+cost[i][0]dp[i][1]=min(dp[i1][0],dp[i1][2])+cost[i][1]dp[i][2]=min(dp[i1][0],dp[i1][1])+cost[i][2]

当染0号房子时,dp[0][0]=cost[0][0]dp[0][1]=cost[0][1]dp[0][2]=cost[0][2], 为了节省空间开销,可以直接使用已有的costs数组作为dp数组

class Solution {
    public int minCost(int[][] costs) {
        int n=costs.length;
        for(int i=1;i<n;i++){
            costs[i][0]=Math.min(costs[i-1][1],costs[i-1][2])+costs[i][0];
            costs[i][1]=Math.min(costs[i-1][0],costs[i-1][2])+costs[i][1];
            costs[i][2]=Math.min(costs[i-1][0],costs[i-1][1])+costs[i][2];
        }
        return Math.min(costs[n-1][0],Math.min(costs[n-1][1],costs[n-1][2]));
    }
}
//O(n)
//O(1)

53. 最大子数组和

https://leetcode.cn/problems/maximum-subarray/
在这里插入图片描述

思路:dp[i]表示以数组元素nums[i]结尾的最大连续子数组和
d p [ i ] = m a x ( d p [ i − 1 ] + n u m s [ i ] , n u m s [ i ] ) dp[i]=max(dp[i-1]+nums[i],nums[i]) dp[i]=max(dp[i1]+nums[i],nums[i]), 如果加上dp[i-1]的连续和更大则加上,否则以nums[i]结尾的最大连续和就是nums[i], 然后找出所有的dp[i]中最大的,即位整体的最大连续子数组和

class Solution {
    public int maxSubArray(int[] nums) {
        int n=nums.length;
        int[]dp=new int[n];
        int maxSum=nums[0];
        dp[0]=nums[0];
        for(int i=1;i<n;i++){
            dp[i]=Math.max(dp[i-1]+nums[i],nums[i]);
            maxSum=Math.max(maxSum,dp[i]);

        }
        return maxSum;
    }
}
//O(n)
//O(n)

121. 买卖股票的最佳时机

https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/
在这里插入图片描述

思路:状态转移方程为 d p [ i ] = m a x ( d p [ i − 1 ] , p r i c e s [ i ] − m i n P r i c e ) dp[i]=max(dp[i-1],prices[i]-minPrice) dp[i]=max(dp[i1],prices[i]minPrice), dp[i]表示[0-i]天内可以取得的最大利润,最大利润在前i-1天的最大利润和第i天卖出获得的利润中取最大值

class Solution {
    public int maxProfit(int[] prices) {
        int n=prices.length;
        int minPrice=prices[0];
        int[] dp=new int[prices.length];
        dp[0]=0;//第0天没有利润
        for(int i=1;i<n;i++){
            minPrice=Math.min(minPrice,prices[i]);//更新[0:i]区间内的最小价格
            dp[i]=Math.max(dp[i-1],prices[i]-minPrice);//第i天卖出  最小价格那天买入(假设第j天价格最低 j属于[0:i-1])
               //更新最大利润
            
        }
        return dp[n-1];
    }
}
//O(n)
//O(n)

由于每次只会用到上一状态的值,可以将dp数组简化为一个变量maxProfit, 减小空间开销

class Solution {
    public int maxProfit(int[] prices) {
        int minPrice=prices[0];
        int maxProfit=0;
        for(int i=1;i<prices.length;i++){
            minPrice=Math.min(minPrice,prices[i]);//更新[0:i]区间内的最小价格
            maxProfit=Math.max(maxProfit,prices[i]-minPrice);//第i天卖出  最小价格那天买入(假设第j天价格最低 j属于[0:i-1])
               ;//更新最大利润
            
        }
        return maxProfit;
    }
}

122. 买卖股票的最佳时机 II

https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/
在这里插入图片描述

思路:在某一天手里可能没有股票,手里可能有一支股票,记作dp[i][0]为在第i天手里没有股票时可以获得的最大利润,dp[i][1]为在第i天手里有1支股票时可以获得的最大利润

dp[i][0]可以从两个状态转移过来:

  1. 第i-1天手里没有股票
  2. 第i-1天手里有股票,但是第i天卖了

dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]

dp[i][1]可以从两个状态转移过来:

  1. 第i-1天手里有股票
  2. 第i-1天手里没有股票,但是第i天买了一支股票

dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]

class Solution {
    public int maxProfit(int[] prices) {
        int n=prices.length;
        int[][] dp=new int[n][2];
        dp[0][0]=0;//第0天手里没有股票的利润为0
        dp[0][1]=-prices[0];//第0天买入 因此利润为负值
        for(int i=1;i<n;i++){
            dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
            dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
        }
        return Math.max(dp[n-1][0],dp[n-1][1]);
    }
}
//O(n)
//O(n)

上面的状态转移方程中,每一天的状态只与前一天的状态有关,而与更早的状态都无关, 因此可以将dp二维数组用两个变量表示,减小空间开销

class Solution {
    public int maxProfit(int[] prices) {
        int n=prices.length;
        int dp0=0;//第0天手里没有股票的利润为0
        int dp1=-prices[0];//第0天买入 因此利润为负值
        for(int i=1;i<n;i++){
            dp0=Math.max(dp0,dp1+prices[i]);
            dp1=Math.max(dp1,dp0-prices[i]);
        }
        return Math.max(dp0,dp1);
    }
}
//O(n)
//O(1)

123. 买卖股票的最佳时机 III

https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/
在这里插入图片描述

思路:在任意一天结束后,我们会处于以下5种状态中的一种

  1. 未进行过任何操作
  2. 只进行过一次买操作
  3. 进行过一次买操作和一次卖操作,即完成一次交易
  4. 完成了一次交易后又进行了一次买操作
  5. 完成了两次交易

状态1没有进行任何操作,因此利润为0,此种状态不考虑,对其他四种状状态最大利润分别记为buy1 sell1 buy2 sell2

{ b u y 1 = m a x ( b u y 1 , − p r i c e s [ i ] ) s e l l 1 = m a x ( s e l l 1 , b u y 1 + p r i c e s [ i ] b u y 2 = m a x ( b u y 2 , s e l l 1 − p r i c e s [ i ] ) s e l l 2 = m a x ( s e l l 2 , b u y 2 + p r i c e s [ i ] ) \begin{cases} buy1=max(buy1,-prices[i]) \\ sell1=max(sell1,buy1+prices[i] \\ buy2=max(buy2,sell1-prices[i]) \\ sell2=max(sell2,buy2+prices[i]) \end{cases} buy1=max(buy1,prices[i])sell1=max(sell1,buy1+prices[i]buy2=max(buy2,sell1prices[i])sell2=max(sell2,buy2+prices[i])

边界条件:对于第0天,buy1=-prices[i], sell1表示当天买入一次卖出一次,因此sell1=0, buy2表示当天完成一次交易后又买入一次,因此buy2=-prices[i], sell2=0

返回值:最大利润肯定是取决于卖出后的利润,假设完成一次交易的利润最大,那么可以在当天再进行一次交易,这样sell1的状态就能够转移到sell2的状态,因此最终返回sell2

class Solution {
    public int maxProfit(int[] prices) {
        int buy1=-prices[0],sell1=0;
        int buy2=-prices[0],sell2=0;
        for(int i=1;i<prices.length;i++){
            buy1=Math.max(buy1,-prices[i]);//(未进行操作,当天买入)
            sell1=Math.max(sell1,buy1+prices[i]);//(未进行操作,上一次买入后卖出)
            buy2=Math.max(buy2,sell1-prices[i]);//(未进行操作,上一次卖出后买入)
            sell2=Math.max(sell2,buy2+prices[i]);//(未进行操作,上一次买入后卖出)
        }
        return sell2;
    }
}
//O(n)
//O(1)

188. 买卖股票的最佳时机 IV

https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/

在这里插入图片描述

思路:第一步考虑n/2和k的大小关系,如果k>=n/2, 相当于不受限制次数的交易,此时转化为122. 买卖股票的最佳时机 II, 对该问题我们可以使用贪心算法来解决; 当k<n/2时,使用动态规划

定义buy[i][j] 表示第i天进行第j次交易(第j次买入股票)时的最大利润
定义sell[i][j] 表示第i天进行第j次交易(第j次卖出股票)时的最大利润

b u y [ i ] [ j ] = m a x ( b u y [ i − 1 ] [ j ] , s e l l [ i − 1 ] [ j − 1 ] − p r i c e s [ i ] ) buy[i][j]=max(buy[i-1][j],sell[i-1][j-1]-prices[i]) buy[i][j]=max(buy[i1][j],sell[i1][j1]prices[i]): max(第i天不进行交易,在第i-1天第j-1次交易的基础上买入一支股票)

s e l l [ i ] [ j ] = m a x ( s e l l [ i − 1 ] [ j ] , b u y [ i ] [ j ] + p r i c e s [ i ] ) sell[i][j]=max(sell[i-1][j],buy[i][j]+prices[i]) sell[i][j]=max(sell[i1][j],buy[i][j]+prices[i]): max(第i天不进行交易,在第i天第j次交易的基础上买入一支股票)

class Solution {
    public int maxProfit(int k, int[] prices) {
        if (prices.length == 0) {
            return 0;
        }

        int n = prices.length;
        if(k>=n/2){
            return greedy(prices);
        }
        int[][] buy = new int[n][k + 1];
        int[][] sell = new int[n][k + 1];

        buy[0][0] = 0;
        sell[0][0] = 0;
        for (int i = 1; i <= k; ++i) {
            buy[0][i] = -prices[0];
            sell[0][i]=0;
        }

        for (int i = 1; i < n; ++i) {
            for (int j = 1; j <= k; ++j) {
                buy[i][j] = Math.max(buy[i - 1][j], sell[i - 1][j-1] - prices[i]);
                sell[i][j] = Math.max(sell[i - 1][j], buy[i][j]+prices[i]); 
                //第i天进行了第j次交易的买入->第i天进行了第j次交易的卖出
                // buy[i][j]+prices[i]-->sell[i][j]  
                //第i-1天进行了第j次交易的买入->第i天进行了第j次交易的卖出
                //buy[i - 1][j]+prices[i]-->sell[i][j]
                //由于buy[i][j]>=buy[i - 1][j]  因此这里只需要写buy[i][j]+prices[i]
                
            }
        }

        return Arrays.stream(sell[n - 1]).max().getAsInt();
    }
    public int greedy(int[] prices){
        int ans=0;
        for(int i=1;i<prices.length;i++){
            if(prices[i]-prices[i-1]>0){
                ans+=prices[i]-prices[i-1];
            }
        }
        return ans;
    }
}
//O(nk)  走greedy的时间是n  不走的时间是nk
//O(nk)  走greedy的时间是1  不走的时间是nk



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CodePanda@GPF

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

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

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

打赏作者

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

抵扣说明:

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

余额充值