动态规划也做了有一些了,挑选一些值得记录的。
题1
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成两笔交易。
这题是难度升级版。之前不限制交易次数的时候标准答案的DP将DP数组的第二维设置成:
dp[ i ][0]: 手上没有股票
dp[ i ][1]: 手上有股票
(所以也是新的思路哈,dp数组的第二维可以是不同的状态)
这题多了一个交易数量的限制,我当初还很傻逼地以为要建立dp数组的第三维…其实不就是多了几种状态吗。这题我们设了4种状态,分别是:
1.第一次持有
2.第一次持有后空仓
3.第二次持有
4.第二次持有后空仓
状态转移函数为
b
u
y
1
=
m
a
x
(
b
u
y
1
,
−
p
r
i
c
e
[
i
]
)
buy_1=max(buy_1,-price[i])
buy1=max(buy1,−price[i])
s
e
l
l
1
=
m
a
x
(
s
e
l
l
1
,
b
u
y
1
+
p
r
i
c
e
[
i
]
)
sell_1=max(sell_1,buy_1+price[i])
sell1=max(sell1,buy1+price[i])
b
u
y
2
=
m
a
x
(
b
u
y
2
,
s
e
l
l
1
−
p
r
i
c
e
[
i
]
)
buy_2=max(buy_2,sell_1-price[i])
buy2=max(buy2,sell1−price[i])
s
e
l
l
2
=
m
a
x
(
s
e
l
l
2
,
b
u
y
2
+
p
r
i
c
e
[
i
]
)
sell_2=max(sell_2,buy_2+price[i])
sell2=max(sell2,buy2+price[i])
代码实际也很简洁:
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int buy1 = -prices[0], sell1 = 0;
int buy2 = -prices[0], sell2 = 0;
for (int i = 1; i < n; ++i) {
buy1 = Math.max(buy1, -prices[i]);
sell1 = Math.max(sell1, buy1 + prices[i]);
buy2 = Math.max(buy2, sell1 - prices[i]);
sell2 = Math.max(sell2, buy2 + prices[i]);
}
return sell2;
}
要注意这里的写法和传统dp构建数组的方法不一样:当dp数组的值与前一个有关系时,可以不用构建数组,直接循环就可以了,因为执行新一次值时,循环只保存上一个值。
目标和——自己转换问题形式
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
这题用了纯朴回溯法其实也可以完成,但是很慢。最开始也想过用递归,但我寻思:有点难弄dp方程,方程的index会有负号(以为可能存在-号)。原题解答把问题转换了一下,使之排除负数的情况。还是挺妙的:
最长理想子序列——怎么构造dp数组?
给你一个由小写字母组成的字符串 s ,和一个整数 k 。如果满足下述条件,则可以将字符串 t 视作是 理想字符串 :
t 是字符串 s 的一个子序列。
t 中每两个 相邻 字母在字母表中位次的绝对差值小于或等于 k 。
返回 最长 理想字符串的长度。
字符串的子序列同样是一个字符串,并且子序列还满足:可以经由其他字符串删除某些字符(也可以不删除)但不改变剩余字符的顺序得到。
注意:字母表顺序不会循环。例如,‘a’ 和 ‘z’ 在字母表中位次的绝对差值是 25 ,而不是 1 。
主要说一下构造dp方程。最开始我傻傻地不知道怎么构造二维dp,然后看了题解5s得知了一种构造方法:
dp[i][j]:前i个字符串以i结尾,上一个字符串是j的的最大长度
public int longestIdealString(String s, int k) {
int[] dp = new int[s.length()];
int res = 0;
Arrays.fill(dp, 1);
for (int i = 0; i < s.length(); i++) {
for (int j = 0; j < i; j++) {
// 绝对值相差不大于 k
if (Math.abs(s.charAt(i) - s.charAt(j)) <= k) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
res = Math.max(res, dp[i]);
}
return res;
}
其实这种构造方法很常见,但我弄完发现超时了- -
因为这相当于二维遍历,如果能缩短就好了,于是题解来了:
将第二维设置成26个字母,dp时只要找上一次范围内的字母(即dp[i - 1][范围内的字母])就可以了!
这个还可以压缩成一维dp,代码如下:
class Solution:
def longestIdealString(self, s: str, k: int) -> int:
l = len(s)
dp = [0] * 26
for i in range(l):
cur = ord(s[i]) - ord('a')
left = max(cur - k, 0)
right = min(cur + k, 25)
dp[cur] = 1 + max(dp[left:right + 1])
return max(dp)