Leetcode 题解算法之动态规划(下)

简介: Leetcode 题解算法之动态规划

Leetcode 300. 最长上升子序列


1668570033560.jpg

这道题并不是要求连续的,只要后面的数比前面的大,那么就是可以被添加到连续上升子序列中的,只是说每次应该加入较小的数字,才能给后面腾出更多的位置,道理是这么说的。


默认开始的状态一定是 1 因为就算是全部递减的值,它的最长上升子序列至少还是等于 1,也就是它本身。


DP [i]=Max (DP [i],DP [j-1]+1) 如果 $nums [i-1]<$nums [$j], 说明此时 $nums [i] 也能加入到子序列中


这个状态转移方程是什么意思呢?可以先看代码在进行解释。

/**
     * @param Integer[] $nums
     * @return Integer
     */
    function lengthOfLIS($nums)
    {
        $size = count($nums);
        if ($size < 2) return $size;
        $dp[0] = $res = 1;
        for ($i = 1; $i < count($nums); $i++) {
            $dp[$i] = 1;
            for ($j = 0; $j < $i; $j++) {
                if ($nums[$j] < $nums[$i]) {
                    $dp[$i] = max($dp[$i], $dp[$j] + 1);
                }
            }
          $res    = max($res, $dp[$i]);
        }
        return $res;
    }

两层遍历,第一层表示的到第 i 个元素,整体的意思是,每次到第 i 个元素的时候,就去看 i 之前的已经推导出来的状态。j 的值就是 0 到 i, 只要 $nums[$j] < $nums[$i] 说明 i 的位置可以利用之前推导的结果组成一个最长上升的子序列。所以就把之前推导出的 $dp[$j] +1,和已经推导出的 $dp[$i] 进行比较,获取最大值,重新存储在 $dp[i] 中。然后每次全局更新最大上升子序列值,最后返回即可。


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


1668570062243.jpg

这系列的题目很有意思,这道题只能有一次交易,就是求利益最大化的买卖。对于这里的状态定义,可能一开始难以想到,我们可以这样想,每一天我们的状态只会有三种,未持有股票 (没购买过),持有股票以及未持有股票 (抛售了):


初始状态 $dp[0][0]=$dp[0][2]=0,$dp[0][1]= -第一天的价格。什么意思呢,其实就是初始状态的定义表示第一天没买股票,那么收益的值 0,即 $dp[0][0]=0$dp[0][2] 表示第一天卖出股票的收益,因为第一天不存在卖,所以初始值也是 0。最后看 $dp[0][1]= -第一天的价格,这里表示如果第一天买了股票那么收益当然是负数的,即购买的价格。


状态定义完了,接下来就是状态转移方程了。也就是 $dp[i][0]$dp[i][1] 以及 $dp[i][2] 咋么转移,我们可以这么想第 i 天持有未购买的情况只能是前面的也没购买,第 i 天持有的情况分为两种情况,一种是前一天已经持有了或者是前一天也是未持有状态然后今天购买持有了。剩下的第 i 天已出售情况只能是前一天持有的状态然后今天抛售了。这个理清楚了,代码也就好写了。

/**
     * @param Integer[] $prices
     * @return Integer
     */
    function maxProfit($prices)
    {
        $dp[0][0] = $dp[0][2] = 0;
        $dp[0][1] = -$prices[0];
        $res      = 0;
        for ($i = 1; $i < count($prices); $i++) {
            $dp[$i][0] = $dp[$i - 1][0];
            $dp[$i][1] = max($dp[$i - 1][1], $dp[$i - 1][0] - $prices[$i]);
            $dp[$i][2] = $dp[$i - 1][1] + $prices[$i];
            $res = max($res, $dp[$i][0], $dp[$i][1], $dp[$i][2]);
        }
        return $res;
    }

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


1668570081833.jpg

这是上一题的扩展,上一题只能交易一次,这一题可以多次交易。这道题我不用三个维护状态维护了,只有两种状态,第 i 天 未持有股票 和第 i 天持有股票。转移方程嘛就很好理解了,持有股票最大值,之前持有的最大值和之前不持有股票今天买了,取最大值。至于不持有股票最大值也分为两种,之前就一直不持有股票值和之前持有股票值今天卖了,取最大值,最后取 dp 数组中不持有股票最后的值。因为每一次更新的都是到 i 天的交易最大值,所以最后得到的必然是全局最大值。

/**
     * @param Integer[] $prices
     * @return Integer
     */
    function maxProfit($prices)
    {
        if (count($prices) == 0) return 0;
        $dp[0][1] = -$prices[0];
        $dp[0][0] = 0;
        for ($i = 1; $i < count($prices); $i++) {
            $dp[$i][1] = max($dp[$i - 1][1], $dp[$i - 1][0] - $prices[$i]);
            $dp[$i][0] = max($dp[$i - 1][1] + $prices[$i], $dp[$i - 1][0]);
        }
        return $dp[count($prices) - 1][0];
    }

