LeetCode & 剑指offer 经典题目总结——动态规划

1. 买卖股票的最佳时机

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

解法:

public class Solution {
    //动态规划 前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}
    public int maxProfit(int[] prices) {
        if(prices==null || prices.length==0){
            return 0;
        }
        int maxProfit=0;
        //前i-1天的最小值
        int preMin=prices[0];
        for(int i=1;i<prices.length;i++){
            preMin=Math.min(prices[i-1],preMin);
            maxProfit=Math.max(prices[i]-preMin,maxProfit);
        }
        return maxProfit;
    }
}

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

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例2:

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

解法:
[7, 1, 5, 6] 第二天买入,第四天卖出,收益最大(6-1),所以一般人可能会想,怎么判断不是第三天就卖出了呢? 这里就把问题复杂化了,根据题目的意思,当天卖出以后,当天还可以买入,所以其实可以第三天卖出,第三天买入,第四天又卖出((5-1)+ (6-5) === 6 - 1)。所以算法可以直接简化为只要今天比昨天大,就卖出。

public class Solution {
    //判断相邻是否递增,因为连续递增可以合起来看为一次买入卖出操作,所以统计所有递增量即可
    public int maxProfit(int[] prices) {
        int maxProfit=0;
        for(int i=1;i<prices.length;i++){
            if(prices[i]>prices[i-1]){
                maxProfit+=prices[i]-prices[i-1];
            }
        }
        return maxProfit;
    }
}

3. 最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。

示例 2:

输入: “cbbd”
输出: “bb”

解法一:动态规划

考虑 “ababa” 这个示例。如果我们已经知道 “bab” 是回文,那么很明显,“ababa” 一定是回文,因为它的左首字母和右尾字母是相同的。
在这里插入图片描述

public String longestPalindrome(String s) {
    int length = s.length();
    boolean[][] P = new boolean[length][length];
    int maxLen = 0;
    String maxPal = "";
    for (int len = 1; len <= length; len++) //遍历所有的长度
        for (int start = 0; start < length; start++) {
            int end = start + len - 1;
            if (end >= length) //下标已经越界,结束本次循环
                break;
            P[start][end] = (len == 1 || len == 2 || P[start + 1][end - 1]) && s.charAt(start) == s.charAt(end); //长度为 1 和 2 的单独判断下
            if (P[start][end] && len > maxLen) {
                maxPal = s.substring(start, end + 1);
            }
        }
    return maxPal;
}

解法二:

我们知道回文串一定是对称的,所以我们可以每次循环选择一个中心,进行左右扩展,判断左右字符是否相等即可。
在这里插入图片描述
由于存在奇数的字符串和偶数的字符串,所以我们需要从一个字符开始扩展,或者从两个字符之间开始扩展,所以总共有 n+n-1 个中心。

public String longestPalindrome(String s) {
    if (s == null || s.length() < 1) return "";
    int start = 0, end = 0;
    for (int i = 0; i < s.length(); i++) {
        int len1 = expandAroundCenter(s, i, i);
        int len2 = expandAroundCenter(s, i, i + 1);
        int len = Math.max(len1, len2);
        if (len > end - start) {
            start = i - (len - 1) / 2;
            end = i + len / 2;
        }
    }
    return s.substring(start, end + 1);
}

private int expandAroundCenter(String s, int left, int right) {
    int L = left, R = right;
    while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
        L--;
        R++;
    }
    return R - L - 1;
}

4. 计算字符串距离(最小编辑代价)

Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。编辑距离的算法是首先由俄国科学家Levenshtein提出的,故又叫Levenshtein Distance。

Ex:

字符串A:abcdefg

字符串B: abcdef

通过增加或是删掉字符”g”的方式达到目的。这两种方案都需要一次操作。把这个操作所需要的次数定义为两个字符串的距离。

要求:给定任意两个字符串,写出一个算法计算它们的编辑距离。

示例:

输入:
abcdefg
abcdef
输出:
1

解法: 例题 7.6

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while(in.hasNext()) {
            String str1 = in.nextLine();
            String str2 = in.nextLine();
            System.out.println(calStringDistance(str1,str2));
        }
    }
    
    private static int calStringDistance(String str1,String str2){
        int M = str1.length();
        int N = str2.length();
        //dp[i][j] 代表 str1[0...i-1] 编辑成 str2[0...j-1] 的最小代价
        int[][] dp = new int[M+1][N+1];
        //将 "" 编辑成 ""
        dp[0][0] = 0;
        //将 str1[0...i-1] 编辑成 "" 的代价
        for(int i = 1;i < M+1;i++){
            dp[i][0] = i;
        }
        //将 "" 编辑成 str2[0...j-1] 的代价
        for(int j = 1;j < N+1;j++){
            dp[0][j] = j;
        }
        
        for(int i = 1;i < M+1;i++){
            for(int j = 1;j < N+1;j++){
                //str1[0...i-1] 先编辑成str1[0...i-2],然后由str1[0...i-2]编辑成str2[0...j-1]
                int c1 = 1 + dp[i-1][j];
                //str1[0...i-1] 先编辑成str2[0...j-2],然后由str2[0...j-2]编辑成str2[0...j-1]
                int c2 = dp[i][j-1] + 1;
                //如果str1[i-1] != str2[j-1]。先把str1[0...i-1]中str1[0...i-2]的部分变为str2[0...j-2]
                //然后把字符str1[i-1]替换成str2[j-1]
                int c3 = dp[i-1][j-1] + 1;
                //如果str1[i-1] == str2[j-1]。把str1[0...i-1]中str1[0...i-2]的部分变为str2[0...j-2]即可
                int c4 = dp[i-1][j-1];
                
                if(str1.charAt(i-1) != str2.charAt(j-1)){
                    dp[i][j] = Math.min(Math.min(c1,c2),c3);
                }else {
                    dp[i][j] = Math.min(Math.min(c1,c2),c4);
                }
            }
        }
        return dp[M][N];
    }
}

