动态规划记录

11 篇文章 0 订阅

动态规划

  • 最优子结构
  • 边界
  • 状态转移公式

1.最大子序和

题目:

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

思路:

因为是要求的连续子序列,从前往后遍历,最大和一定为当前元素值加上之前的最大和,dp[n]表示为到下标n为止的最大和,则dp[n]=num[n]+Math.max(dp[n-1],0)

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

2.路径问题

最小路径和
链接
题目:

在这里插入图片描述
思路:
在这里插入图片描述

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

求共有多少不同路径

题目:
在这里插入图片描述
此题和上题解法类似,使用二维数组dp[i][j]表示到达[i][j]网格处的路径数目,那么当i>0&&j>0时,dp[i][j]=dp[i-1][j]+dp[i][j-1]

    public int uniquePaths(int m, int n) {
        int[][] dp=new int[m][n];

        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(i==0&&j==0)dp[i][j]=1;
                else if(i==0)dp[i][j]=dp[i][j-1];
                else if(j==0)dp[i][j]=dp[i-1][j];
                else{
                    dp[i][j]=dp[i-1][j]+dp[i][j-1];
                }
            }
        }
        return dp[m-1][n-1];
    }

3.最长回文字符串

在这里插入图片描述
思路
dp[j][k]表示从j到k行程的子字符串是否为回文字符串,首先易得如果首尾字符串不相等,此字符串必定不是回文
在这里插入图片描述

    public String longestPalindrome(String s) {
        int len=s.length();
        if(len<2)
            return s;
        int maxLen=1;
        int begin=0;
        boolean [][] dp=new boolean[len][len];
        //遍历字符串每一个区间
        for(int k=0;k<len;k++){
            for(int j=0;j<=k;j++){
            	//如果不相等必定不是回文
                if(s.charAt(j)!=s.charAt(k))
                    dp[j][k]=false;
                else{
                    if(k<j+3)
                        dp[j][k]=true;
                    else
                        dp[j][k]=dp[j+1][k-1];
                }
                //记录最长的回文长度和起始位置
                if(dp[j][k]&&k-j+1>maxLen){
                    maxLen=k-j+1;
                    begin=j;
                }
            }
        }
        return s.substring(begin,begin+maxLen);
    }

回文子串
在这里插入图片描述
用dp[i][j]代表从i到j的子串是否为回文子串

public int countSubstrings(String s) {
        boolean [][] dp=new boolean[s.length()][s.length()];
        int ans=0;
        int len=s.length();
        for(int j=0;j<len;j++){
            for(int i=0;i<=j;i++){
                if(s.charAt(i)==s.charAt(j)&&(j-i<2||dp[i+1][j-1])){
                    dp[i][j]=true;
                    ans++;
                }
            }
        }
        return ans;
    }

4.股票问题

买股票的最佳时机
在这里插入图片描述

    public int maxProfit(int[] prices) {
        if(prices==null||prices.length==0)
            return 0;
        int max=0;
        int min=prices[0];
        for(int i=0;i<prices.length;i++){
            max=Math.max(max,prices[i]-min);
            min=Math.min(min,prices[i]);
        }
        return max;
    }

买卖股票的最佳时机II
在这里插入图片描述
用贪心算法,因为可以做无限次交易,那么我们可以将每次可能的利润都获取
在这里插入图片描述

    public int fun4(int[] prices){
        int res=0;
        for (int i = 0; i <prices.length-1 ; i++) {
            int temp=prices[i+1]-prices[i];
            if(temp>0)res+=temp;
        }
        return res;
    }

