0
点赞
收藏
分享

微信扫一扫

LeetCode Top 100 Liked Questions 300. Longest Increasing Subsequence (Java版; Medium)


​​welcome to my blog​​

LeetCode Top 100 Liked Questions 300. Longest Increasing Subsequence (Java版; Medium)

题目描述

Given an unsorted array of integers, find the length of longest increasing subsequence.

Example:

Input: [10,9,2,5,3,7,101,18]
Output: 4
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4.
Note:

There may be more than one LIS combination, it is only necessary for you to return the length.
Your algorithm should run in O(n2) complexity.
Follow up: Could you improve it to O(n log n) time complexity?

class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
if(n==0){
return 0;
}
//dp[i]表示以nums[i]结尾时的LIS
//dp[i] = 1 + max(dp[i-1] if nums[i]>num[i-1], dp[i-2],...dp[0])
int[] dp = new int[n];
int res = 0;
for(int i=0; i<n; i++){
dp[i] = 1;
for(int j=0; j<i; j++){
if(nums[i] > nums[j]){
dp[i] = Math.max(dp[i], 1+dp[j]);
}
}
res = Math.max(res, dp[i]);
}
return res;
}
}

第一次做;二分法; 二分到死:找有序数组中第一个大于target的数; 二分法出错时, 可以检查一下是不是while()中的if else if写错了, 比如, 应该是在arr[]数组中进行二分查找, 但是错写成nums[]了; 这个方法很trick, 主要是掌握二分法出错的检查方法和下面的动态规划的解法

class Solution {
public int lengthOfLIS(int[] nums) {
if(nums==null || nums.length==0)
return 0;
int n = nums.length;
//arr[]是辅助数组, 当处理完所有的nums[]元素后, arr[]中非零元素的长度就是LIS
int[] arr = new int[n];
//第一个数不存在之前的数, 所以直接放到arr[0]处
arr[0]=nums[0];
//记录最常LIS最后一个元素的索引
int end=0;
//细节:i从1开始, 因为0已经处理过了
for(int i=1; i<n; i++){
if(nums[i] > arr[end])
arr[++end] = nums[i];
else{
//使用二分法查到arr[0,end]范围上第一个大于nums[i]的元素; 不能有等于的元素....否则会无限循环
int left = 0;
int right = end;
while(left < right){
int mid = left + ((right - left)>>1);
if(arr[mid] < nums[i])
left = mid + 1;
else
right = mid;
}
//here, left==right,nums[left]>nums[i]
arr[left] = nums[i];
}
}
return end+1;
}
}

第一次做; 动态规划; 两个核心:dp[i]的含义; dp数组的初始化; 方法论: dp[i]的两大类含义: 1) [0,i]上的最终结果 2)[0,i]上并且以nums[i]结尾的最终结果

执行用时 :
14 ms, 在所有 java 提交中击败了72.38%的用户
内存消耗 :36.6 MB, 在所有 java 提交中击败了31.32%的用户

/*
动态规划,
核心1:dp[i]的含义
核心2:初始化
*/
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums==null || nums.length==0)
return 0;
//dp[i]表示以nums[i]结尾的LIS
int[] dp = new int[nums.length];
//必须每个位置都初始化为1, 不能初始化0位置, 如果只初始化0位置, 这个例子不能通过: [10,9,2,5,3,7,101,18]
for(int i=0; i<nums.length; i++)
dp[i]=1;

int max=1;
for(int i=1; i<nums.length; i++){
for(int j=0; j<i; j++){
if(nums[j] < nums[i])
dp[i] = Math.max(dp[i], dp[j]+1);
}
max = Math.max(max, dp[i]);
}
return max;
}
}

第一次做; 改进的暴力递归; 增加记忆数组memo[pre][curr],减少重复子问题的计算; 3个核心: 1) memo[][]的初始化, Arrays.fill() 2)pre==-1时, 不管curr是什么,都可以选择或者不选择nums[curr] 3)由于pre范围[-1,nums.length-1], -1不能作为索引, 所以同意约定, memo[pre][curr]的pre实际表示pre-1, 只需要对memo中的pre加一, 其它地方pre的形式不变

执行用时 :
116 ms, 在所有 java 提交中击败了5.22%的用户
内存消耗 :78.1 MB, 在所有 java 提交中击败了5.01%的用户

/*
改进的暴利递归, 记忆状态, 避免重复子问题的计算(拿空间换时间)
*/
import java.util.Arrays;


class Solution {
public int lengthOfLIS(int[] nums) {
if(nums==null || nums.length==0)
return 0;
//memo[i][j]表示pre索引是i,当前索引是j的LIS
//pre范围[-1,nums.length-1], 所以pre所在的维度有n+1个元素
//由于-1不能作为索引, 所以让pre范围为[0,nums.length],实际含义还是[-1,nums.length-1]
int[][] memo = new int[nums.length+1][nums.length];
for(int[] m : memo)
Arrays.fill(m, -1);

return core(memo, nums, -1, 0);
}
public int core(int[][]memo, int[] nums, int pre, int curr){
if(curr==nums.length)
return 0;
//计算过的子问题就不再重复计算了
if(memo[pre+1][curr]>=0)
return memo[pre+1][curr];
int chose = -1, notchose;
//pre==-1时可以选, 这样就避免了nums[-1]的错误
if(pre==-1 || nums[curr] > nums[pre])
chose = core(memo, nums, curr, curr+1) + 1;
notchose = core(memo, nums, pre, curr+1);
//更新记忆状态,减少重复子问题的计算
memo[pre+1][curr] = Math.max(chose, notchose);
return memo[pre+1][curr];
}
}

第一次做; 暴力递归; 超时21/24; 核心: 初始化

/*
暴力递归, 每个位置的数字, 选或者不选, 如果前一个数字比当前数字大于等于, 那么当前数字不选
*/
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums==null || nums.length==0)
return 0;
//
return core(nums, 0, Long.MIN_VALUE);
}

/*
递归函数逻辑: 获取[index, n-1]上最大上升子序列长度
*/
public int core(int[] nums, int index, long pre){
if(index==nums.length)
return 0;
int chose=-1, notchose;
if(nums[index] > pre)
chose = core(nums, index+1, nums[index]);
notchose= core(nums,index+1, pre);
//return的写法是错误的, 错在chose+1, 即使没有执行chose = core(nums, index+1, nums[index])最后也会执行chose+1, 所以错误的
//也就是没有执行chose = core(nums, index+1, nums[index])的时候也不能执行chose+1
//修改方法: 初始值置为-1, 这样的话, 执行chose = core(nums, index+1, nums[index])就会覆盖-1; 没有执行chose = core(nums, index+1, nums[index]), chose+1==0, 不影响结果)
return Math.max(chose+1, notchose);
}
}

优秀题解; 提到了动态规划和数学归纳法的联系:动态规划的核心设计思想是数学归纳法。

LeetCode Top 100 Liked Questions 300. Longest Increasing Subsequence (Java版; Medium)_初始化


举报

相关推荐

0 条评论