5. 最长公共子串

查找两个字符串a,b中的最长公共子串。若有多个,输出在较短串中最先出现的那个。
示例:

输入:
abcdefghijklmnop
abcsafjklmnopqrstuvw
输出:
jklmnop

解法:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while(in.hasNext()) {
            String str1 = in.nextLine();
            String str2 = in.nextLine();
            System.out.println(maxCommonStr(str1,str2));
        }
    }

    private static String maxCommonStr(String str1,String str2){
        //str1保存较短的串
        if(str1.length()>str2.length()){
            String temp = str1;
            str1 = str2;
            str2 = temp;
        }
        int M = str1.length();
        int N = str2.length();
        //dp[i][j] 表示 str1[0~i-1] 和 str2[0~j-1]的公共后缀
        int[][] dp = new int[M+1][N+1];
        int maxLen = 0,start = 0;
        for(int i = 1;i < M+1;i++){
            for(int j = 1;j < N+1;j++){
                if(str1.charAt(i-1) == str2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1] + 1;
                    if(dp[i][j] > maxLen){
                        maxLen = dp[i][j];
                        //记录最长公共子串的起始位置
                        start = i-1 - maxLen + 1;
                    }
                }else{
                    dp[i][j] = 0;
                }
            }
        }
        return str1.substring(start,start + maxLen);
    }
}

6. 最长公共子序列

给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如过最长公共子序列为空,则输出-1。

输入描述:

输入包括两行,第一行代表字符串str1,第二行代表str2。

输出描述:

输出一行,代表他们最长公共子序列。如果公共子序列的长度为空,则输出-1。

示例1:

输入:

1A2C3D4B56
B1D23CA45B6A

输出:

123456

说明:

"123456"和“12C4B6”都是最长公共子序列,任意输出一个。

解法:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while(in.hasNext()) {
            String str1 = in.nextLine();
            String str2 = in.nextLine();
            System.out.println(maxSubSequence(str1,str2).length() > 0 ? maxSubSequence(str1,str2):-1);
        }
    }

    public static String maxSubSequence(String str1,String str2) {
        int M = str1.length();
        int N = str2.length();
        //多添加一行一列便于初始化
        int[][] dp = new int[M+1][N+1];//dp[i][j] 表示 str1[0~i-1] 与 str2[0~j-1] 的最长公共子序列的长度
        int maxLen = 0;
        String maxSubSequence = "";
        for(int i = 1;i < M+1;i++){
            for(int j = 1;j < N+1;j++){
                if(str1.charAt(i-1) == str2.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]);
                }
                if(dp[i][j] > maxLen){
                    maxLen = dp[i][j];
                    maxSubSequence += str1.charAt(i-1);
                }
            }
        }
        return maxSubSequence;
    }
}

7. 通配符匹配

给定一个字符串 (s) 和一个字符模式 § ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。
‘?’ 可以匹配任何单个字符。
‘*’ 可以匹配任意字符串(包括空字符串)。

示例 1:

输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串

示例 2:

输入:
s = “aa”
p = ""
输出: true
解释: '
’ 可以匹配任意字符串。

示例 3:

输入:
s = “cb”
p = “?a”
输出: false
解释: ‘?’ 可以匹配 ‘c’, 但第二个 ‘a’ 无法匹配 ‘b’。

解法:
dp[i][j] 表示 s[0~i-1] 和 p[0~j-1] 是否匹配

如果s[i-1]和p[j-1]相同,或者p[j-1]为 ‘?’,则 dp[i][j] = dp[i-1][j-1]

如果p[j-1]为 ‘*’:

  1. 若 p[j-1] 匹配空字符,则 dp[i][j] = dp[i][j-1]
  2. 若 p[j-1] 匹配 s[i-1],则 dp[i][j] = dp[i-1][j]
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while(in.hasNext()) {
            String p = in.nextLine();
            String s = in.nextLine();
            System.out.println(isMatch(s,p));
        }
    }

    public static boolean isMatch(String s, String p) {
        int M = s.length();
        int N = p.length();
        //dp[i][j] 表示 s[0~i-1] 和 p[0~j-1] 是否匹配,添加两个空字符用于初始化
        boolean[][] dp = new boolean[M+1][N+1];
        dp[0][0] = true;
        for(int j = 1;j < N+1;j++){
            dp[0][j] = dp[0][j-1] && p.charAt(j-1) == '*';
        }
        for(int i = 1;i < M+1;i++){
            for(int j = 1;j < N+1;j++){
                if(s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '?'){
                    dp[i][j] = dp[i-1][j-1];
                }else if(p.charAt(j-1) == '*'){
                    dp[i][j] = dp[i][j-1] || dp[i-1][j];
                }
            }
        }
        return dp[M][N];
    }
}

8. 最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
  • 你算法的时间复杂度应该为 O(n2) 。

解法:
在这里插入图片描述
动画

public class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        int[] dp = new int[nums.length];
        dp[0] = 1;
        int maxans = 1;
        for (int i = 1; i < dp.length; i++) {
            int maxval = 0;
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    maxval = Math.max(maxval, dp[j]);
                }
            }
            dp[i] = maxval + 1;
            maxans = Math.max(maxans, dp[i]);
        }
        return maxans;
    }
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值