JAVA 动态规划

 

 

300. 最长递增子序列

难度中等

 

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

O(N^2)  O(N)

class Solution {
    public int lengthOfLIS(int[] nums) {
        //dp[i] 前i个元素组成的最长上升子序列的长度(必须包含nums[i])
          int len =0,n  =nums.length;
          int dp[]  =new int[n+1];
          dp[len]  =1;  
          int maxx  =1;
          for(int i=1;i<n;i++){
               dp[i]  =1;
               for(int j=0;j<i;j++ ){
                   if(nums[i]>nums[j]){
                       dp[i]  =Math.max(dp[i],dp[j]+1);
                       maxx =Math.max(maxx,dp[i]);
                   }
               }
          }
          return maxx;
    }
}

 

class Solution {
    public int lengthOfLIS(int[] nums) {
        //希望递增子序列更长,表示该序列增加的更缓慢。也就是希望一定长度下的递增子序列的最后一个值更小
        //dp[l]: 长度为l的递增子序列的末尾最小值
        //0 2 1 7 :0 2 ,0 1,0 7,2 7 dp[2]=1
        //因此dp 递增,可以二分
        int n   = nums.length;
        int dp[]  =new int [n+1];
        int l=1;
        dp[l] = nums[0];
        for(int i =1;i<n;i++){
            if(nums[i]>dp[l]){
                dp[++l]  =nums[i];
            }
            else{
                int j=1,k=l,pos=0;
                while(j<=k){
                    int mid  =(j+k)>>1;
                    //在dp[1-l]中找到第一个比nums[i]大的数
                    if(nums[i]>dp[mid]){//只要小于nums[i]就记录
                        pos =mid;
                        j =mid+1;
                    }
                    else{
                        k =mid-1;
                    }
                }
                //pos+1就是dp[1-l]中找到第一个比nums[i]大的数的ID
                dp[pos+1]=nums[i];
            }
        }
        return l;
    }
}

 

1143. 最长公共子序列

难度中等

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

 

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length(), n = text2.length();
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            char c1 = text1.charAt(i - 1);
            for (int j = 1; j <= n; j++) {
                char c2 = text2.charAt(j - 1);
                if (c1 == c2) {
                    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[m][n];
    }
}

 

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

示例 1:

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
 

提示:

0 < grid.length <= 200
0 < grid[0].length <= 200

//DP

//grid[i][j] 以[i][j]为右下角的棋盘结果(礼物最大值)
class Solution {
    public int maxValue(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];//只能从左边过来
                else if(j==0)  grid[i][j]+=grid[i-1][j];
                else grid[i][j]+=Math.max(grid[i-1][j],grid[i][j-1]);
            }
        }
        return grid[grid.length-1][grid[0].length-1];
    }
}

//DFS

class Solution {
    int m,n;
    public int maxValue(int[][] grid) {
        this.m  = grid.length;
        this.n  =grid[0].length;
        int [][]men  =new int[m][n];//mem[i][j]从右下角到(i,j)的礼物最大值
         return dfs(grid,men,0,0,1);

    }
    public int dfs(int [][]grid,int [][]mem,int x,int y,int num){
        if(x>=m||y>=n) return 0;
        if(mem[x][y]!=0) return mem[x][y];//走过的不要重复,走过的一定是最优解了
        if(num==m+n)  return 0;//回溯 
        int down = dfs(grid,mem,x+1,y,num+1);
        int right = dfs(grid,mem,x,y+1,num+1);
        int ans  =grid[x][y]+Math.max(down,right);
        mem[x][y] =ans;
        return ans;
    }
}

 

 

 

剑指 Offer 49. 丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

 

思路参考:https://leetcode-cn.com/problems/chou-shu-lcof/solution/mian-shi-ti-49-chou-shu-dong-tai-gui-hua-qing-xi-t/

class Solution {
    public int nthUglyNumber(int n) {
         int []dp  =new int[n+1];
         dp[1]=1;
         int a=1,b=1,c=1;
         int num1,num2,num3;
         for(int i =2;i<=n;i++){
              numa = dp[a]*2;
              numb = dp[b]*3;
              numc = dp[c]*5;
              dp[i]  =Math.min(num1,Math.min(num2,num3));
              if(dp[i]==numa) a++;//第i个丑数是第a个丑数乘以2得到的,因此需要用第a+1个丑数乘以2来作为第i+1个丑数的可选项。
              //如果第i个丑数不是第a个丑数乘以2得到的,那么a不变
              if(dp[i]==numb) b++;
              if(dp[i]==numc) c++;
         }
         return dp[n];
    }
}

 

 

 

编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符
 

 

示例1

输入:
"abc","adc",5,3,2
返回值:
2

示例2

输入:
"abc","adc",5,3,100
返回值: 8
 
