用三道面试题了解一下动态规划的基本逻辑(趁热打铁第二道)

原题目链接:300. 最长递增子序列(LIS)

快点击这里征服第一道!!!
题目描述:

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
 

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

 

提示:

    1 <= nums.length <= 2500
    -104 <= nums[i] <= 104

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

做题思路:

1.  动态规划简单介绍

动态规划(Dynamic Programming)简称dp,是求解最优化问题的一种常用策略,通常来说,使用动态规划去解决问题无非就是三步

1.1  定义状态(状态是原问题,子问题的解),比如定义dp(i)的含义

1.2  设置初始状态(边界),比如设置dp(0)的值

1.3  确定状态转移方程,比如确定dp(i) 和dp(i - 1)的关系

2.  那么利用这三步,让我们看看如何解决上面这道题。

2.1  假设nums的值是{10,2,2,5,1,7,101,18},显而易见,最大递增子序列是{2,5,7,101}和{2,5,7,18}。长度为4

2.2  定义状态

假设dp(i)是以nums[i]结尾的最长递增子序列的长度,什么意思呢?例如

nums[0],10 结尾的最长上升子序列是 10,所以dp(0) = 1

nums[1],2 结尾的最长上升子序列是 2,所以dp(1) = 1

nums[2],2 结尾的最长上升子序列是 2,所以dp(2) = 1

nums[3],5 结尾的最长上升子序列是 2,5,所以dp(3) = dp(1)+ 1 = dp(2)+ 1 = 2,这里的dp(1)和dp(2)代表第一个2和第二个2都可以和5拼成最上上升子序列

nums[4],1 结尾的最长上升子序列是 1,所以dp(4) = 1

nums[5],7 结尾的最长上升子序列是 2,5,7,所以dp(5) = dp(3)+ 1 = 3

nums[6],101 结尾的最长上升子序列是 2,5,7,101,所以dp(6) = dp(5)+ 1 = 4

nums[7],18 结尾的最长上升子序列是 2,5,7,18,所以dp(7) = dp(5)+ 1 = 4

所以我们可以发现能成为拼接的最长上升子序列的元素,得具备两个条件,首先当前的元素肯定是比之前最长上升子序列的末尾值还大,因为是上升的,所以肯定要大,不然无法拼接。其次是拼接后的长度是最长的

2.3  状态转移方程

遍历j∈[0,i)

如果nums[i] > nums[j],nums[i] 可以拼接在 nums[j] 后面,形成一个比dp(j)更长的上升子序列,长度是dp(j)+ 1,所以dp(j)= max{dp(i),dp(j)+ 1}

如果nums[i] <= nums[j],nums[i] 不能拼接在 nums[j] 后面,就跳过此次遍历

2.4  初始状态

dp(0)= 1,因为只有一个元素,肯定是长度只有1,所有的dp(i)默认都初始化为1,因为我们要防止出现{3,5,1,10},当 i 等于2时,就是元素1,那么如果dp(i)没有初始化为1,那么代码中,元素1肯定会对3,5进行跳过,dp(2)= 0,显然不符合题意,因为只有元素只有一个的时候,它的长度就是1,所以这里要初始化为1

3.  如果觉得太晦涩的话,接下来我就用大白话讲解

例如你现在在打王者荣耀,nums[]数组里面元素都是各个玩家的战斗力,dp(i)里面是以i结尾的组成战队的人数,因为王者荣耀是组队战斗,每个战队要轮流挑选队长,也就是说,每个能加进队伍的玩家都是队长实力,从优择选,就像战队有两个人{村服选手,省服选手},那么这支队伍在选人的时候,肯定是希望有国服选手加入,并且担任队长职位,再比如有一条战队战力是{2,4,5,10},那么当这只队伍挑选下一个队长的时候,只有战斗力大于10的人才有资格入队,战斗力小于10的玩家就会被排除掉,以此类推,最终会出现人数最多的队伍,那种队伍就是我们需要的答案。

废话不多说,直接上代码,为了让各位看官更能清晰理解,我的代码写得不精简,我的代码里加了大量的注释,相信各位看官可以理解,如果我有些没写清楚或者写错的,可以评论区或者私信我喔

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums == null || nums.length == 0) return 0;
        int[] dp = new int[nums.length];    //由于我是从0下标开始,所以不用nums.length+1
        int max = dp[0] = 1;    //假设只有一个元素,那么最长度就是1
        for(int i = 1; i < nums.length; i++){
            dp[i] = 1;      //如果出现当前的元素是比之前的每条子序列最末位的数小,说明不能加进去,所以当前的元素的拼成的子序列,只有它本身,也就是长度是1
            for(int j = 0; j < i; j++){     
            //如果出现当前的元素是没有比之前的每条子序列最末位的数大,说明不能加进去,就跳过
                if(nums[i] <= nums[j]) continue;
                //代码执行到这里,说明当前元素是比子序列最末位的数大,所以把这个元素加到末尾
                //所以才有dp[j] + 1
                dp[i] = Math.max(dp[i],dp[j] + 1);
                //取出较大值
                max = Math.max(max,dp[i]);
            }
        }
        //返回长度最长的值
        return max;
    }
}

都看到这里了,不考虑点个赞再走嘛?如果大家不点赞的话,我的心就是冰冰的了

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值