LeetCode - 209. 长度最小的子数组(前缀+二分 , 滑动窗口)

209. 长度最小的子数组

 

解题思路:

1.自己写的时间复杂度O(n*logn)

首先想到了前缀和,一个辅助的数组sum记录前缀和。

①在遍历原数组时判断是否有一个元素值 >= target,如果有则返回1,这肯定是最小的子数组了。

②判断最后一个元素的前缀和 ,如果sum[nums.size()+1] < target ,前缀和的最大值都没有 > target,直接返回 0。

③先找到前缀和 >= target 的下标 i ,然后从 i 开始向后找在 nums[0] ~ nums[i]区间内的最小区间使得 sum[i] - sum[j] >= target 。

                

代码:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
     int size = nums.size();
	vector<int> sum(size + 1, 0);

	for (int i = 0; i < size; ++i)
	{
		if (nums[i] >= target)//存在一个数 >= target
			return 1;

		sum[i + 1] = nums[i] + sum[i];
	}

	if (sum[size] < target) //所有数的总和都没有 > target
		return 0;

   
	 int minlength = INT_MAX;
	 int i = 1;
	 for (; i <= size; ++i)
	 {
	 	if (sum[i] == target) //先找到 > target的下标
	 	{
	 		minlength = i;
	 	}
	 	else if (sum[i] > target)
	 	{
	 		break;
	 	}
		
	 }


	 for (; i <= size; ++i)
	 {
	 	for (int j = i - 2; j >= 0; --j)//从这个前缀和向前找最小区间
	 	{
	 		int _target = sum[i] - sum[j]; //区间的差值
	 		if (_target >= target)//只要满足了条件一定是这个区间的最小子数组
			{
				minlength = min(minlength, i - j);
				break;
	 		}
	 	} 
	 }

	return minlength;
    }
};

                        

 2.优化 - 二分查找

我们申请一个临时数组 sums,其中 sums[i] 表示的是原数组 nums 前 i 个元素的和,题中说了 “给定一个含有 n 个 正整数 的数组”,既然是正整数,那么相加的和会越来越大,也就是sums数组中的元素是递增的。我们只需要找到 sums[k]-sums[j]>=s,那么 k-j 就是满足的连续子数组,但不一定是最小的,所以我们要继续找,直到找到最小的为止。求 sums[k]-sums[j]>=s 我们可以求 sums[j]+s<=sums[k],那这样就好办了,因为数组sums中的元素是递增的,也就是排序的,我们只需要求出 sum[j]+s 的值,然后使用二分法查找即可找到这个 k即可。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
     int size = nums.size();
	vector<int> sum(size + 1, 0);

	for (int i = 0; i < size; ++i)
	{
		if (nums[i] >= target)//存在一个数 >= target
			return 1;

		sum[i + 1] = nums[i] + sum[i];
	}

	if (sum[size] < target) //所有数的总和都没有 > target
		return 0;
   
   //二分

   int minlength = INT_MAX;
   for(int i = 0; i < size-1 ;++i) //最后两个元素不用判断
   {
      int _target = target+sum[i];
      
      if(_target > sum[size])//预期值> 前缀和的最大值不合理,跳出循环
      break;
      
      int left = i+1;
      int right = size;
      while(left < right)
      {
          int mid = (left+right) >> 1;
          if(sum[mid] >= _target)
          {
              right = mid;
          }
          else
          {
              left = mid+1;
          }
      }

     //当while停下来的时候,right指向的就是使区间最小的前缀和。
     minlength = min(minlength , right-i); 
  }
 
	return minlength;
  }
};

                        

 3.滑动窗口 O(n)

定义两个下标left,right初始化为0 ,窗口值windows_nums = 0 ,

①刚开始 right 向右走,窗口值+=nums[right] , 直到windows_num > target ;

②此时left 开始向右移只要windows_sum >= target , len = right - left //新的子数组长度 ;  res = min(res, len); //更新窗口大小   windows_sum -= nums[left];//更新窗口状态

③在right < num.size() 情况下,重复①② 。

                        

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int left = 0, right = 0, res = INT_MAX, len = 0;
        int windows_sum = 0;

        for(int i= 0 ; i < nums.size() ;++i) //小优化
        {
            if(nums[i] >= target)
            return 1;
        }

        while(right < nums.size())//窗口区间为[left, right)
        {
            while(right < nums.size() && windows_sum < target) //增大窗口 ->右边先走
            {
                windows_sum += nums[right];//更新窗口状态
                right++;
            }

            while(windows_sum >= target)//收缩窗口 
            {
                len = right - left;
                res = min(res, len); //更新窗口大小
                windows_sum -= nums[left];//更新窗口状态
                left++;
            }
        }
        return res == INT_MAX? 0:res;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值