[0,i]---->[0.j]  dp[i][j]
所有的操作对象为word1
添加  
....i
.....j
dp[i][j]  =dp[i][j-1]+1
删除
......i
.....j
dp[i][j]  =dp[i-1][j]+1
交换
....i
....j
word1[i]==word2[j]  dp[i][j]  = dp[i-1][j-1]
else{
 dp[i][j]  = dp[i-1][j-1]+1
}
class Solution {
    
    public int minDistance(String word1, String word2) {
               word1 = " "+word1;
               word2  =" "+word2;
              int t =1;
               int l1=word1.length(),l2  =word2.length();
              // System.out.println(l1+" "+l2);
                  int dp[][]  =new int[l1][l2];
               for(int i =0;i<l1;i++) dp[i][0]  =i;//删除
               for(int i=0;i<l2;i++) dp[0][i]  =i;//添加
               for(int i =1;i<l1;i++) {
                   for(int j =1;j<l2;j++){
                       dp[i][j]  =Math.min(dp[i-1][j],dp[i][j-1])+1;
                      
                       if(word1.charAt(i)==word2.charAt(j)){
                           t  =0;
                       }
                       dp[i][j]  =Math.min(dp[i][j],dp[i-1][j-1]+t);
                       t  =1;
                   }
               }
            return dp[l1-1][l2-1];

    }
}

 

 
 

描述

给定两个字符串str1和str2,再给定三个整数ic,dc和rc,分别代表插入、删除和替换一个字符的代价,请输出将str1编辑成str2的最小代价。
 
数据范围:0 \le |str1|,|str2| \le 50000str1,str25000,0\le ic,dc,rc \le 10000\0ic,dc,rc10000 
要求:空间复杂度 O(n)O(n),时间复杂度 O(nlogn)O(nlogn)
 
import java.util.*;


public class Solution {
    /**
     * min edit cost
     * @param str1 string字符串 the string
     * @param str2 string字符串 the string
     * @param ic int整型 insert cost
     * @param dc int整型 delete cost
     * @param rc int整型 replace cost
     * @return int整型
     */
    
    
    public int minEditCost (String str1, String str2, int ic, int dc, int rc) {
    
    
  
               str1 = " "+str1;
               str2  =" "+str2;
              int t =0;
               int l1=str1.length(),l2  =str2.length();
                  int dp[][]  =new int[l1][l2];
               for(int i =0;i<l1;i++) dp[i][0]  =i*dc;//删除
               for(int i=0;i<l2;i++) dp[0][i]  =i*ic;//插入
               for(int i =1;i<l1;i++) {
                   for(int j =1;j<l2;j++){
                       dp[i][j]  =Math.min(dp[i-1][j]+dc,dp[i][j-1]+ic);
                       if(str1.charAt(i)!=str2.charAt(j)){
                           t  =rc;
                       }
                       dp[i][j]  =Math.min(dp[i][j],dp[i-1][j-1]+t);
                       t  =0;
                   }
               }
            return dp[l1-1][l2-1];

    }
}
   

 

滚动数组优化空间复杂度

import java.util.*;


public class Solution {
    /**
     * min edit cost
     * @param str1 string字符串 the string
     * @param str2 string字符串 the string
     * @param ic int整型 insert cost
     * @param dc int整型 delete cost
     * @param rc int整型 replace cost
     * @return int整型
     */
    
    
    public int minEditCost (String str1, String str2, int ic, int dc, int rc) {
    
    
  
               str1 = " "+str1;
               char s1 []  =str1.toCharArray();
               str2  =" "+str2;
               char s2 []  =str2.toCharArray();
              int t =0;
               int l1=s1.length,l2  =s2.length;
                  int dp[]  =new int[l2];
              // for(int i =0;i<l1;i++) dp[i][0]  =i*dc;//删除
               for(int i=0;i<l2;i++) dp[i]  =i*ic;//插入
        int pre,tmp;
               for(int i =1;i<l1;i++) {
                      
                    pre  =dp[0];//dp[i-1][0]
                     dp[0] = i*dc;//dp[i][0]
                   for(int j =1;j<l2;j++){
                       
                       tmp =dp[j];//dp[i-1][j]
                     //  dp[i][j]  =Math.min(dp[i-1][j]+dc,dp[i][j-1]+ic);
                        dp[j]  =Math.min(tmp+dc,dp[j-1]+ic);//dp[j-1]:dp[i][j-1]
                       if(str1.charAt(i)!=str2.charAt(j)){
                           t  =rc;
                       }
                       //dp[i][j]  =Math.min(dp[i][j],dp[i-1][j-1]+t);
                       dp[j]  =Math.min(dp[j],pre+t);
                       
                       t  =0;
                       pre = tmp;//pre :对于下个j来说就是dp[i-1][j-1]
                   }
               }
            return dp[l2-1];

    }
}
   

 

 

