leetcode 子数组 相关问题(分析+代码)

文章目录

        • [560. 和为K的子数组](https://leetcode-cn.com/problems/subarray-sum-equals-k/)
        • [209. 长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum/)
        • [713. 乘积小于K的子数组](https://leetcode-cn.com/problems/subarray-product-less-than-k/)
        • [1248. 统计「优美子数组」](https://leetcode-cn.com/problems/count-number-of-nice-subarrays/)
        • [581. 最短无序连续子数组](https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray/)

560. 和为K的子数组

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1][1,1] 为两种不同的情况。

分析:找到该数组中和为k的连续的子数组,实际上就是找从 i 到 j 和为k的数组个数,因此,如果用sum表示从下标0到 i 的和,也就是找 sum[i]-sum[j]=k 的个数。故可以用一个数组存储sum,然后用MAP存储从该sum值到该值出现次数的映射关系,如果遍历sum[i]时,发现sum[i]-k已经出现过,则加上其值即可。

  • 需要先判断是否存在,然后再对自身sum[i]建立映射,避免出现k=0的情况导致错误
  • 需要注意的是,sum[0]=0,即未添加任何元素,或者在map初始化时添加map[0]=1
class Solution {
     
public:
    int subarraySum(vector<int>& nums, int k) 
    {
     
        vector<int> sum(nums.size()+1);
        sum[0]=0;
        for(int i=1;i<=nums.size();i++)
        {
     
            sum[i]=sum[i-1]+nums[i-1];   
        }
        int result=0;
        //  如果从j到i和为k,则 sum[i]-sum[j-1]=k
        unordered_map<int,int> MAP; //建立映射,从 sum[i]到其值出现的次数的映射
		for(int i=0;i<sum.size();i++)
		{
     
			if(MAP.find(sum[i]-k)!=MAP.end()) //判断差为k的是否已经出现
				result+=MAP[sum[i]-k];	
            MAP[sum[i]]++;
        } 
        return result;
    }
};

但是我们发现,其实sum并不需要用数组存储起来,可以在求和过程中直接建立映射并判断即可,代码可以变得更加简洁。

class Solution {
     
public:
    int subarraySum(vector<int>& nums, int k) 
    {
     
        unordered_map<int,int> MAP;//映射关系
        MAP[0]=1; //表示没有添加任何元素
        int sum=0,result=0;
        for(int item:nums)
        {
     
            sum+=item;
            if(MAP.find(sum-k)!=MAP.end()) result+=MAP[sum-k];
            MAP[sum]++;
        }
        return result;
    }
};

209. 长度最小的子数组

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

示例:

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

分析:

方法一:双指针法,动态维护区间

求符合条件的最小的连续子区间,可以采用双指针法,动态维护一个符合条件的区间。

  • 设置两个指针,left和right,分别标记当前维护的区间的左端和右端
  • 如果当前区间和小于s,则右移right,直至大于等于s
  • 如果当前区间和大于s,则右移left,直至小于等于s (这个过程需要记录最小的区间长度)
  • 直至right遍历至数组结束,返回过程中记录的最小区间长度即可

该算法由于至多遍历两边数组,故时间复杂度是 O ( n ) O(n) O(n)

class Solution {
     
public:
    int minSubArrayLen(int s, vector<int>& nums) 
	{
     
		int left=0,right=0;//双指针,维护一个大于等于s的区间
		int ans=INT_MAX,sum=0;
		for(right=0;right<nums.size();right++)
		{
     
			sum+=nums[right];
			while(sum>=s) //如果当前的sum大于s,右移左指针,直至该条件不成立 
			{
     
				ans=min(ans,right-left+1);//刷新长度
				sum-=nums[left++];	//左指针右移,刷新sum 
			}	
		}
		return ans==INT_MAX?0:ans;	
    }
};

方法二:前缀和,二分查找

该方法思路主要是求子数组的和问题,通常都可以采用前缀和进行简化,将数组的和转化为前缀和数组中两个值的差。该题中,由于数字都是正整数,因此前缀和是单调递增的,故可以每次确定一个 s u m [ i ] sum[i] sum[i]的位置,然后利用二分查找的方法找左侧 s u m [ i ] − s sum[i]-s sum[i]s 的位置。

因为需要遍历数组,每个位置都需要进行二分查找,因此总的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)

class Solution {
     
public:
    int minSubArrayLen(int s, vector<int>& nums) 
	{
     
		vector<int> sum(nums.size()+1);
        sum[0]=0;  //注意将sum[0]初始化为0
        if(nums.size()==0) 
            return 0;
		for(int i=1;i<sum.size();i++)
			sum[i]=sum[i-1]+nums[i-1];
		int ans=INT_MAX;
		for(int i=0;i<sum.size();i++)
		{
     
			//lowerbound_bound返回大于等于val的第一个元素位置
            if(sum[i]+s>sum[sum.size()-1]) 
                break;
            //找到大于等于sum[i]+s的位置
			int pos=lower_bound(sum.begin(),sum.end(),sum[i]+s)-sum.begin();
			ans=min(ans,pos-i);  //由于多了一个sum[0],因此计算长度时实际上是pos-i即可
		}
		return ans==INT_MAX?0:ans;	
    }
};

713. 乘积小于K的子数组

给定一个正整数数组 nums。

找出该数组内乘积小于 k 的连续的子数组的个数。

示例 1:
输入: nums = [10,5,2,6], k = 100
输出: 8
解释: 8个乘积小于100的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于100的子数组。

分析:双指针法,动态维护符合要求的区间。

设立两个指针left和right,分别标记当前维护的区间的头和尾。

  • 向右移动right,动态改变区间乘积
  • 如果当前区间乘积小于k,也就说明增添的这个新元素仍使得区间符合要求,增添一个新元素会增加right+left-1个符合要求的连续子数组(考虑,增添的连续子数组为:包含新增添的这个元素,然后从区间左边第一个开始至新元素、第二个至新元素、直至仅剩新元素一个。)
  • 如果当前区间乘积大于等于k,就需要移动左节点,动态更新区间的乘积,直至小于k为止
  • 重复上述操作,直至right遍历至结尾
class Solution {
     
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) 
    {
     
        if(k<=1) return 0;
        int left=0,right=0;
        int mul=1,ans=0;
        while(right<nums.size())
        {
     
            mul*=nums[right];  //动态维护mul
            while(mul>=k)  //移动左指针
            {
     
                mul/=nums[left++];  //动态维护mul
            }
            ans+=right-left+1;  //增加的符合要求的区间数量
            right++;
        }
        return ans;
    }
};

