给定一个整数数组和一个整数 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已经出现过,则加上其值即可。
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;
}
};
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的连续子数组,返回 0。
示例:
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
分析:
方法一:双指针法,动态维护区间
求符合条件的最小的连续子区间,可以采用双指针法,动态维护一个符合条件的区间。
该算法由于至多遍历两边数组,故时间复杂度是 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;
}
};
给定一个正整数数组 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,分别标记当前维护的区间的头和尾。
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;
}
};
给你一个整数数组 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
分析:优美子数组是指含有指定的奇数数字的连续子数组,因此在此可以采用滑动窗口法即双指针法。
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;
}
};
给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
你找到的子数组应是最短的,请输出它的长度。
示例 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;
}
};