买卖股票的最佳时机III
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

    public int maxProfit(int[] prices) {
        int[][] dp=new int[prices.length][5];
        dp[0][1]=-prices[0];
        dp[0][3]=-prices[0];
        for(int i=1;i<prices.length;i++){
            dp[i][0]=dp[i-1][0];
            dp[i][1]=Math.max(dp[i-1][0]-prices[i],dp[i-1][1]);
            dp[i][2]=Math.max(dp[i-1][1]+prices[i],dp[i-1][2]);
            dp[i][3]=Math.max(dp[i-1][2]-prices[i],dp[i-1][3]);
            dp[i][4]=Math.max(dp[i-1][3]+prices[i],dp[i-1][4]);
        }
        return dp[prices.length-1][4];
    }

最佳买卖股票时机含冷冻期

在这里插入图片描述
在这里插入图片描述

    public int maxProfit(int[] prices) {
        if(prices.length==0)return 0;
        int[][] dp=new int[prices.length][3];
        dp[0][0]=-prices[0];
        //dp[i][0]:手上持有股票的最大收益
        //dp[i][1]:手上不持有股票且第i+1天是冷冻期的最大收益
        //dp[i][2]:手上不持有股票且第i+1天不是冷冻期的最大收益
        for(int i=1;i<prices.length;i++){
            dp[i][0]=Math.max(dp[i-1][0],dp[i-1][2]-prices[i]);
            dp[i][1]=dp[i-1][0]+prices[i];
            dp[i][2]=Math.max(dp[i-1][2],dp[i-1][1]);
        }
        int max=Math.max(dp[prices.length-1][1],dp[prices.length-1][2]);
        return max;
    }

JZ67

题目:

给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

思路:

这题目用动态规划。首先题目要求至少切一次。当target<=3时可以明确得出最大乘积,当target>3后,dp[n]=max(dp[n-i]*dp[i])。假设n为10,第一刀之后分为了4-6,而6也可能再分成2-4(6的最大是3-3,但过程中还是要比较2-4这种情况的),而上一步4-6中也需要求长度为4的问题的最大值,可见,各个子问题之间是有重叠的,所以可以先计算小问题,存储下每个小问题的结果,逐步往上,求得大问题的最优解

public class Solution {
    public int cutRope(int target) {
        if(target<2)
            return 0;
        if(target==2)
            return 1;
        if(target==3)
            return 2;
        int[]dp=new int[target+1];
        dp[1]=1;
        dp[2]=2;
        dp[3]=3;
        int max=0;
        for(int i=4;i<=target;i++){
            for(int j=1;j<=i/2;j++){
                max=max>(dp[j]*dp[i-j])?max:(dp[j]*dp[i-j]);
            }
            dp[i]=max;
        }
        return dp[target];
    }
}

最长公共子串

在这里插入图片描述

import java.util.*;
public class Solution {
    public String LCS (String str1, String str2) {
        int m = str1.length(), n = str2.length();
        //dp[i][j]代表str1[0~m-1]和str2[0~n-1]的最大公共子串的长度
        int[][] dp = new int[m + 1][n + 1];
        int max = 0, index = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (str1.charAt(i) == str2.charAt(j)) {
                    dp[i + 1][j + 1] = dp[i][j] + 1;
                    if (max < dp[i + 1][j + 1]) {
                        max = dp[i + 1][j + 1];
                        index = i + 1;
                    }
                }
            }
        }
        return max == 0 ? "-1" : str1.substring(index-max,index);
    }
}

dp[i][j]代表str1[0~m]和str2[0-n]的最大公共子串的长度,这里要求公共子串必须以str1[m]=str2[n]结尾

背包问题********************************************

问题背景
有一个空间有限(设空间为W)的背包和一堆物品,每个物品有两个属性,一个是占据的空间w,一个是价值v。我们要解决的问题就是在有限的空间W内,将物品装入背包,并且使总价值尽可能的大。背包问题按大类分为三大类:

  • 01背包
  • 多重背包
  • 完全背包