剑指 Offer 48. 最长不含重复字符的子字符串

难度中等

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

 

输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
  请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

// dp[j]:以s[j]结尾的最长的不包含重复字符的子串长度
// j:当前id, i距离s[j]最近的字符(s[i]==s[j])
// 1.i<0  表示前面没有和s[j]一样的字符 
// 2.dp[j-1]>=j-i 表示i在dp[j-1]的字符串内部 
//       i j-1 j
//   p s a p   a 
//  那么dp[j]   = j-i
// 3.dp[j-1]<j-i 表示i在dp[j-1]的字符串外部 
//      i      j-1 j
//      a  p s  p  a
// 那么dp[j]  =dp[j-1]+1
优化空间复杂度 tmp :dp[]
class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character,Integer>map = new HashMap();
           int tmp =0,maxx=0;
           int n  =s.length();
           for(int j=0;j<n;j++){
               int i  = map.getOrDefault(s.charAt(j),-1);// s[i]==s[j]i距离j最近
               map.put(s.charAt(j),j);
               tmp  =tmp <j-i?tmp+1:j-i;
               maxx  =Math.max(maxx,tmp);
           }
           return maxx;
    }
}

 

请实现一个函数用来匹配包含'. ''*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a""ab*ac*a"匹配,但与"aa.a""ab*a"均不匹配。

 

 

 

 

  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'
class Solution {
    public boolean isMatch(String s, String p) {
        int ls  =s.length(),lp =p.length();
        boolean dp[][]  =new boolean[ls+1][lp+1];//dp[i][j]  表示p的前j个字符和s的前i个字符是否匹配
        // 从后往前
        // p的每个字符可能有三种情况:(1)正常字符,(2)'.',(3)'*'
        // (1)+(2)
        //  如果 s[i-1]==p[j-1] ||p[j-1]=='.'
        //   dp[i][j]   = dp[i-1][j-1];(i>=1&&j>=1)
        // (3)
        // 前面字符可以重复0次或者多次
        // 1. 0次  
        //  dp[i][j]|=dp[i][j-2];  (j>=2)
        // 2. 多次
        //  如果 s[i-1]==p[j-2]||p[j-2]=='.'
        //  dp[i][j]|=dp[i-1][j];  (i>=1&&j>=2)
        //  (3)用的是| 表示情况1和情况2都不行才表明当前不成立
        //  初始条件:dp[0][0]=1 ,dp[i][0]=0(i>0)
                  
        for(int i=0;i<=ls;i++){
        for(int j=0;j<=lp;j++){
            if(j==0)  dp[i][j]= i==0;
            else{
                if(p.charAt(j-1)!='*'){
                     if(i>=1&&(s.charAt(i-1)==p.charAt(j-1)||p.charAt(j-1)=='.')){
                             dp[i][j]   = dp[i-1][j-1];
                     }
                }
                else{
                    //两个if, 先不用c* ,再用c* 
                    // 0|0 ==0 其他都是1
                    if(j>=2)  {
                        dp[i][j]|=dp[i][j-2];                 
                    }      
                    if(i>=1&&j>=2&&(s.charAt(i-1)==p.charAt(j-2)||p.charAt(j-2)=='.')){
                            dp[i][j]|=dp[i-1][j];                             
                        }               
                }
            }
        }
        }
        return dp[ls][lp];
    }
}

 

312. 戳气球

难度困难

有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

求所能获得硬币的最大数量。

题解

题目是一个个的删除元素,但是一旦删除某个元素,便直接影响元素的相对位置

因此,一个个的加元素 逆操作

3 2  5 --> 1 3 2 5 1

 按照 3 2 5  的顺序删除,也就是按照 5 2  3 的顺序添加

 

 

 

 

  solve(i,j) 开区间 把区间(i,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];
     for(int i=1;i<n+1;i++)  val[i]  =nums[i-1];
     val[0]=val[n+1]=1;
     for(int i=0;i<n+2;i++)
       Arrays.fill(ans[i],-1);
     return solve(0,n+1);

    }
    public int solve(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+=solve(left,i)+solve(i,right);
            ans[left][right]  =Math.max(ans[left][right],sum);
        }
        return ans[left][right];
    }
}

 时间复杂度 O(N^3)

 空间复杂度 O(N^2)

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];
     for(int i=1;i<n+1;i++)  val[i]  =nums[i-1];
     val[0]=val[n+1]=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+=ans[i][k]+ans[k][j];
                   ans[i][j] = Math.max(ans[i][j],sum);
               }
           }
       }
       return ans[0][n+1];
    }
}

 

42. 接雨水

难度困难

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

 

 i位置处的雨水:min(i左侧最大高度,i右侧最大高度)-height[i]

暴力

O(N^2) O(1)

