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

相关文章
|
29天前
|
算法
测试工程师的技能升级:LeetCode算法挑战与职业成长
这篇文章通过作者亲身体验LeetCode算法题的过程,探讨了测试工程师学习算法的重要性,并强调了算法技能对于测试职业成长的必要性。
44 1
测试工程师的技能升级:LeetCode算法挑战与职业成长
|
1月前
|
机器学习/深度学习 算法 Java
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
这篇文章介绍了基于贪婪技术思想的Prim算法和Dijkstra算法,包括它们的伪代码描述、Java源代码实现、时间效率分析,并展示了算法的测试用例结果,使读者对贪婪技术及其应用有了更深入的理解。
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
|
1月前
|
算法 Java 测试技术
算法设计(动态规划实验报告) 基于动态规划的背包问题、Warshall算法和Floyd算法
这篇文章介绍了基于动态规划法的三种算法:解决背包问题的递归和自底向上实现、Warshall算法和Floyd算法,并提供了它们的伪代码、Java源代码实现以及时间效率分析。
算法设计(动态规划实验报告) 基于动态规划的背包问题、Warshall算法和Floyd算法
|
1月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
37 6
|
1月前
|
存储 算法 Java
LeetCode经典算法题:打家劫舍java详解
LeetCode经典算法题:打家劫舍java详解
46 2
|
1月前
|
人工智能 算法 Java
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
37 1
|
1月前
|
存储 算法 Java
LeetCode经典算法题:预测赢家+香槟塔java解法
LeetCode经典算法题:预测赢家+香槟塔java解法
35 1
|
1月前
|
存储 算法 Java
LeetCode经典算法题:二叉树遍历(递归遍历+迭代遍历+层序遍历)以及线索二叉树java详解
LeetCode经典算法题:二叉树遍历(递归遍历+迭代遍历+层序遍历)以及线索二叉树java详解
60 0
|
1月前
|
算法 Java
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
32 0
|
1月前
|
算法 Java
LeetCode初级算法题:环形链表+排列硬币+合并两个有序数组java解法
LeetCode初级算法题:环形链表+排列硬币+合并两个有序数组java解法
43 0