- 写于2019年6月28日
文章目录
- [560. 和为K的子数组](https://leetcode.com/problems/subarray-sum-equals-k/)
- [394. 字符串解码](https://leetcode.com/problems/decode-string/)
- [309. 最佳买卖股票时机含冷冻期](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/)
- 股票有关的所有题目
- [121. 买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/)
- [122. 买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/)
- [123. 买卖股票的最佳时机 III](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/)
- [188. 买卖股票的最佳时机 IV](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/)
- [714. 买卖股票的最佳时机含手续费](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/)
560. 和为K的子数组
① 题目描述
中文描述:https://leetcode-cn.com/problems/subarray-sum-equals-k/
② 暴力法
- 从前往后遍历,求解子数组的和sum,如果sum等于target,则计数变量加1。
- 时间复杂度: O ( n 2 ) O(n^2) O(n2),空间复杂度: O ( 1 ) O(1) O(1)。
- 代码如下,运行时间
122ms
:
public int subarraySum(int[] nums, int k) {
int count = 0;
for (int i = 0; i < nums.length; i++) {
int sum = 0;
for (int j = i; j < nums.length; j++) {
sum = sum + nums[j];
if (sum == k) {
count++;
}
}
}
return count;
}
③ 哈希表
- 使用hash表存储从下标0开始到当前位置的累积和,并记录累计和的个数。
- 假设在j位置时,累积和为
sum[j]
;在i位置时,累积和为sum[i]
。若有sum[j]+k=sum[i]
则表示从j+1位置
到i位置
的累积和为k。这时,查找hash表中sum[j]
的个数,也就是j+1位置
到i位置
的连续子数组的累积和个数。 - 特殊的: 当从初始位置到某一位置的累积和为k时,j的值应该为-1,本身数组中是不存在该下标的。需要我们在hash表中手动添加
map.put(0,1)
。 - 代码如下,运行时间
13ms
:
public int subarraySum(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
int s = 0;
int count = 0;// 应该提前加入子数组和0,因为从起始位置到某一位置的子数组和可能为k
map.put(0,1);
for (int i = 0; i < nums.length; i++) {
s += nums[i];
// 如果存在sum[j]+k=sum[i],说明从当前位置i开始往前走,有子数组和为k
if (map.containsKey(s - k)) {
count += map.get(s - k);
}
// 将当前位置的累积和存入hash表中
map.put(s, map.getOrDefault(s, 0) + 1);
}
return count;
}
394. 字符串解码
① 题目描述
中文题目:https://leetcode-cn.com/problems/decode-string/
② 两个栈
- 一个栈用于存放重复次数,一个栈用于存放待重复的字符。
- 计算重复次数时,因为是数字字符串,所以要进行十进制累加。
- 遇到左括号
[
就将重复次数入栈,将当前字符串入栈,这时当前字符串是不用进行重复的,比如a3[cb]
中的第一个a
。 - 遇到右括号
]
就将重复次数出栈,并对当前字符串进行重复,然后与栈顶元素构成新的当前字符串。 - 代码如下,运行时间
1ms
:
public String decodeString(String s) {
Stack<Integer> nStack = new Stack<>();
Stack<String> sStack = new Stack<>();
String cur = "";// 保存当前字符串
int num = 0;// 保存重复数字
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (Character.isDigit(ch)) {
num = num * 10 + ch - '0';// 计算重复次数
} else if (ch == '[') {// 将重复次数和当前字符串压入栈并清零
nStack.push(num);
sStack.push(cur);
num = 0;
cur = "";
} else if (Character.isLetter(ch)) {
cur += ch;// 加入到当前字符串中
} else if (ch == ']') {
int times = nStack.pop();
cur = helper(times, cur);// 构造重复字符串
cur = sStack.pop() + cur;// 将栈顶元素出栈,构造新的待重复字符串
}
}
return cur;
}
309. 最佳买卖股票时机含冷冻期
① 题目描述
中文题目:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/
② 动态规划
- 对于第i天,有两种情况:第i天持有股票,第i天不持股票。
- 第i天不持股票,也有两种情况:
① 第i-1天不持股票
② 第i-1天持有股票,第i天卖出 - 第i天持有股票,也有两种情况:
① 第i-1天持有股票
② 第i-1天不持股票,可能是第i-1天刚好卖出,但是第i天就成了冷冻期。为了保证第i天不是冷冻期,应该是第i-2天不持股票 - 建立两个dp数组,一个用于记录第i不持有股票的最大利润,一个用于记录第i天持有股票的最大利润。
d p 1 [ i ] = m a x ( d p 1 [ i − 1 ] , d p 2 [ i − 1 ] + p r i c e [ i ] ) dp_1[i]=max(dp1[i-1],dp2[i-1]+price[i]) dp1[i]=max(dp1[i−1],dp2[i−1]+price[i])
d p 2 [ i ] = m a x ( d p 2 [ i − 1 ] , d p 1 [ i − 2 ] − p r i c e [ i ] ) dp_2[i]=max(dp2[i-1],dp1[i-2]-price[i]) dp2[i]=max(dp2[i−1],dp1[i−2]−price[i]) - 初始情况:
d
p
1
[
0
]
=
0
dp1[0]=0
dp1[0]=0,第0天没有进行任何交易,利润为0;
d
p
2
[
0
]
=
−
p
r
i
c
e
[
0
]
dp2[0]=-price[0]
dp2[0]=−price[0],第0天就买入股票,这时利润为
-price[0]
。 - 代码如下,运行时间
1ms
:
public int maxProfit(int[] prices) {
if (prices.length == 0) {
return 0;
}
int[] dp1 = new int[prices.length];// 表示第i天不持有股票
int[] dp2 = new int[prices.length];// 表示第天持有股票
// dp1[0]=0,因为第0天没有股票,说明没有进行任何交易
// dp2[0]=-prices[0],第一天就买入股票,利润做减法
dp2[0] = -prices[0];
for (int i = 1; i < prices.length; i++) {
// 第i天不持股票,可能是第i-1天也不持股票,或者第i-1天持有股票,第i天卖出
dp1[i] = Math.max(dp1[i - 1], dp2[i - 1] + prices[i]);
// 第i天持有股票,可能是第i-1天也持有股票,或者第i天买入股票,这要求前一天为冷冻期或者不持有股票
// 第i-1天不持有股票可能是刚卖出,为了保证第i天不是冷冻期,第i-2天必须不持股票
dp2[i] = Math.max(dp2[i - 1], (i - 2 >= 0 ? dp1[i - 2] : 0) - prices[i]);
}
return Math.max(dp1[prices.length - 1], dp2[prices.length - 1]);
}
股票有关的所有题目
121. 买卖股票的最佳时机
- 使用暴力法,求解当 j > i j>i j>i时, m a x ( p r i c e [ j ] − p r i c e [ i ] ) max(price[j]-price[i]) max(price[j]−price[i])的值,就是最大利润。
- 通过折线图发现,最低价格之后的最高价格(形成最低谷和最高峰):
public int maxProfit(int[] prices) {
if (prices.length == 0) {
return 0;
}
int minPrice = Integer.MAX_VALUE;
int profit = 0;
for (int i = 0; i < prices.length; i++) {
if (prices[i] < minPrice) {// 寻找最低谷
minPrice = prices[i];
} else {
if (prices[i] - minPrice > profit) {// 寻找最低谷后的最高价格,形成最高峰
profit = prices[i] - minPrice;
}
}
}
return profit;
}
122. 买卖股票的最佳时机 II
- 与309题很相似,只是没有了冷冻期,昨天卖出,今天就可以买入。
- 也使用两个
dp数组
分别表示第i天不持股票的最大利润、第i天持有股票的最大利润。 - 第i天不持股票,有两种情况:
① 第i-1天也不持股票
② 第i天卖出股票,这就要求第i-1天持有股票 - 第i天持有股票,有两种情况:
① 第i-1天持有股票
② 第i天买入股票,这就要求第i-1天不持股票 - 状态转移方程:
d p 1 [ i ] = M a t h . m a x ( d p 1 [ i − 1 ] , d p 2 [ i − 1 ] + p r i c e s [ i ] ) dp1[i]=Math.max(dp1[i-1],dp2[i-1]+prices[i]) dp1[i]=Math.max(dp1[i−1],dp2[i−1]+prices[i])
d p 2 [ i ] = M a t h . m a x ( d p 2 [ i − 1 ] , d p 1 [ i − 1 ] − p r i c e s [ i ] ) dp2[i]=Math.max(dp2[i-1],dp1[i-1]-prices[i]) dp2[i]=Math.max(dp2[i−1],dp1[i−1]−prices[i]) - 初始条件:
d p 1 [ 0 ] = 0 dp1[0]=0 dp1[0]=0
d p 2 [ 0 ] = − p r i c e s [ 0 ] dp2[0]=-prices[0] dp2[0]=−prices[0] - 代码如下,运行时间
2ms
:
public int maxProfit(int[] prices) {
if (prices.length == 0) {
return 0;
}
int[] dp1=new int[prices.length];// 第i天不持股票的最大利润
int[] dp2=new int[prices.length];// 第i天持有股票的最大利润
// 第0天不持股票,未进行任何交易,利润为0,dp1[0]=0;
// 第0天持有股票,当天进行的买入,利润为-price[0]
dp2[0]=-prices[0];
for (int i=1;i<prices.length;i++){
dp1[i] = Math.max(dp1[i - 1], dp2[i - 1] + prices[i]);
dp2[i] = Math.max(dp2[i - 1], dp1[i - 1] - prices[i]);
}
return Math.max(dp1[prices.length-1],dp2[prices.length-1]);
}
123. 买卖股票的最佳时机 III
- 暴力法: 从第i个位置,将整个数组分为左半部分和右半部分,分别查找left和right的
一次售卖股票的最大利润
,二者的和的最大值就是两次售卖股票的最大利润。 - 运行时间很长
586ms
,因为需要分别求解left和right一次售卖股票的最大利润:
public int maxProfitOne(int[] prices) {
if (prices.length == 0) {
return 0;
}
int profit = 0;
int minPrice = Integer.MAX_VALUE;
for (int i = 0; i < prices.length; i++) {
if (prices[i] < minPrice) {
minPrice = prices[i];
} else {
if (prices[i] - minPrice > profit) {
profit = prices[i] - minPrice;
}
}
}
return profit;
}
public int maxProfit(int[] prices) {
int profit = 0;
for (int i = 0; i < prices.length; i++) {
int[] left = new int[i];
int[] right = new int[prices.length - i];
System.arraycopy(prices, 0, left, 0, left.length);
System.arraycopy(prices, i, right, 0, right.length);
profit = Math.max(profit, maxProfitOne(left) + maxProfitOne(right));
}
return profit;
}
188. 买卖股票的最佳时机 IV
不会!!!后面补
714. 买卖股票的最佳时机含手续费
- 又是一个不限制交易次数的问题, 共有三种类型:
① 不限制交易次数
② 不限制交易次数,但是有一天的冷冻期
③ 不限制交易次数,但是交易完成要收手续费 - 这个题的状态转移方程就变成了:
- 状态转移方程:
d p 1 [ i ] = M a t h . m a x ( d p 1 [ i − 1 ] , d p 2 [ i − 1 ] + p r i c e s [ i ] − 2 ) dp1[i]=Math.max(dp1[i-1],dp2[i-1]+prices[i]-2) dp1[i]=Math.max(dp1[i−1],dp2[i−1]+prices[i]−2) 有手续费
d p 2 [ i ] = M a t h . m a x ( d p 2 [ i − 1 ] , d p 1 [ i − 1 ] − p r i c e s [ i ] ) dp2[i]=Math.max(dp2[i-1],dp1[i-1]-prices[i]) dp2[i]=Math.max(dp2[i−1],dp1[i−1]−prices[i]) - 代码如下,运行时间
6ms
:
public int maxProfit(int[] prices, int fee) {
if (prices.length == 0) {
return 0;
}
int[] dp1 = new int[prices.length];
int[] dp2 = new int[prices.length];
dp2[0] = -prices[0];
for (int i = 1; i < prices.length; i++) {
dp1[i] = Math.max(dp1[i - 1], dp2[i - 1] + prices[i] - fee);
dp2[i] = Math.max(dp2[i - 1], dp1[i - 1] - prices[i]);
}
return Math.max(dp1[prices.length - 1], dp2[prices.length - 1]);
}