常见面试算法题(动态规划-经典题)

184 阅读2分钟

动态规划三要素

三步走:

  1. 定义dp含义
  2. 找好边界条件
  3. 找到递推关系

1. 爬楼梯

leetcode-cn.com/problems/cl…

class Solution {
    public int climbStairs(int n) {
        if(n < 2) return 1;
        // dp[n] 表示爬上第n节楼梯有多少种方法
        int[] dp = new int[n+1];
        dp[0] = 1;
        dp[1] = 1;
        
        //dp[n] = dp[n-1] + dp[n-2]
        for(int i = 2; i <=n ; i++) dp[i] = dp[i-1] + dp[i-2];
        return dp[n];
    }
}

2. 最大子序和

leetcode-cn.com/problems/ma…


class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        if(n == 0) return 0;
        // dp[i] 表示以nums[i]结尾的子数组之和
        int[] dp = new int[n];
        dp[0] = nums[0];
        int res = Integer.MIN_VALUE;
        for(int i = 1 ; i < n ; i++){
            dp[i] = Math.max(dp[i-1] , 0) + nums[i];
        }
        //找到dp数组中的最大值
        for(int num : dp) res = Math.max(res , num);
        return res;

    }
}

class Solution {
    public int maxSubArray(int[] nums) {
        int res = Integer.MIN_VALUE;
        int sum = 0;
        for(int num : nums){
            if(sum > 0) sum += num;
            else sum = num;

            res = Math.max(res , sum);
        }
        return res;
    }
}

3.乘积最大子数组

leetcode-cn.com/problems/ma…

class Solution {
    public int maxProduct(int[] nums) {
        int n = nums.length;
        if(n == 0) return 0;
        int res = Integer.MIN_VALUE;
        int min = 1 , max = 1;
        for(int i = 0 ; i < n ; i++){
            if(nums[i] < 0){
                int tem = min;
                min = max;
                max = tem;
            }

            min = Math.min(nums[i] , nums[i]*min);
            max = Math.max(nums[i] , nums[i]*max);
            res = Math.max(res , max);
        }
        return res;

    }
}

3.不同路径

leetcode-cn.com/problems/un…

class Solution {
    public int uniquePaths(int m, int n) {
        if(m == 0 || n == 0) return 1;
        int[][] dp = new int[m][n];
        dp[0][0] = 1;
        for(int i = 0 ; i < n ; i++) dp[0][i] = 1;
        for(int i = 0 ; i < m ; i++) dp[i][0] = 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];
    }
}

4.不同路径2

leetcode-cn.com/problems/un…

class Solution {
    public int uniquePathsWithObstacles(int[][] matrix) {
        int row = matrix.length;
        int col = matrix[0].length;
        if(row == 0 || col == 0) return 0;
        int[][] dp = new int[row][col];

        for(int i = 0 ; i < row ; i++) {
            if (matrix[i][0] != 1) dp[i][0] = 1;
            else break;
        }

        for(int i = 0 ; i < col ; i++){
            if(matrix[0][i] != 1) dp[0][i] = 1;
            else break;
        }

        for(int i = 1 ; i < row ; i++){
            for(int j = 1 ;j < col ; j++){
                if(matrix[i][j] != 1) dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[row-1][col-1];

    }
}

5.最小路径和

leetcode-cn.com/problems/mi…

class Solution {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        if(m == 0 || n == 0) return 0;
        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];
    }
}

6.三角形最小路径和

leetcode-cn.com/problems/tr…

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int row = triangle.size();
        int col = triangle.get(row-1).size();
        int[][] dp = new int[row][col];

        dp[0][0] = triangle.get(0).get(0);
        for(int i = 1 ; i < row ; i++){
            for(int j = 0; j <= i ;j++){
                
                if(j == i) dp[i][j] = dp[i-1][j-1] + triangle.get(i).get(j);
                else if(j == 0) dp[i][j] = dp[i-1][j] + triangle.get(i).get(j);
                else dp[i][j] = Math.min(dp[i-1][j-1] , dp[i-1][j]) + triangle.get(i).get(j);

            }
        }

        int res = Integer.MAX_VALUE;
        for(int i = 0 ; i < col ; i++){
            res = Math.min(res , dp[row-1][i]);
        }
        return res;

    }
}

