原题目链接: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;
}
}
都看到这里了,不考虑点个赞再走嘛?如果大家不点赞的话,我的心就是冰冰的了