先对数组进行排序 这个很重要
之后再利用count变量储存连续的次数,如果和上一个不连续,就重置count 并把count的值保存到res变量中,最后返回res即可
注意:
时间复杂度O(n)+O(nlogn)
空间复杂度O(1)
class Solution {
public int longestConsecutive(int[] nums) {
if (nums.length == 0 || nums == null) {
return 0;
}
Arrays.sort(nums);
int len=nums.length;
int count = 1;
int res=0;
for (int i = 1; i < len; i++) {
if (nums[i] == nums [i-1]) { //如果和上一个相同,直接跳过本次循环
continue;
}
if((nums[i] - nums[i-1]) == 1){
count++;
}else{
res=Math.max(count,res);
count=1;
}
}
return Math.max(res,count);
}
}
具体过程如下:
O(n)
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> hash = new HashSet<Integer>();
for(int x : nums) //放入hash表中
hash.add(x);
int res = 0;
for(int x : hash)
{
if(!hash.contains(x-1))
{
int y = x; //以当前数x向后枚举
while(hash.contains(y + 1))
y++;
res = Math.max(res, y - x + 1); //更新答案
}
}
return res;
}
}
比如,以 nums = [10,9,2,5,3,7,101,18] 为例,我们开辟一个数组 arr 大小与nums相同,同时声明一个变量 size 记录 arr 实际存储了多少个元素:
遍历到 nums[0] (10)时,arr 为空,直接放进去,arr = [10];
遍历到 nums[1](9)时,9 比 10 小,9 替换掉 10,arr = [9];
遍历到 nums[2](2)时,2 比 9 小,2 替换掉 9,arr = [2];
遍历到 nums[3](5)时,5 比 2 大,在后面累加,arr = [2, 5];
遍历到 nums[4](3)时,3 比 2 大,比 5 小,把 5 替换掉,arr = [2, 3],这样的话,后面如果有 4 ,是可以放在 3 后面的,也就是增长得更缓慢一些;
遍历到 nums[5](7)时,7 比 3 大,在后面累加,arr = [2, 3, 7];
遍历到 nums[6](101)时,101 比 7 大,在后面累加,arr = [2, 3, 7, 101];
遍历到 nums[7](18)时,18 比 7 大,比 101 小,把 101 替换掉,arr = [2, 3, 7, 18]。
最后,arr 的实际大小 size 就是我们求得的结果:最长递增子序列的长度。
class Solution {
public int lengthOfLIS(int[] nums) {
int len = nums.length;
if (len <= 1) {
return len;
}
int size = 1; // 表示arr数组实际的大小
int[] arr = new int[len];
arr[0] = nums[0]; // 初始化第0个元素
// 遍历nums中所有元素
for (int i = 1; i < len; i++) {
// 如果比arr最后一个元素大,直接在后面累加
if (nums[i] > arr[size - 1]) {
arr[size++] = nums[i];
} else {
// 否则的话,在arr中寻找它应该在的位置
// 即在arr中寻找大于等于它的元素的位置
int right = size - 1;
// 从后向前遍历找到这个位置
while (right >= 0 && arr[right] >= nums[i]) {
right--;
}
// 上面多减了一次,要补回来,使用更小的替换
arr[right + 1] = nums[i];
}
}
return size;
}
}
把寻找位置的过程改成二分法而已,为什么可以使用二分法呢?
因为 arr 数组是递增的,天然适合使用二分法查找,代码如下:
class Solution {
public int lengthOfLIS(int[] nums) {
int len = nums.length;
if (len <= 1) {
return len;
}
int size = 1; // 表示arr数组实际的大小
int[] arr = new int[len];
arr[0] = nums[0]; // 初始化第0个元素
for (int i = 1; i < len; i++) {
if (nums[i] > arr[size - 1]) {
arr[size++] = nums[i];
} else {
// 利用二分法查找nums[i]在数组arr中的大于等于它的元素的位置
//左闭右开的二分
int left = 0, right = size;
while (left < right) {
int mid = (left + right) / 2;
if (arr[mid] >= nums[i]) {
right = mid;
} else {
left = mid + 1;
}
}
//这里是将比结果数组中更小的元素 替换到结果数组中,不会改变结果数组大小
//结果数组中符合条件的最小的
arr[left] = nums[i];
}
}
return size;
}
}
class Solution {
public int lengthOfLIS(int[] nums) {
int len = nums.length;
if (len <= 1) {
return len;
}
int ans = 0;
int[] memo = new int[len];
//以每个元素为起点的最长递增子序列
for (int i = 0; i < len; i++) {
// 已经搜索过的直接跳过
if (memo[i] == 0) {
ans = Math.max(ans, dfs(nums, i, memo));
}
}
return ans;
}
private int dfs(int[] nums, int index, int[] memo) {
if (memo[index] != 0) {
return memo[index];
}
int ans = 0;
// 需要下探后面所有比当前元素大的元素
for (int i = index + 1; i < nums.length; i++) {
if (nums[i] > nums[index]) {
ans = Math.max(ans, dfs(nums, i, memo));
}
}
memo[index] = ++ans;
return ans;
}
}
有了记忆化搜索,我们转成动态规划就非常简单了,我们可以这样定义动态规划:
class Solution {
public int lengthOfLIS(int[] nums) {
int len = nums.length;
if (len <= 1) {
return len;
}
int ans = 0;
int[] dp = new int[len];
Arrays.fill(dp, 1); //初始化,仅一个单词的长度肯定为1
for (int i = 1; i < len; i++) {
// 遍历 i 之前的所有元素,看有没有比它小的,有就可以从它们转移过来
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1); // +1 表示加上当前节点本身的长度1
}
}
ans = Math.max(ans, dp[i]);
}
return ans;
}
}