7.打家劫舍

leetcode-cn.com/problems/ho…

class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if(n == 0) return 0;
        if(n == 1) return nums[0];
        //dp[i] 表示偷nums[i]家的最大金额
        int[] dp = new int[n];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0] , nums[1]);
        for(int i = 2 ; i < n ; i++){
            dp[i] = Math.max(dp[i-1] , dp[i-2] + nums[i]);
        }
        return dp[n-1];
    }
}

8.打家劫舍2

leetcode-cn.com/problems/ho…

class Solution {
    public int rob(int[] nums) {
        int n  = nums.length;
        if(n == 0) return 0;
        if(n == 1) return nums[0];
        if(n == 2) return Math.max(nums[0],nums[1]);

        //偷第一个,不偷最后一个
        int[] dp = new int[n];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0] , nums[1]);
        for(int i = 2;  i < n-1 ;i++) dp[i] = Math.max(dp[i-1],dp[i-2] + nums[i]);
        int res = dp[n-2];

        //不偷第一个,偷最后一个
        int[] dp1 = new int[n];
        dp1[0] = 0;
        dp1[1] = nums[1];
        dp1[2] = Math.max(nums[1] , nums[2]);
        for(int i = 2 ; i < n ; i++) dp1[i] = Math.max(dp1[i-1] , dp1[i-2] + nums[i]);
        int res1 = dp1[n-1]; 
        return Math.max(res , res1);

    }
}

9.编辑距离

leetcode-cn.com/problems/ed…


class Solution {
    public int minDistance(String s, String p) {
        int ls = s.length();
        int lp = p.length();
        // dp[i][j] 表示s[0-i] 到 p[0-j] 的距离
        int[][] dp = new int[ls+1][lp+1];
        dp[0][0] = 0;

        for(int i = 0 ; i <= ls ; i++) dp[i][0] = i;
        for(int j = 0 ; j <= lp ; j++) dp[0][j] = j;

        for(int i = 1; i <= ls ; i++){
            for(int j = 1 ;j <= lp ;j++){
                if(s.charAt(i-1) == p.charAt(j-1)) dp[i][j] = dp[i-1][j-1];
                else dp[i][j] = Math.min(dp[i-1][j-1] , Math.min(dp[i-1][j] , dp[i][j-1]))+1; 
            }
        }
        return dp[ls][lp];
    }
}

10.解码方法

leetcode-cn.com/problems/de…

class Solution {
    public int numDecodings(String s) {
         int n = s.length();
        if (n == 0) return 0;
        int[] dp = new int[n+1];
        dp[0] = 1;
        for (int i = 1; i <= n ; i++) {
            //1. 因为没有0对应的字母,所以如果最后一个是 0 的话 直接加上前面一个的个数即可
            if (s.charAt(i-1) != '0') dp[i] += dp[i-1];
            if (i >= 2){
                //2. 如果最后一个数是10-26之间的话,说明除了dp[i-1] 外还可以 加上dp[i-2]
                int num = Integer.parseInt(s.substring(i-2 , i));
                if (num >= 10 && num <= 26) dp[i] += dp[i-2];
            }
        }
        return dp[n];
    }
}

11.扔鸡蛋 -- 反向dp

leetcode-cn.com/problems/su…