Leetcode 123. 买卖股票的最佳时机 III 终极难题


1668570100996.jpg

这道题又是上一版的扩展,因为规定了具体的交易次数,所以这一题单单是二维数据也必然不够了,因为我们还需要一个维度用来维护第 k 次交易的状态。这道题难度确实挺大的。


DP [i][k][j] 定义了一个三维数组,i 表示第几天 k 表示第几次交易 j 表示是否持有股票


然后分情况,第 i 天第 k 次都用两种情况持有股票和没有持有股票这两种状态。自己看代码理解一下吧。

/**
     * @param Integer[] $prices
     * @return Integer
     */
    function maxProfit($prices)
    {
        $res         = 0;
        $dp[0][0][0] = 0;
        $dp[0][0][1] = -$prices[0];
        $dp[0][1][0] = -$prices[0];
        $dp[0][1][1] = -$prices[0];
        $dp[0][2][0] = 0;
        for ($i = 1; $i < count($prices); $i++) {
            $dp[$i][0][0] = $dp[$i - 1][0][0];
            $dp[$i][0][1] = max($dp[$i - 1][0][1], $dp[$i - 1][0][0] - $prices[$i]);
            $dp[$i][1][0] = max($dp[$i - 1][1][0], $dp[$i - 1][0][1] + $prices[$i]);
            $dp[$i][1][1] = max($dp[$i - 1][1][0] - $prices[$i], $dp[$i - 1][1][1]);
            $dp[$i][2][0] = max($dp[$i - 1][2][0], $dp[$i - 1][1][1] + $prices[$i]);
        }
        $length = count($prices) - 1;
        return max($res, $dp[$length][0][0], $dp[$length][1][0], $dp[$length][2][0]);
    }

另外,结合自己做题之路,分享一个好方法,千万千万别一题题做下来,既然刷题当然需要针对性,一定要专门针对性的练习,哪一块数据结构或者算法短板,没关系,先补下知识,然后想刷二分?可以,Leecode (如果你看的是英文版的话,Leetcode-cn 的话更省事了,直接看中文) 首页点击 tags, 选择 Binary Serach, 这样针对性的学习,我想成长只是时间的问题吧。因为你帅我才告诉你。

1668570124733.jpg

相关文章
|
6月前
|
机器学习/深度学习 存储 算法
动态规划算法深度解析:0-1背包问题
0-1背包问题是经典的组合优化问题,目标是在给定物品重量和价值及背包容量限制下,选取物品使得总价值最大化且每个物品仅能被选一次。该问题通常采用动态规划方法解决,通过构建二维状态表dp[i][j]记录前i个物品在容量j时的最大价值,利用状态转移方程避免重复计算子问题,从而高效求解最优解。
690 1
|
5月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
11月前
|
机器学习/深度学习 算法 Go
【LeetCode 热题100】139:单词拆分(动态规划全解析+细节陷阱)(Go语言版)
本题是 LeetCode 热题 139:单词拆分(Word Break),需判断字符串 `s` 是否能由字典 `wordDict` 中的单词拼接而成。通过动态规划(DP)或记忆化搜索解决。DP 中定义布尔数组 `dp[i]` 表示前 `i` 个字符是否可拆分,状态转移方程为:若存在 `j` 使 `dp[j]=true` 且 `s[j:i]` 在字典中,则 `dp[i]=true`。初始条件 `dp[0]=true`。代码实现中用哈希集合优化查找效率。记忆化搜索则从起始位置递归尝试所有切割点。两种方法各有利弊,DP 更适合面试场景。思考扩展包括输出所有拆分方式及使用 Trie 优化大字典查找。
342 6
|
存储 算法 Java
算法系列之动态规划
动态规划(Dynamic Programming,简称DP)是一种用于解决复杂问题的算法设计技术。它通过将问题分解为更小的子问题,并存储这些子问题的解来避免重复计算,从而提高算法的效率。
470 4
算法系列之动态规划
|
算法 Java C++
【潜意识Java】蓝桥杯算法有关的动态规划求解背包问题
本文介绍了经典的0/1背包问题及其动态规划解法。
471 5
|
算法 安全 调度
【动态规划篇】穿越算法迷雾:约瑟夫环问题的奇幻密码
【动态规划篇】穿越算法迷雾:约瑟夫环问题的奇幻密码
|
机器学习/深度学习 算法 测试技术
【动态规划篇】01 背包的逆袭:如何用算法装满你的 “财富背包”
【动态规划篇】01 背包的逆袭:如何用算法装满你的 “财富背包”
|
算法
动态规划算法学习三:0-1背包问题
这篇文章是关于0-1背包问题的动态规划算法详解,包括问题描述、解决步骤、最优子结构性质、状态表示和递推方程、算法设计与分析、计算最优值、算法实现以及对算法缺点的思考。
767 2
动态规划算法学习三:0-1背包问题
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
363 4
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
311 2

热门文章

最新文章