最常见的问题问法有求方案数,求最优方案数,求最大价值,求能不能满足某一条件等等。接下来让我们先分别来看看这些问题的原型
01背包
01背包是指物品的个数都为1,只能使用一次。这类背包首先建立一个二维数组,dp[i][j],表示前i件物品在体积不超过j的前提下的最大价值。其中对于每一个物品的体积用一维数组表示,每一个物品的价值也用一维数组表示。对于每一个物品有两个状态,一个是添加到背包,一个是 没有添加到背包,两种情况满足以下条件:

  • 第i件物品没有添加到背包,前i件物品体积不超过j的最大价值就是体积不超过j的前i-1物品的最大价值,即dp[i][j]=dp[i-1][j]
  • 第i件物品添加进了背包,直接在前i-1件物品在空间不超过j-w[i]的最大价值上加上v[i],即dp[i][j]=dp[i-1][j-w]+v;

以上两种情况的选择取决于谁的价值更大,用公式表示就是
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w]+v)

public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //背包总重
        int cap =Integer.parseInt(sc.nextLine().trim());
        String str1 = "1,2,3";
        String str2 = "6,10,12";
        String[] weights = str1.trim().split(",");
        String[] values = str2.trim().split(",");
        int len = weights.length;
        int[] weight = new int[len + 1];
        int[] value = new int[len + 1];
        for (int i = 0; i < len; i++) {
            weight[i + 1] = Integer.parseInt(weights[i]);
        }
        for (int i = 0; i < len; i++) {
            value[i + 1] = Integer.parseInt(values[i]);
        }
        int[][] dp = new int[len + 1][cap + 1];
        for (int i = 1; i <= len; i++) {
            for (int j = 1; j <= cap; j++) {
                if (weight[i] > j) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
                }

            }
        }
    }

完全背包
完全背包和01背包不同点在于,完全背包中的物品可以无限次的取

 /**
     *
     * @param W 背包的总体积
     * @return
     */
    public int func(int W, int N) {
        int[] weight = {1, 3, 3, 6};
        int[] value = {2, 5, 3, 1};
        int N=weight.length;
        //dp[i][j]表示前i件物品在体积不超过j的情况下的最大价值
        int[][] dp = new int[N + 1][W + 1];
        for (int i = 0; i < N; i++) {
            int w = weight[i-1], v = value[i-1];
            for (int j = 1; j <= W; j++) {
                if(j>=w){
                	//完全背包和01背包的差别在此,请注意
                    dp[i][j]=Math.max(dp[i-1][j],dp[i][j-w]+v);
                }else
                    dp[i][j]=dp[i-1][j];
            }
        }https://leetcode-cn.com/problems/coin-change/
    }

在这里插入图片描述

在这里插入图片描述

零钱兑换

题目链接
在这里插入图片描述
dp[i]表示组成面值i所需要的最小硬币数

import java.util.*;
class Solution {
    public int coinChange(int[] coins, int amount) {
       int max=amount+1;
       int[] dp=new int[amount+1];
       //给数组所有元素都赋值max
       Arrays.fill(dp,max);
       dp[0]=0;
       for(int i=1;i<=amount;i++){
           for(int j=0;j<coins.length;j++){
               if(i>=coins[j]){
                   dp[i]=Math.min(dp[i],dp[i-coins[j]]+1);
               }
           }
       }
       //如果大于account就说明硬币无法凑成account,返回-1
       return dp[amount]>amount?-1:dp[amount];
    }
}

完全平方数

在这里插入图片描述

在这里插入图片描述

   public int numSquares(int n) {
       if(n<=2)return n;
       int[] dp=new int[n+1];
       for(int i=1;i<=n;i++){
       		//最坏情况为i个,即全为1
           dp[i]=i;
           for(int j=1;i-j*j>=0;j++){
           		//状态转移方程
               dp[i]=Math.min(dp[i],dp[i-j*j]+1);
           }
       } 
       return dp[n];
    }

打家劫舍三部曲*********************************************

