目录
300-最长上升子序列
301-删除无效的括号
309-最佳买卖股票时机含冷静期
312-戳气球
322-零钱兑换
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:输入:nums = [0,1,0,3,2,3]
输出:4
动态规划详解::link
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length==0) return 0;
int[] dp = new int[nums.length];
dp[0] = 1;
int maxlen = 1;
//依次判断当前点的最长递增子序列
for(int i=1;inums[j]){
//等于i之前的子序列和nums[i]构成的最长递增子序列的长度
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
maxlen = Math.max(maxlen,dp[i]);
}
return maxlen;
}
}
给你一个由若干括号和字母组成的字符串 s ,删除最小数量的无效括号,使得输入的字符串有效。
返回所有可能的结果。答案可以按 任意顺序 返回。
示例 1:
输入:s = "()())()"
输出:["(())()","()()()"]示例 2:
输入:s = "(a)())()"
输出:["(a())()","(a)()()"]
link
class Solution{
public List removeInvalidParentheses(String s) {
List res = new ArrayList<>();
dfs(s, res, '(', ')', 0, 0);
return res;
}
private void dfs(String s, List res, char opening, char closing, int i, int lastRemoved) {
int count = 0;
while (i < s.length() && count >= 0) {
if (s.charAt(i) == opening) {
count++;
} else if (s.charAt(i) == closing) {
count--;
}
i++;
}
// 如果count==0 代表字符串遍历完了,open和close平衡
// 如果count>0 代表字符串遍历完了,但有多余的open符号 此时需要从右到左,处理open符号的问题,可以理解为close符号的镜像问题。利用翻转和交换close和open符号复用逻辑
// 如果count<0 代表字符串还没有遍历完,遇到不合法的close符号 需要从[上一个删除点, index) (注意左闭右开) 中任意删除一个close 进入dfs
if (count < 0) { // find one illegal closing, delete it and dfs
for (int j = lastRemoved; j < i; j++) {
if (s.charAt(j) == closing && (j == 0 || s.charAt(j - 1) != closing)) { // prevent duplicate
dfs(s.substring(0, j) + s.substring(j + 1), res, opening, closing, i - 1, j);
}
}
} else if (count > 0) { // more openings needs redo from right to left to remove
dfs(new StringBuilder(s).reverse().toString(), res, closing, opening, 0, 0);
} else { // balance, add to result
if (opening == '(') {
res.add(s);
} else {
res.add(new StringBuilder(s).reverse().toString());
}
}
}
}
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]来源:力扣(LeetCode)
link
class Solution {
/**
注意到第 0 天实际上是不存在处于冷冻期的情况的,但我们仍然可以将对应的状态 f[0][1] 置为零
f[0][2]的状态就是第0天不买入,很容易理解。而对这句话,我的想法如下:
简单理解: 两种都是不持有,自然收益就是0了
更具体理解:f[0][1] 其实可以存在,可以理解为当天买入卖出,收益自然也是0。
而且会导致下一天"冷冻期”,显然是只有弊端。
结合代码理解: f[0][1]只影响f[1][2],而f[1][2]又是由f[0][1]和f[0][2]共同决定的,
而f[0][2]如上文所说,肯定就是0。 因此f[1][2] =Math.max(f[0][1], f[0][2]); ,
只要f[0][1]<=0 都是不会造成影响的。
因此我认为 f[0][1] = Integer.MIN_VALUE; 更容易理解,表示只会带来负收益
*/
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int res = 0;
int len = prices.length;
//dp[i][j]为对应的累积最大收益
// dp[i][0] : 持有股票
// dp[i][1] : 不持有股票,本日卖出,下一天为冷冻期
// dp[i][2] : 不持有股票,本日无卖出,下一天非冷冻期(今天可以处于冷冻期,f[1][2]受到f[0][1]和f[0][2]共同决定)
int[][] dp = new int[len][3];
//初始状态:
// 1: 第一天持有,只有可能是买入
dp[0][0] = -prices[0];
// 其实dp[0][1]、dp[0][2] 都不写,默认为0也对
// 2. 第0天,相当于当天买入卖出,没有收益,并造成下一天冷冻期,减少选择。综合认为是负收益
dp[0][1] = Integer.MIN_VALUE;
// 3. 相当于没买入,收益自然为0
dp[0][2]=0;
for (int i = 1; i < len; i++) {
// 持有股票: 1.昨天持有股票 2.本日买入(条件:昨天不持有,且不是卖出)
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i]);
//本日卖出 : (条件:昨天持有)(今日持有今日卖出没有意义,且会导致明天冷静期,没有意义)
dp[i][1] = dp[i - 1][0] + prices[i];
// 不持有,但也不是卖出 : 1.昨天卖出,不持有 2.昨天没卖出,但也不持有
dp[i][2] = Math.max(dp[i - 1][1], dp[i - 1][2]);
}
// 最后一天还持有股票是没有意义的,肯定是不持有的收益高,不用对比 dp[len-1][0]
return Math.max(dp[len - 1][1], dp[len - 1][2]);
}
}
有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。
求所能获得硬币的最大数量。
示例 1:
输入:nums = [3,1,5,8]
输出:167
解释:
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
示例 2:输入:nums = [1,5]
输出:10
link
class Solution {
public int maxCoins(int[] nums) {
if(nums == null || nums.length == 0) {
return 0;
}
int n = nums.length;
//创建一个n+2的数组,开头和末尾都填1
int[] arr = new int[n + 2];
for(int i = 1; i <= n; ++i) {
arr[i] = nums[i - 1];
}
arr[0]= 1;
arr[n + 1] = 1;
int[][] dp = new int[n + 2][n + 2];
//从下往上遍历,i控制左边界,j控制右边界
for(int i = n - 1; i >= 0; --i) {
for(int j = i + 2; j <= n + 1; ++j) {
//k在(i,j)范围内遍历气球,计算每选择一个气球的积分
//一轮遍历完后,就能确定(i,j)的最大积分
for(int k = i + 1; k < j; ++k) {
int total = arr[i] * arr[k] * arr[j];
total += dp[i][k] + dp[k][j];
dp[i][j] = Math.max(dp[i][j], total);
}
}
}
return dp[0][n + 1];
}
}
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:输入:coins = [2], amount = 3
输出:-1
class Solution {
/**
状态:金额
转移:一个硬币,这个硬币的金额在coins[]数组中遍历
dp函数:输入金额,返回最少硬币数
*/
public int coinChange(int[] coins, int amount) {
//最多的硬币情况是全部是1元,共有amount个硬币,共有amount+1个状态,amount+1个金额(0,,,,account)
int[] dp = new int[amount+1];
//必须将所有的dp赋最大值,因为要找最小值
Arrays.fill(dp,amount+1);
//自底向上动态规划,金额为0,则硬币最小数也是0
dp[0] = 0;
for(int i=1;i<=amount;i++){
//遍历coins的金额
for(int coin : coins){
//枚举金额面值大于硬币时,所需要的最少金币数
if(i>=coin){
//dp[i-coin]+1 为dp[当前金额i减去当前硬币剩余的金额]加上当前硬币
dp[i] = Math.min(dp[i],dp[i-coin]+1);
}
}
}
return dp[amount]>amount ? -1 : dp[amount];
}
}