动态规划(更新至8.8)

动态规划也做了有一些了,挑选一些值得记录的。

题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,sell1price[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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值