leetcode一题打尽动态规划和贪心算法之300. 最长上升子序列

动态规划和贪心算法的知识点

动态规划的核心思想是把原问题分解成子问题进行求解,也就是分治的思想。

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解

题目

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

 - 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。 
 - 你算法的时间复杂度应该为 O(n^2)

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

Go版动态规划

首先这道题其实是有两问,一个是算法时间复杂度为现O(n^2),二是时间复杂度为O(n log n)。我们先来看看怎么实现O(n^2)。
算法:动态规划

状态定义:

 1. dp[i] 的值代表 nums 前 i 个数字的最长子序列长度。 
 2. 注意 nums[i] 必须被选取。


转移方程:

 1. dp[i] = max(dp[i], dp[j] + 1) for j in [0, i)
 2. 注意只有nums[i]>nums[j]的时候,nums[i]可以接在nums[j]之后

初始状态

 1. dp[i]所有元素设置为1,表明每个元素都可以单独成为子序列

返回值

 1. 返回dp数组中的最大值

算法复杂度分析

 1. 时间复杂度 O(n^2) 
 2. 空间复杂度 O(n)

执行用时:12 ms
内存消耗:2.4 MB
func lengthOfLIS(nums []int) int {
    l := len(nums)
    if l==0 {
        return 0
    }
    
    dp,max := make([]int,l),1
    dp[0]=1

    for i:=1;i<len(nums);i++ {
        maxitem := 0
        for j:=0;j<i;j++ {
            if nums[i]>nums[j]&&dp[j]>maxitem {
                maxitem = dp[j]
            }
        }
        dp[i] = maxitem+1
        if dp[i]>max {
            max = dp[i]
        }
    }

    return max
}

PHP版贪心算法

接下来,我们思考一下如何将时间复杂度从O(n^2)提升到O(n log n)呢?其实,一般见到logn,会下意识的想到二分法。那么我们的二分法怎么使用?这就到了贪心算法+二分法的环节了。
贪心算法+二分法

解析

 - 考虑一个简单的贪心,如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。

状态设计

 - 基于上面的贪心思路,我们维护一个数组tails,其中每个元素 tails[k]的值代表 长度为k+1的子序列尾部元素的值

状态转移设计

 - 遍历数组nums,同时二分法搜索tails
 - 如果 tails 中元素都比它小,将它插到最后
 - 否则,用它覆盖掉比它大的元素中最小的那个

返回值

 - tails数组的长度

执行用时:8 ms
内存消耗:15.2 MB
class Solution {

    /**
     * @param Integer[] $nums
     * @return Integer
     */
    function lengthOfLIS($nums) {
        $size = count($nums);
        if ($size<2) { 
            return $size;
        }
       
       $tails[0] = $nums[0];
       $i = 1;
       while ($i<$size) {
           if ($nums[$i]>$tails[count($tails)-1]) {
               $tails[]=$nums[$i];
           }
           $l=0;
           $r=count($tails)-1;
           while($l<$r) {
               $mid = Floor(($l+$r)/2);
               if ($tails[$mid]<$nums[$i]) {
                   $l = $mid+1;
               } else {
                   $r = $mid;
               }
           }
           $tails[$l] = $nums[$i];
           $i++;
       }
        
        return count($tails);
    }
}

总结

  1. tails未必是真实的最长上升子序列,但长度是对的。
  2. 动态规划的核心在于分治
  3. 贪心算法的核心是局部最优解
  4. practice makes perfect
发布了19 篇原创文章 · 获赞 1 · 访问量 230

猜你喜欢

转载自blog.csdn.net/helen920318/article/details/104863629