数据结构进阶篇,数组连续子序列专题

128. 最长连续序列

  「题目:」

  给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

  「示例:」

  输入:nums = [100,4,200,1,3,2],输出:4。

  「第一种解题思路:」

  对 nums 进行排序预处理,遍历 nums 的过程中,注意判断相邻元素是否相等,来过滤重复元素。

  然后,在遍历的过程中不断比较当前连续序列的长度,即可得到最优解。

  时间复杂度:O(nlogn),空间复杂度:O(logn)。

const longestConsecutive = function(nums) {
    if (nums.length === 0) {
        return 0;
    }

    nums.sort((a, b) => a - b);

    let ans = 1;
    let pre = nums[0];
    let current = 1;

    for (let i = 1; i < nums.length; i++) {
        if (nums[i] === nums[i - 1]) {
            continue;
        }

        if (nums[i] - pre === 1) {
            current++;
            ans = Math.max(ans, current);
        } else {
            current = 1;
        }
        pre = nums[i];
    }

    return ans;
};

  「第二种解题思路:」

  先通过哈希表对 nums 进行去重预处理。

  然后,遍历哈希表,以当前元素为连续序列的起点,搜索其最大的长度,但是这样实现的时间复杂度为 O(n^2)。

  对于每一个连续序列,其实只需要对它的起点元素进行搜索即可,这样整体的时间复杂度可以优化为 O(n)。

  时间复杂度:O(n),空间复杂度:O(n)。

const longestConsecutive = function(nums) {
    if (nums.length === 0) {
        return 0;
    }
    const record = new Set();

    let ans = 0;

    for (let i = 0; i < nums.length; i++) {
        record.add(nums[i]);
    }

    for (const num of record.values()) {

        if (!record.has(num - 1)) {
            let currentNum = num;
            let currentLength = 1;

            while (record.has(currentNum + 1)) {
                currentNum++;
                currentLength++;
            }

            ans = Math.max(ans, currentLength);
        }
    }

    return ans;
};

581. 最短无序连续子数组

  「题目:」

  给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

  请你找出符合题意的最短子数组,并输出它的长度。

  「示例:」

  输入:nums = [2,6,4,8,10,9,15],输出:5。

  「第一种解题思路:」

  获取到升序之后的数组,利用双指针从两端开始扫描,当升序数组中的元素与 nums 数组中不一致时,则该下标为最短子数组的起始或者终止下标。

  时间复杂度:O(nlogn),空间复杂度:O(logn)。

const findUnsortedSubarray = function(nums) {
    const sortNums = [...nums].sort((a, b) => a - b );

    let start = 0;
    let end = nums.length - 1;

    while (start < nums.length - 1 && sortNums[start] === nums[start]) {
        start++;
    }

    while (end >= 0 && sortNums[end] === nums[end]) {
        end--;
    }

    return end > start ? end - start + 1 : 0;
};

  「第二种解题思路:」

  利用双指针技巧,左指针向右扫描的过程中,通过最大值与当前元素的比较,可以找出右边界;右指针向左扫描的过程中,通过最小值与当前元素的比较,可以找出左边界。

  时间复杂度:O(n),空间复杂度:O(1)。

const findUnsortedSubarray = function(nums) {
    const maxIndex = nums.length - 1;
    let start = 0;
    let end = -1;
    let max =  nums[0];
    let min = nums[nums.length - 1];

    for (let i = 0; i < nums.length; i++) {
        if (nums[i] < max) {
            end = i;
        } else {
            max = nums[i];
        }

        if (nums[maxIndex - i] > min) {
            start = maxIndex - i;
        } else {
            min = nums[maxIndex - i];
        }
    }

    return end - start + 1;
};

523. 连续的子数组和

  「题目:」

  给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:

  • 子数组大小至少为 2。

  • 子数组元素总和为 k 的倍数。

  如果存在,返回 true ;否则,返回 false 。

  0 始终视为 k 的一个倍数。

  「示例:」

  输入:nums = [23,2,4,6,7], k = 6,输出:true。

  「第一种解题思路:」

  朴素的思路是嵌套遍历 nums 数组,找到子数组的起终点,然后再求和,这样需要 O(n^3) 的时间复杂度。

  通过前缀和技巧,可以将动态求和的 O(n) 时间复杂度优化为直接查表的 O(1) 时间复杂度。

  时间复杂度:O(n^2),空间复杂度:O(n)。

const checkSubarraySum = (nums, k) => {
  const length = nums.length;
  if (length < 2) {
    return false;
  }

  const preSum = Array(length + 1).fill(0);

  for (let i = 1; i <= length; i++) {
    preSum[i] = preSum[i - 1] + nums[i - 1];
  }

  for (let i = 0; i < length + 1; i++) {
    for (let j = i + 2; j < length + 1; j++) {
      if ((preSum[j] - preSum[i]) % k === 0) {
        return true;
      }
    }
  }
  return false;
}

  接下来,还可以对寻找子数组的嵌套循环进行优化,假设 [i, j] 为目标区间,那么:preSum[j] - preSum[i - 1] = n*k。

  对上述表达式进行变形可得:sum[j] / k - sum[i - 1] / k = n。

  要想使得两数除 k 相减为整数,那么 sum[j] 和 sum[i - 1] 对 k 取余应该相同。

  再结合哈希表记录遍历过程中最早出现的余数及其下标,就可以将 O(n^2) 优化为 O(n)。

  时间复杂度:O(n),空间复杂度:O(n)。

const checkSubarraySum = (nums, k) => {
    const length = nums.length;
    if (length < 2) {
        return false;
    }

    const preSum = Array(length + 1).fill(0);

    for (let i = 1; i <= length; i++) {
        preSum[i] = (preSum[i - 1] + nums[i - 1]) % k;
    }

    const record = new Map();

    for (let i = 0; i <= length; i++) {
        const index = record.get(preSum[i]);
        if (index !== undefined) {
            if (i - index >= 2) {
                return true;
            }
        } else {
            record.set(preSum[i], i); 
        }
    }

    return false;
}

写在最后

  「感谢您能耐心地读到这里,如果本文对您有帮助,欢迎点赞、分享、或者关注下方的公众号哟。」

  相关链接:

  • 《128. 最长连续序列》 https://leetcode-cn.com/problems/longest-consecutive-sequence/

  • 《581. 最短无序连续子数组》 https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray/

  • 《523. 连续的子数组和》 https://leetcode-cn.com/problems/continuous-subarray-sum/

数据结构进阶篇,数组连续子序列专题_第1张图片

你可能感兴趣的:(算法,数据结构,leetcode,java,动态规划)