动态规划

表达式匹配

leetcode 10.正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

说明:

  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。 正则表达式匹配
    class Solution {
    // 1.如果 p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1];
    // 2.如果 p.charAt(j) == '.' : dp[i][j] = dp[i-1][j-1];
    // 3.如果 p.charAt(j) == '*':
    // 3.1如果 p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] //in this case, a* only counts as empty
    // 3.2如果 p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == '.':
    // dp[i][j] = dp[i-1][j] //in this case, a* counts as multiple a
    // or dp[i][j] = dp[i][j-1] // in this case, a* counts as single a
    // or dp[i][j] = dp[i][j-2] // in this case, a* counts as empty
    public boolean isMatch(String s, String p) {
    if (s == null || p == null) {
    return false;
    }
    boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
    dp[0][0] = true;
    for (int i = 1; i <= p.length(); i++) {
    if (p.charAt(i - 1) == '*' && dp[0][i - 2]) {
    dp[0][i] = true;
    }
    }
    for (int i = 1; i <= s.length(); i++) {
    for (int j = 1; j <= p.length(); j++) {
    if (p.charAt(j - 1) == '.' || p.charAt(j - 1) == s.charAt(i - 1)) {
    dp[i][j] = dp[i - 1][j - 1];
    }
    if (p.charAt(j - 1) == '*') {
    if (p.charAt(j - 2) != s.charAt(i - 1) && p.charAt(j - 2) != '.') {
    dp[i][j] = dp[i][j - 2];
    } else {
    dp[i][j] = (dp[i][j - 1] || dp[i - 1][j] || dp[i][j - 2]);
    }
    }
    }
    }
    return dp[s.length()][p.length()];
    }
    }

leetcode 44.通配符匹配

给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。

‘?’ 可以匹配任何单个字符。
‘*’ 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。

说明:

  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。

通配符匹配

class Solution {
// 状态 dp[i][j] : 表示 s 的前 i 个字符和 p 的前 j 个字符是否匹配 (true 的话表示匹配)
// 状态转移方程:
// 1. 当 s[i] == p[j],或者 p[j] == ? 那么 dp[i][j] = dp[i - 1][j - 1];
// 2. 当 p[j] == * 那么 dp[i][j] = dp[i][j - 1] || dp[i - 1][j] 其中:
// dp[i][j - 1] 表示 * 代表的是空字符,例如 ab, ab*
// dp[i - 1][j] 表示 * 代表的是非空字符,例如 abcd, ab*
public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();

// 状态 dp[i][j] : 表示 s 的前 i 个字符和 p 的前 j 个字符是否匹配
boolean[][] dp = new boolean[m + 1][n + 1];

// 初始化
dp[0][0] = true;
for (int i = 1; i <= n; i++) {
dp[0][i] = dp[0][i - 1] && p.charAt(i - 1) == '*';
}

// 状态转移
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; 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];

}
}

连续子数组和、积最值问题

leetcode 53. 最大子序和

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

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

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

}

leetcode 152. 乘积最大子数组

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

class Solution {
public int maxProduct(int[] A) {
// store the result that is the max we have found so far
int r = Integer.MIN_VALUE;
int imax = 1;
int imin = 1;

// imax/imin stores the max/min product of
// subarray that ends with the current number A[i]
for (int i = 0; i < A.length; i++) {
// multiplied by a negative makes big number smaller, small number bigger
// so we redefine the extremums by swapping them
if (A[i] < 0) {
int tmp = imax;
imax = imin;
imin = tmp;
}
// max/min product for the current number is either the current number itself
// or the max/min by the previous number times the current one
imax = Math.max(A[i], imax * A[i]);
imin = Math.min(A[i], imin * A[i]);

// the newly computed max value is a candidate for our global result
r = Math.max(r, imax);
}
return r;
}

public int maxProduct(int[] A) {
int r = Integer.MIN_VALUE;
int[] dpMax = new int[A.length + 1];
dpMax[0] = 1;
int[] dpMin = new int[A.length + 1];
dpMin[0] = 1;

for (int i = 1; i <= A.length; i++) {

if (A[i-1] < 0) {
int tmp = dpMax[i-1];
dpMax[i-1] = dpMin[i-1];
dpMin[i-1] = tmp;
}

dpMax[i] = Math.max(A[i-1],dpMax[i-1]*A[i-1]);
dpMin[i] = Math.min(A[i-1],dpMin[i-1]*A[i-1]);

r = Math.max(r, dpMax[i]);
}
return r;
}
}

leetcode 300. 最长上升子序列

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

示例:

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

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length+1];
// base case:dp 数组全都初始化为 1
Arrays.fill(dp, 1);
int res = 0;
for (int i = 1; i <= nums.length; i++) {
for (int j = 1; j < i; j++) {
if (nums[i-1] > nums[j-1])
dp[i] = Math.max(dp[i], dp[j] + 1);
}
res = Math.max(res, dp[i]);
}
return res;
}


