算法--数组--长度最小的子数组

71 阅读3分钟

算法--数组--长度最小的子数组

力扣题目链接

给定一个含有  n  个正整数的数组和一个正整数  s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

示例:

输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组  [4,3]  是该条件下的长度最小的子数组。

提示:

  • 1 <= target <= 10^9
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

1 解答

先写再看噢~

解答

这个讲得很详细👉代码随想录

1.1 思路

没太看懂题目,先列出来找一下规律,1个,2个之和,3个之和,...

/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function (target, nums) {
  // 先列出来找规律,1个,2个之和,3个之和,...

  // 1个
  for (let i = 0; i <= nums.length; i++) {
    if (nums[i] >= target) {
      return 1;
    }
  }

  // 2个
  for (let i = 0; i + 1 <= nums.length; i++) {
    if (nums[i] + nums[i + 1] >= target) {
      return 2;
    }
  }

  // 3个
  for (let i = 0; i + 2 <= nums.length; i++) {
    if (nums[i] + nums[i + 1] + nums[i + 2] >= target) {
      return 3;
    }
  }

  // ...

  // k个
  for (let i = 0; i + k <= nums.length; i++) {
    // const sum = nums[i] + nums[i + 1] + ... + nums[i + k];

    // 求和
    let sum = 0;
    for (let j = i; j <= i + k; j++) {
      sum += nums[j];
    }

    if (sum >= target) {
      return k;
    }
  }

  // ...

  return 0;
};

整合一下这个规律

// 三层 for 循环:长度为 k 的窗口(循环一次)滑过去(循环一次),窗口内求和(循环一次)
var minSubArrayLen = function (nums, target) {
  for (let k = 0; k <= nums.length; k++) {
    for (let i = 0; i + k <= nums.length; i++) {
      let sum = 0;
      for (let j = i; j <= i + k; j++) {
        sum += nums[j];
      }

      if (sum >= target) {
        return k + 1;
      }
    }
  }
  return 0;
};

1.2 优化

var minSubArrayLen = function (target, nums) {
  let result = Infinity; // 最终的结果
  let sum = 0; // 子序列的数值之和
  let subLength = 0; // 子序列的长度
  for (let i = 0; i < nums.length; i++) {
    // 设置子序列起点为i
    sum = 0; // 总共运行 n(i=0,1,2,...,n-1) 次
    for (let j = i; j < nums.length; j++) {
      // 设置子序列终止位置为j
      sum += nums[j]; // 总共运行 n-1(i=0), n-2(i=1), n-3(i=2), ... ,1(i=n+1) 次
      if (sum >= target) {
        // 一旦发现子序列和超过了s,更新result
        subLength = j - i + 1; // 取子序列的长度
        result = result < subLength ? result : subLength; // 取小
        break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
      }
    }
  }
  // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
  return result == Infinity ? 0 : result;
};

两层 for 循环,滑动窗口起点指向第i个(循环一次),终点j指向后一个,j不断往后移,边移边加(循环一次,长度小的留下),

o(n^2) = n + n-1 + n-2 + n-3 + ... + 1 = (1+n) * n /2 = n^2

1.3 优化--滑动窗口

一层 for 循环,滑动窗口起点指向第i个,终点指向第j个, j不断往后移,边移边加,若超了,i往后移,边移边减, o(2*n)=o(n)

var minSubArrayLen = function (target, nums) {
  let result = Infinity; // 最终的结果
  let sum = 0; // 滑动窗口数值之和
  let i = 0; // 滑动窗口起始位置
  let subLength = 0; // 滑动窗口的长度
  for (let j = 0; j < nums.length; j++) {
    sum += nums[j]; // 总共运行 n(i=0,1,2,...,n-1) 次
    // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
    while (sum >= target) {
      // 总共运行 1(j=0), 2(j=1), 3(j=2), ... ,n(j=n-1) 次 (x错
      // 总共运行 n 次 (√对
      subLength = j - i + 1; // 取子序列的长度
      result = result < subLength ? result : subLength; // 取小
      sum -= nums[i]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
      i++;
    }
  }
  // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
  return result == Infinity ? 0 : result;
};

不是两层循环就是n^2,i,j 最多移动n次

因为 target 为正整数,所以 满足 sum >= target 的窗口至少有1个数

因为每次的i不是从头开始的,而是从上一次继续往下走,满足 sum >= target 的窗口至少有1个数,所以 i++ 最多执行 n 次