class Solution {
    public int trap(int[] height) {
         int n = height.length;
         int a[] = new int[n+1];
         int maxx1=0,maxx2=0;
         int sum  =0;
         for(int i=1;i<n-1;i++){
             for(int j=0;j<i;j++) {
                 if(maxx1<height[j])  maxx1  =height[j];
             }
             for(int k=i+1;k<n;k++) {
                 if(maxx2<height[k])  maxx2=height[k];
             }
             int p  =Math.min(maxx1,maxx2)-height[i];
             sum+=p>0?p:0;
             maxx1=0;maxx2=0;
         }             
         return sum;
    }
}

Dp

O(N)O(N)

class Solution {
    public int trap(int[] height) {
           int n  =height.length;
           int dp1[]   =new int[n+1];
           int dp2[]  =new int[n+1];
           for(int i=1;i<=n-2;i++){
               if(height[i-1]>dp1[i])  dp1[i] = height[i-1];
               if(dp1[i-1]>dp1[i])  dp1[i]  =dp1[i-1];
           }
           for(int i=n-2;i>=1;i--){
               if(height[i+1]>dp2[i])  dp2[i]  =height[i+1];
               if(dp2[i+1]>dp2[i])  dp2[i]  =dp2[i+1];
           }
           int sum=0;
           for(int i=1;i<=n-2;i++){
               int p  =Math.min(dp1[i],dp2[i])-height[i];
               sum+=p>0?p:0;
               
           }
          
           return sum;

    }
}

双指针

O(N)O(1)

class Solution {
    public int trap(int[] height) {
        int n  =height.length;
         int l=0,r=n-1;  
         int sum=0;
         int lmax=0,rmax=0;
        //  iL i维护lmax iR i维护rmax
        //  jL  j维护lmax jR j维护rmax
        //  如果j>i 肯定 iL<=jL ,iR>=jR 
        //  == 两边都可以 不影响指针移动 所以我们只分析不等的情况
        //  如果 height[i]<height[j]
         //i++时,j不变 height[j]就是jR
        //      (1)i++导致,j在i++的阶段保持不变。因此当前i之前不可能存在大于height[j]的数,又因为height[j]就是jR
        //         因此 jR>iL  -->iR>iL -->i处加雨水
        //      (2)j++导致,i在j++的阶段保持不变。height[j]是j右边最大的数,height[j]是jR.又因为height[i]就是iL
        //         因此 jR>iL  -->iR>iL  -->i处加雨水
        //  height[j]<height[i] 同理


         while(l<r){
             if(height[l]<=height[r]) {//==放在if 和else 都可以
                 if(lmax<height[l])  lmax  =height[l];
                 sum+=lmax-height[l];
                 l++;
             }
             else{
                 if(rmax<height[r]) rmax = height[r];
                 sum+=rmax-height[r];
                 r--;
             }
         }
        return sum;
        }
}

 

 数位DP

902. 最大为 N 的数字组合

难度困难

给定一个按 非递减顺序 排列的数字数组 digits 。你可以用任意次数 digits[i] 来写的数字。例如,如果 digits = ['1','3','5'],我们可以写数字,如 '13''551', 和 '1351315'

返回 可以生成的小于或等于给定整数 n 的正整数的个数 。

 

示例 1:

输入:digits = ["1","3","5","7"], n = 100
输出:20
解释:
可写出的 20 个数字是:
1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77.

示例 2:

输入:digits = ["1","4","9"], n = 1000000000
输出:29523
解释:
我们可以写 3 个一位数字,9 个两位数字,27 个三位数字,
81 个四位数字,243 个五位数字,729 个六位数字,
2187 个七位数字,6561 个八位数字和 19683 个九位数字。
总共,可以使用D中的数字写出 29523 个整数。

示例 3:

输入:digits = ["7"], n = 8
输出:1

class Solution {
    public int atMostNGivenDigitSet(String[] digits, int n) {
          String s =  String.valueOf(n);
          int k = s.length();
          int dp[]  =new int[k+1];
          dp[k]=1;//1
          int l = digits.length;
          for(int i=k-1;i>=0;i--){
               int num  = s.charAt(i)-'0';
              for(String d:digits){
                  if(Integer.valueOf(d)<num){
                      dp[i]+=Math.pow(l,k-1-i);//小于num 表示 从第i+1位到第k-1位 随意用
                  }
                  else if(Integer.valueOf(d)==num){
                      dp[i]+=dp[i+1];//依赖dp[i+1]
                  }
              }
            //    System.out.println(i+" "+dp[i]); 
          }
          for(int i=1;i<k;i++){
              dp[0]+=Math.pow(l,i);
          }
          return dp[0];
    }
}

 

posted on 2021-12-31 11:49  cltt  阅读(96)  评论(0编辑  收藏  举报

导航