1248. 统计「优美子数组」

给你一个整数数组 nums 和一个整数 k。

如果某个 连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。

请返回这个数组中「优美子数组」的数目。

示例 1:
输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1][1,2,1,1] 。

示例 2:
输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。

示例 3:
输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16

分析:优美子数组是指含有指定的奇数数字的连续子数组,因此在此可以采用滑动窗口法即双指针法

  • 设置一个数组,用来存储奇数的位置(这样也方便记录相邻奇数间的偶数个数)
  • 在数组的头和尾插入一个奇数,即-1和nums.size()的位置,为了避免无限循环和遗漏末尾
  • 每遇到一个奇数就将奇数位置放入数组
  • 当满足要求时,区间可以通过设置的数组知道,在不违反要求的前提下当前区间可以进行拓展,即让区间左端点向左移动,区间右端点向右移动,直至不能拓展,就是记录左侧有多少偶数L,右侧有多少偶数R,包含这k个奇数的区间实际上有 (L+1)*(R+1)个,(实现时我直接用了索引差,就不需要再加1),然后右移左端点至下一个奇数,继续遍历
class Solution {
     
public:
    int numberOfSubarrays(vector<int>& nums, int k)
    {
     
        vector<int> odd_pos; //记录奇数的位置 
        int ans = 0;
        int left = 1; //记录左侧 
        odd_pos.push_back(-1); //等价于在nums的最左侧增加一个奇数,方便判断 
        for (int right = 0; right <= nums.size(); right++)
        {
     
            if (right == nums.size()||(nums[right] & 1))  //末尾处也加一个奇数
            {
     
                odd_pos.push_back(right);
            }

            if (odd_pos.size() - left > k) 
            {
     
                int L = odd_pos[left] - odd_pos[left - 1];
                int R = right - odd_pos[odd_pos.size() - 2];
                ans += L * R;
                left++;
            }
        }
        return ans;
    }
};

581. 最短无序连续子数组

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

你找到的子数组应是最短的,请输出它的长度。

示例 1:
输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。

方法一:排序法

利用快排将数组排序,比较排序后的数组与原始数组第一个和最后一个不同的位置,这两个位置就是需要整理的最短无序连续子数组。

时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) 空间复杂度为 O ( n ) O(n) O(n)

class Solution {
     
public:
    int findUnsortedSubarray(vector<int>& nums) 
    {
     
        vector<int> sort_nums=nums;
        sort(sort_nums.begin(),sort_nums.end());
        int left=-1,right=-1;
        for(int i=0;i<nums.size();i++)
        {
     
            if(left==-1&&sort_nums[i]!=nums[i])
                left=i;
            if(sort_nums[i]!=nums[i])
                right=i;
        }
        return left==-1?0:(right-left+1);
    }
};

方法二:定位法

该数组大致是这样的,首先是升序子列,然后是一段子列,然后是升序子列。因此,要定位乱序的起始位置,就需要找到这之间的数组的最小值应该在的位置,最大值应该在的位置。即无序子数组中最小元素的正确位置可以决定左边界,最大元素的正确位置可以决定右边界。因此通过多遍遍历的方式,先找到最小元素最大元素,然后再次找到其应该在的位置。

时间复杂度 O ( n ) O(n) O(n) 空间复杂度 O ( 1 ) O(1) O(1)

class Solution {
     
public:
    int findUnsortedSubarray(vector<int>& nums) 
    {
     
        if(nums.size()==0||nums.size()==1) return 0;
        int left=0,right=0;
        for(int i=1;i<nums.size();i++) //确定左边界
        {
     
            if(nums[i]<nums[i-1])
            {
     
                left=i-1;
                break;
            }
        }
        for(int i=nums.size()-2;i>=0;i--) //确定右边界
        {
     
            if(nums[i]>nums[i+1])
            {
     
                right=i+1;
                break;
            }
        }
        if(left==right) return 0;
        int MIN=INT_MAX,MAX=INT_MIN;
        for(int i=left;i<=right;i++) //找到最小值最大值
        {
     
            MIN=min(MIN,nums[i]);
            MAX=max(MAX,nums[i]);
        }
        //找到最小值最大值的应该的位置
        //用left和right记录
        for(int i=0;i<nums.size();i++)
        {
     
            if(MIN<nums[i])
            {
     
                left=i;
                break;
            }
        }
        for(int i=nums.size()-1;i>=0;i--)
        {
     
            if(MAX>nums[i])
            {
     
                right=i;
                break;
            }
        }
        return right-left+1;
    }
};

你可能感兴趣的:(数据结构与算法,数据结构,算法,leetcode,子数组,双指针)