class Solution {
    public int superEggDrop(int K, int N) {
//         dp[k][m] = n
// # 当前有 k 个鸡蛋,可以尝试扔 m 次鸡蛋
// # 这个状态下,最坏情况下最多能确切测试一栋 n 层的楼

// # 比如说 dp[1][7] = 7 表示:
// # 现在有 1 个鸡蛋,允许你扔 7 次;
// # 这个状态下最多给你 7 层楼,
// # 使得你可以确定楼层 F 使得鸡蛋恰好摔不碎
// # (一层一层线性探查嘛)

        // //最终要确定扔鸡蛋的次数,也就是m
        // int m = 0;
        // while(dp[k][m] <  N){
        //     m++;
        //     //递推公式
        //     //1、无论你在哪层楼扔鸡蛋,鸡蛋只可能摔碎或者没摔碎,碎了的话就测楼下,没碎的话就测楼上。
        //     //2、无论你上楼还是下楼,总的楼层数 = 楼上的楼层数 + 楼下的楼层数 + 1(当前这层楼)
        //     // dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1
        //     //dp[k][m - 1] 就是楼上的楼层数,因为鸡蛋个数 k 不变,也就是鸡蛋没碎,扔鸡蛋次数 m 减一;
        //     //dp[k - 1][m - 1] 就是楼下的楼层数,因为鸡蛋个数 k 减一,也就是鸡蛋碎了,同时扔鸡蛋次数 m 减一

        // }
        // return m;  //结束条件是dp[K][m] == N

        // m 最多不会超过 N 次(线性扫描)
        int[][] dp = new int[K + 1][N + 1];
        // base case:
        // dp[0][..] = 0
        // dp[..][0] = 0
        // Java 默认初始化数组都为 0
        int m = 0;
        while (dp[K][m] < N) {
            m++;
            for (int k = 1; k <= K; k++)
                dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1;
        }
        return m;
    }
}

第二部分、零钱兑换问题

1.零钱兑换

leetcode-cn.com/problems/co… 每种硬币的数量是无限的

class Solution {
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        //凑出amount+1的方法数
        int[] dp = new int[amount+1];
        Arrays.fill(dp , amount+1);
        dp[0] = 0;
        for(int i = 1 ; i <= amount ; i++){
            for(int coin : coins){
                if(coin <= i) dp[i] = Math.min(dp[i] , dp[i-coin]+1);
            }
        }
        return dp[amount] == amount+1 ? -1 : dp[amount];

    }
}

2.零钱兑换2

leetcode-cn.com/problems/co…

class Solution {
    public int change(int amount, int[] coins) {
        int n = coins.length;
        int[] dp = new int[amount+1];
        dp[0] = 1;
        for(int i = 0 ; i < n ; i++){
            for(int j = 1 ;j <=amount ; j++){
                if(j - coins[i] >= 0) dp[j] = dp[j] + dp[j-coins[i]];
            }
        }
        return dp[amount];
    }
}

第三部分、子集分割、子序列问题

1.分割等和子集

leetcode-cn.com/problems/pa…

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for(int num : nums) sum += num;
        if(sum % 2 == 1) return false;
        int n = nums.length;
        sum = sum/2; //只要装一半sum即可
        //dp[i][j] 表示用前i个数之和能否等于j
        boolean[][] dp = new boolean[n+1][sum+1];
        for(int i = 0 ; i <= n ; i++) dp[i][0] = true;
        for(int i = 1; i <= n ; i++){
            for(int j = 1; j<= sum ;j++){
                if(j - nums[i-1] < 0) dp[i][j] = dp[i-1][j]; //不能装入背包,只能看前一个值是否可以
                else dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]]; //装入或者不装入
            }
        }
        return dp[n][sum];
    }
}

2.最长递增子序列

leetcode-cn.com/problems/lo…


class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        if(n == 0) return 0;
        //dp[i]表示以nums[i]结尾的最长递增子序列
        int[] dp = new int[n];
        Arrays.fill(dp,1);
        for(int i = 1 ; i < n ;i++){
            for(int j = i-1 ;j >= 0 ;j--){
                if(nums[i] > nums[j]) dp[i] = Math.max(dp[i] , 1+dp[j]);
            }
        }
        int res = 1;
        for(int num : dp) res = Math.max(res , num);
        return res;
    }
}

3.俄罗斯套娃信封问题

leetcode-cn.com/problems/ru…

class Solution {
    public int maxEnvelopes(int[][] envelopes) {
        if(envelopes.length == 0) return 0;
        //注意如果相等按照降序排列
        Arrays.sort(envelopes,(a,b) -> (a[0] == b[0] ? b[1]-a[1] : a[0]-b[0]));

        int[] nums = new int[envelopes.length];
        int index = 0;
        for(int[] num : envelopes) nums[index++] = num[1];

        //求最长递增子序列
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp , 1);
        for(int i = 1 ; i < n ;i++){
            for(int j = i-1 ; j >= 0 ;j--){
                if(nums[i] > nums[j]) dp[i] = Math.max(dp[i] , 1+dp[j]);
            }
        }