在这里插入图片描述

    public int rob(int[] nums) {
        if(nums.length<=1)return nums.length==0?0:nums[0];
        int[] dp=new int[nums.length];
        int max=Math.max(nums[0],nums[1]);
        for(int i=0;i<nums.length;i++){
            if(i==0){dp[i]=nums[i];continue;}
            if(i==1){dp[i]=Math.max(nums[0],nums[1]);continue;}
            //状态转移方程
            dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
            max=Math.max(max,dp[i]);
        }
        return max;
    }

在这里插入图片描述

此题为环装,将其分为三种情况后,和上题用一样的方法即可

 public int rob(int[] nums) {
        if(nums.length<=1)return nums.length==0?0:nums[0];
        /**
        1.不偷头,不偷尾巴
        2.偷头,不偷尾
        3.不偷头,偷尾    
         */
        int[] nums1=Arrays.copyOfRange(nums,1,nums.length-1);
        int[] nums2=Arrays.copyOfRange(nums,0,nums.length-1);
        int[] nums3=Arrays.copyOfRange(nums,1,nums.length);
        return Math.max(helper(nums1),Math.max(helper(nums2),helper(nums3)));
    }
    public int helper(int[] nums){
        if(nums.length<=1)return nums.length==0?0:nums[0];
        int max=Math.max(nums[1],nums[0]);
        int[] dp=new int[nums.length];
        for(int i=0;i<nums.length;i++){
            if(i==0){dp[i]=nums[i];continue;}
            if(i==1){dp[i]=Math.max(nums[1],nums[0]);continue;}
            dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
            max=Math.max(max,dp[i]);
        }
        return max;
    }

在这里插入图片描述

public int rob(TreeNode root) {
        /**
            res数组
            res[0] 表示抢劫当前节点的最大值
            res[1] 表示不抢劫当前节点的最大值
         */
        int[] res=dp(root);
        return Math.max(res[1],res[0]);
    }
    public int[] dp(TreeNode root){
        if(root==null)return new int[]{0,0};
        int[] left=dp(root.left);
        int[] right=dp(root.right);
        int select=root.val+left[1]+right[1];
        int notSelect=Math.max(left[1],left[0])+Math.max(right[1],right[0]);
        return new int[]{select,notSelect};
    }

在这里插入图片描述
在这里插入图片描述

    public int translateNum(int num) {
        if(num==0)return 1;
        String s=String.valueOf(num);
        int[] dp=new int[s.length()];
        dp[0]=1;
        for(int i=1;i<s.length();i++){
           if(s.substring(i-1,i+1).compareTo("10")>=0&&s.substring(i-1,i+1).compareTo("25")<=0){
               if(i==1)
                    dp[i]=2;
                else
                    dp[i]=dp[i-2]+dp[i-1];
           }              
            else
                dp[i]=dp[i-1];
        }
        return dp[s.length()-1];
    }

环形数组的最大和

在这里插入图片描述

如果使用到了环,则求出nums[1]-nums[len-2]的最小值
用sum-min即可得到使用环情况下的最大值

    public int maxSubarraySumCircular(int[] nums) {
        if(nums.length==1)return nums[0];
        int sum=0;
        for(int i=0;i<nums.length;i++)
            sum+=nums[i];
        int[] dp1=new int[nums.length];
        int[] dp2=new int[nums.length];
        dp1[0]=nums[0];
        dp2[0]=nums[0];
        int min=dp1[0];
        int max=dp2[0];
        for(int i=1;i<nums.length;i++){
            dp2[i]=Math.max(dp2[i-1]+nums[i],nums[i]);
            max=Math.max(max,dp2[i]);
        }
        //如果使用到了环,则求出nums[1]-nums[len-2]的最小值
        //用sum-min即可得到使用环情况下的最大值
        for(int i=1;i<nums.length-1;i++){
            dp1[i]=Math.min(dp1[i-1]+nums[i],nums[i]);
            min=Math.min(min,dp1[i]);
        }
        return Math.max(max,sum-min);
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值