public int lengthOfLIS(int[] nums) {
int[] tails = new int[nums.length];
int size = 0;
for (int x : nums) {
int i = 0, j = size;
while (i != j) {
int m = (i + j) / 2;
if (tails[m] < x)
i = m + 1;
else
j = m;
}
tails[i] = x;
if (i == size) ++size;
}
return size;
}
}

背包问题

0-1 背包问题

给你一个可装载重量为 totalWeight 的背包和 N 个物品,每个物品有重量和价值两个属性。其中第 i 个物品的重量为 wt[i],价值为 val[i],现在让你用这个背包装物品,最多能装的价值是多少?

N = 3, totalWeight = 4
weight = [2, 1, 3]
value = [4, 2, 3]

/**
* dp[3][5] = 6,其含义为:对于给定的一系列物品中,若只对前 3 个物品进行选择,当背包容量为 5 时,最多可以装下的价值为 6。
*/
int knapsack(int totalWeight, int[] weight, int[] value) {
// base case 已初始化
int[][] dp = new int[totalWeight + 1][totalWeight + 1];
for (int i = 1; i <= weight.length; i++) {
for (int w = 1; w <= totalWeight; w++) {
if (w - weight[i - 1] < 0) {
// 这种情况下只能选择不装入背包
dp[i][w] = dp[i - 1][w];
} else {
// 装入或者不装入背包,择优
dp[i][w] = Math.max(dp[i - 1][w - weight[i - 1]] + value[i - 1], dp[i - 1][w]);
}
}
}
return dp[weight.length][totalWeight];
}

leetcode 416. 分割等和子集

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:

每个数组中的元素不会超过 100
数组的大小不会超过 200

class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;

for (int num : nums) {
sum += num;
}

if (sum % 2 != 0) {
return false;
}
sum /= 2;

int n = nums.length;
boolean[][] dp = new boolean[n+1][sum+1];

// base case
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) {
// 背包容量不足,不能装入第 i 个物品
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];
}
}

leetcode 518. 零钱兑换 II

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

class Solution {
public int change(int amount, int[] coins) {
int n = coins.length;
int[][] dp = new int[coins.length+1][amount+1];
// base case
for (int i = 0; i <= n; i++)
dp[i][0] = 1;

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

}

股票问题

状态转移方程

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
max( 选择 rest , 选择 sell )

解释:今天我没有持有股票,有两种可能:
要么是我昨天就没有持有,然后今天选择 rest,所以我今天还是没有持有;
要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。

dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
max( 选择 rest , 选择 buy )

解释:今天我持有着股票,有两种可能:
要么我昨天就持有着股票,然后今天选择 rest,所以我今天还持有着股票;
要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。

leetcode 121. 买卖股票的最佳时机

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

class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n+1][2];
dp[0][0] = 0;
dp[0][1] = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]);
dp[i][1] = Math.max(dp[i-1][1], -prices[i-1]);
}
return dp[n][0];
}
}

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

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n+1][2];
dp[0][0] = 0;
dp[0][1] = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i-1]);
}
return dp[n][0];
}
}

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

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int max_k = 2;
int[][][] dp = new int[n+1][max_k + 1][2];

dp[0][1][0] = 0;
dp[0][1][1] = Integer.MIN_VALUE;
dp[0][2][0] = 0;
dp[0][2][1] = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
for (int k = max_k; k >= 1; k--) {
dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i-1]);
dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i-1]);
}
}
// 穷举了 n × max_k × 2 个状态,正确。
return dp[n][max_k][0];
}
}

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

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

class Solution {
public int maxProfit(int max_k, int[] prices) {
int n = prices.length;
if (max_k > n / 2) {
return maxProfit_k_inf(prices);
}
int[][][] dp = new int[n+1][max_k + 1][2];
//base case
for(int k = 1;k <= max_k; k++){
dp[0][k][0] = 0;
dp[0][k][1] = Integer.MIN_VALUE;
}

for (int i = 1; i <= n; i++) {
for (int k = max_k; k >= 1; k--) {
dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i-1]);
dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i-1]);
}
}
// 穷举了 n × max_k × 2 个状态,正确。
return dp[n][max_k][0];
}

public int maxProfit_k_inf(int[] prices) {
int n = prices.length;
int[][] dp = new int[n+1][2];
dp[0][0] = 0;
dp[0][1] = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i-1]);
}
return dp[n][0];
}
}

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

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n+1][2];
dp[0][0] = 0;
dp[0][1] = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]);
if(i == 1){
dp[i][1] = Math.max(dp[i-1][1], - prices[i-1]);
} else {
dp[i][1] = Math.max(dp[i-1][1], dp[i-2][0] - prices[i-1]);
}

}
return dp[n][0];
}
}

leetcode 714. 买卖股票的最佳时机含手续费

给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

class Solution {
public int maxProfit(int[] prices, int fee) {
int n = prices.length;
int[][] dp = new int[n+1][2];
dp[0][0] = 0;
dp[0][1] = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i-1] - fee);
}
return dp[n][0];
}
}