        int res = 1;
        for(int num : dp) res = Math.max(res , num);
        return res;
    }
}

4. 最长公共子数组

leetcode-cn.com/problems/ma…

class Solution {
    public int findLength(int[] A, int[] B) {
        int la = A.length;
        int lb = B.length;
        //dp[i][j] 表示以A[i] 和 B[j] 结尾的最长公共子数组
        int[][] dp = new int[la+1][lb+1];
        dp[0][0] = 0;
        int res = 0;
        for(int i = 1; i <= la ; i++){
            for(int j = 1;j <= lb ;j++){
                if(A[i-1] == B[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
                else dp[i][j] = 0;

                res = Math.max(res , dp[i][j]);
            }
        }
        return res;
    }
}

5. 最长公共子序列

leetcode-cn.com/problems/lo…

class Solution {
    public int longestCommonSubsequence(String s, String p) {
        int ls = s.length();
        int lp = p.length();
        //dp[i][j] 表示 s[0 - i] 和 p[0 - j] 的最长公共子序列长度
        int[][] dp = new int[ls+1][lp+1];
        dp[0][0] = 0;
        for(int i = 1 ; i <= ls ; i++){
            for(int j = 1 ; j <= lp ; j++){
                if(s.charAt(i-1) == p.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[ls][lp];
    }
}

第四部分、回文子串的问题

1.最长回文子串

leetcode-cn.com/problems/lo…

class Solution {
    public String longestPalindrome(String s) {
        int n = s.length();
        if(n == 0) return "";
        //dp[i][j] 表示 s[i - j] 之间是不是回文
        boolean[][] dp = new boolean[n][n];
        for(int i = 0; i < n ;i++) dp[i][i] = true;

        int maxlen = 1;
        int start = 0;

        for(int i = n-2 ; i >= 0 ; i--){
            for(int j = i+1 ;j < n ; j++){
                if(s.charAt(i) != s.charAt(j)) dp[i][j] = false;
                else{
                    if(j - i < 3) dp[i][j] = true;
                    else dp[i][j] = dp[i+1][j-1];
                }

                if(dp[i][j] && j-i+1 > maxlen){
                    maxlen = j-i+1;
                    start = i;
                }
            }
        }

        return s.substring(start , start + maxlen);
    }
}

2.最长回文子序列

leetcode-cn.com/problems/lo…


class Solution {
    public int longestPalindromeSubseq(String s) {
        char[] chars = s.toCharArray();
        int n = chars.length;
        //1. dp[i][j] 表示 chars[i] 到 chars[j] 之间的最长回文子序列长度
        int[][] dp = new int[n][n];
        //2.初始化
        for(int i = 0 ; i < n ; i ++) dp[i][i] = 1;
        //3.递推关系 i 在前 ,j在后
        for(int i = n-2 ; i >= 0 ; i--){
            for(int j= i+1 ; j < n ; j++){
                if(chars[i] == chars[j]) dp[i][j] = dp[i+1][j-1] + 2;
                else dp[i][j] = Math.max(dp[i+1][j] , dp[i][j-1]);
            }
        }
        return dp[0][n-1];

    }
}

3.回文子串的个数

leetcode-cn.com/problems/pa…

class Solution {
    public int countSubstrings(String s) {
        int res = 0;
        int n = s.length();

        boolean[][] dp = new boolean[n][n];
        for(int i = 0 ; i < n ; i++){
            dp[i][i] = true;
            res++;
        }

        for(int i = n-2 ; i >= 0 ;i--){
            for(int j = i+1 ; j < n ;j++){
                if(s.charAt(i) != s.charAt(j)) dp[i][j] = false;
                else{
                    if(j - i < 3) dp[i][j] = true;
                    else dp[i][j] = dp[i+1][j-1];
                }
                if(dp[i][j]) res++;
            }
        }
        return res;

    }
}

4.让字符串成为回文串的最少插入次数

leetcode-cn.com/problems/mi…

class Solution {
    public int minInsertions(String s) {
        //求最长回文子序列,然后减一下
        int n = s.length();
        int m = maxHuiWen(s);
        return n-m;
    }

    int maxHuiWen(String s){
        char[] chars = s.toCharArray();
        int n = chars.length;
        int[][] dp = new int[n][n];
        for(int i = 0 ; i < n ; i++) dp[i][i] = 1;

        for(int i = n-2 ; i >= 0 ; i--){
            for(int j = i+1 ; j < n ;j++){
                if(chars[i] == chars[j]) dp[i][j] = dp[i+1][j-1] + 2;
                else dp[i][j] = Math.max(dp[i+1][j] , dp[i][j-1]);
            }
        }
        return dp[0][n-1];
    }
}

5.分割回文串的最少分割次数

leetcode-cn.com/problems/pa…

class Solution {
    public int minCut(String s) {
        int n = s.length();
        if(n < 2) return 0;
        int[] dp = new int[n];
        //base case:将每个字符都分割
        for(int i = 0 ; i < n ; i++) dp[i] = i;

        boolean[][] dp2 = new boolean[n][n];
        for (int r = 0 ;  r < n ; r++){
            for (int l = 0 ; l <= r ;l++){
                if (s.charAt(l) == s.charAt(r) && (r-l <= 2 || dp2[l+1][r-1])) dp2[l][r] = true;
            }
        }

        for (int i = 1; i < n; i++) {
            if (dp2[0][i]){ // 如果从 0 - i 已经是回文了
                dp[i] = 0;
                continue;
            }
            for (int j = 0; j < i; j++) {
                if (dp2[j+1][i]){
                    dp[i] = Math.min(dp[i] , dp[j]+1);
                }
            }
        }
        return dp[n-1];

    }
}

第五部分、字符串匹配的问题

1. 正则表达式匹配

leetcode-cn.com/problems/re…

class Solution {
    public boolean isMatch(String s, String p) {
        int ls = s.length();
        int lp = p.length();

        boolean[][] dp = new boolean[ls+1][lp+1];
        dp[0][0] = true;
        // "" "a*" a* 可以和空字符串匹配
        for(int i = 2 ; i <= lp ; i++) dp[0][i] = dp[0][i-2] && p.charAt(i-1) == '*';

        for(int i = 1 ; i <= ls ; i++){
            for(int j = 1;  j<= lp ;j++){
                int m = i-1 , n = j-1;
                if(p.charAt(n) == '*'){
                    dp[i][j] = dp[i][j-2]  //和空字符串匹配
                    || (dp[i-1][j] && (s.charAt(m) == p.charAt(n-1) || p.charAt(n-1) == '.'));
                       //前面匹配 ,后面匹配一个或多个
                }else if(p.charAt(n) == '.' || s.charAt(m) == p.charAt(n)) dp[i][j] = dp[i-1][j-1];
            }
        }
        return dp[ls][lp];
    }
}

2. 单词拆分

leetcode-cn.com/problems/wo…

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {

        int ls =  s.length();
        boolean[] dp = new boolean[ls+1];
        dp[0] = true;
        for(int i = 1 ; i <= ls ; i++){
            for(int j = i-1 ; j >= 0 ; j--){
                String tem = s.substring(j , i);
                if(wordDict.contains(tem) && dp[j]){
                    dp[i] = true;
                    break;
                } 
            }
        }
        return dp[ls];
    }
}

3.交错字符串

leetcode-cn.com/problems/in…


class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        int n1 = s1.length();
        int n2 = s2.length();
        int n3 = s3.length();

        if(n1 + n2 != n3) return false;
        boolean[][] dp = new boolean[n1+1][n2+1];
        dp[0][0] = true;

        for(int i = 1 ; i <= n1 ;i++){
            dp[i][0] = dp[i-1][0] && s1.charAt(i-1) == s3.charAt(i-1);
        }
        for(int i = 1; i <= n2 ; i++){
            dp[0][i] = dp[0][i-1] && s2.charAt(i-1) == s3.charAt(i-1);
        }
        for(int i = 1; i <= n1 ; i++){
            for(int j = 1; j <= n2 ; j++){
                dp[i][j] = dp[i-1][j] && s1.charAt(i-1) == s3.charAt(i-1+j)
                         || dp[i][j-1] && s2.charAt(j-1) == s3.charAt(j-1+i);
            }
        }
        return dp[n1][n2];
    }
}