209. 长度最小的子数组
问题描述
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
最容易想到的方法就是直接对数组进行遍历,然后再从遍历的位置开始查找符合条件的子数组。
时间复杂度: O ( n 2 ) O(n^2 ) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)
注:暴力法虽然思路简单,容易想到,但是时间复杂度太高,所以考虑有无简单的方法。
实际上滑动窗口和之前的双指针法很像,也是两个指针控制程序的执行,只是写法略有不同,我自己实现的代码写出来就很像双指针法
参考代码如下
int minSubArrayLen(int target, int* nums, int numsSize){
int headIndex = 0, rearIndex = 1;
int sum = nums[0];
int minLength = numsSize+1;
while(1)
{
if(sum >= target)
{
if(minLength > rearIndex - headIndex) minLength = rearIndex - headIndex;
sum -= nums[headIndex++];
}
else
{
if(rearIndex != numsSize) sum += nums[rearIndex++];
else break;
}
}
if(minLength == numsSize + 1)return 0;
else return minLength;
}
通过headIndex和rearIndex来记录下标,通过前后指针的移动对指针之间的数组元素的和进行相应变化,然后找到最终结果。
这样的写法有点理解起来比较困难,参考了其他人的写法,觉得另一种写法更好点。
参考代码如下
int minSubArrayLen(int target, int* nums, int numsSize){
int result = INT32_MAX;
int sum = 0; // 滑动窗口数值之和
int headIndex = 0, rearIndex = 0;
int minLength = 0;
for (; rearIndex < numsSize; rearIndex++) {
sum += nums[rearIndex];
while (sum >= target) {
minLength = (rearIndex - headIndex + 1);
result = result < minLength ? result : minLength;
sum -= nums[headIndex++];
}
}
return result == INT32_MAX ? 0 : result;
}
通过rearIndex前移引入新的元素,当两个下标内的数组元素和大于等于target后,再通过headIndex前移去掉旧的元素,同时更新minLength。
总结:实际上两种写法在执行上只有微小的差别,但是从可阅读性上来说,后面一种要优于前面一种
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)
问题描述:
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
- 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
- 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
- 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。
实现:
int totalFruit(int* fruits, int fruitsSize){
int headIndex = 0, rearIndex = 0;
int maxLength = 0;
int baskets[4];
baskets[0] = -1;
baskets[1] = 0;
baskets[2] = -1;
baskets[3] = 0;
for(;rearIndex < fruitsSize; rearIndex++)
{
if(baskets[0] != fruits[rearIndex])
{
if(baskets[2] != fruits[rearIndex])
{
if(maxLength < baskets[1]+baskets[3]) maxLength = baskets[1]+baskets[3];
while(baskets[1] != 0 && baskets[3] != 0)
{
if(baskets[0] == fruits[headIndex++]) baskets[1]--;
else baskets[3]--;
}
if(baskets[1] == 0)
{
baskets[0] = fruits[rearIndex];
baskets[1] = 1;
}
else
{
baskets[2] = fruits[rearIndex];
baskets[3] = 1;
}
}
else
{
baskets[3]++;
}
}
else
{
baskets[1]++;
}
}
if(maxLength < baskets[1]+baskets[3]) maxLength = baskets[1]+baskets[3];
return maxLength;
}
稍微做了一点改进:
int totalFruit(int* fruits, int fruitsSize){
int headIndex = 0, rearIndex = 0;
int maxLength = 0;
int baskets[4];
if(fruitsSize <= 2) return fruitsSize;
baskets[0]=fruits[rearIndex++];
baskets[1]= 1;
while(baskets[0] == fruits[rearIndex++] && rearIndex < fruitsSize)baskets[1]++;
if(rearIndex == fruitsSize) return fruitsSize;
baskets[2]=fruits[rearIndex - 1];
baskets[3]= 1;
for(;rearIndex < fruitsSize; rearIndex++)
{
if(baskets[0] != fruits[rearIndex])
{
if(baskets[2] != fruits[rearIndex])
{
if(maxLength < baskets[1]+baskets[3]) maxLength = baskets[1]+baskets[3];
while(baskets[1] != 0 && baskets[3] != 0)
{
if(baskets[0] == fruits[headIndex++]) baskets[1]--;
else baskets[3]--;
}
if(baskets[1] == 0)
{
baskets[0] = fruits[rearIndex];
baskets[1] = 1;
}
else
{
baskets[2] = fruits[rearIndex];
baskets[3] = 1;
}
}
else
{
baskets[3]++;
}
}
else
{
baskets[1]++;
}
}
if(maxLength < baskets[1]+baskets[3]) maxLength = baskets[1]+baskets[3];
return maxLength;
}
问题描述:
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
- 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
- 如果 s 中存在这样的子串,我们保证它是唯一的答案。
实现:
int inStr(char a, char* t, int tLength)
{
for(int i = 0; i < tLength; i++) if(a == t[i]) return 1;
return 0;
}
void replace(char a, char* t, int* tPostion, int tLength, int* minLengthHeadIndex, int* minLengthRearIndex, int rearIndex)
{
int minPostion = INT32_MAX, maxPostion = 0;
int targetMinPostionIndex = 0;
while(t[targetMinPostionIndex] != a) targetMinPostionIndex++;
for(int i = 0; i < tLength; i++) if(t[i] == a && tPostion[i] < tPostion[targetMinPostionIndex]) targetMinPostionIndex = i;
tPostion[targetMinPostionIndex] = rearIndex;
for(int i = 0; i < tLength; i++) if(tPostion[i] > maxPostion) maxPostion = tPostion[i];
for(int i = 0; i < tLength; i++) if(tPostion[i] < minPostion) minPostion = tPostion[i];
if(maxPostion - minPostion < *minLengthRearIndex - *minLengthHeadIndex)
{
*minLengthHeadIndex = minPostion;
*minLengthRearIndex = maxPostion;
}
}
int init(char a, char* t, int* tPostion, int tLength, int* minLengthHeadIndex, int* minLengthRearIndex, int rearIndex)
{
int targetMinPostionIndex = 0;
while(t[targetMinPostionIndex] != a) targetMinPostionIndex++;
for(int i = 0; i < tLength; i++) if(t[i] == a && tPostion[i] < tPostion[targetMinPostionIndex]) targetMinPostionIndex = i;
tPostion[targetMinPostionIndex] = rearIndex;
// for(int i = 0; i < tLength; i++) printf("%d ",tPostion[i]);
// printf("\n");
for(int i = 0; i < tLength; i++) if(tPostion[i] == -1) return 0;
for(int i = 0; i < tLength; i++) if(tPostion[i] > *minLengthRearIndex) *minLengthRearIndex = tPostion[i];
for(int i = 0; i < tLength; i++) if(tPostion[i] < *minLengthHeadIndex) *minLengthHeadIndex = tPostion[i];
return 1;
}
char* minWindow(char * s, char * t){
int rearIndex = 0;
int minLengthHeadIndex = INT32_MAX, minLengthRearIndex = 0;
int tLength = strlen(t);
int* tPostion = (int*)malloc(sizeof(int) * tLength);
char * result;
for(int i = 0; i < tLength; i++) tPostion[i] = -1;
for(; s[rearIndex]; rearIndex++)
if(inStr(s[rearIndex], t, tLength))
if(init(s[rearIndex], t, tPostion, tLength, &minLengthHeadIndex, &minLengthRearIndex, rearIndex)) break;
if(!s[rearIndex])
{
result = (char*)malloc(sizeof(char));
result[0] = 0;
return result;
}
for(++rearIndex; s[rearIndex]; rearIndex++)if(inStr(s[rearIndex], t, tLength)) replace(s[rearIndex], t, tPostion, tLength, &minLengthHeadIndex, &minLengthRearIndex, rearIndex);
result = (char*)malloc(sizeof(char)*(minLengthRearIndex - minLengthHeadIndex + 2));
for(int i = 0; i <= minLengthRearIndex - minLengthHeadIndex; i++) result[i] = s[i + minLengthHeadIndex];
result[minLengthRearIndex - minLengthHeadIndex + 1] = 0;
return result;
}
这个实现方式实际上也算是滑动窗口,但是由于每次更新rearIndex指针后都需要在字符串t中查找,更新tPostion数组,以及遍历tPostion数组来更新minLengthRearIndex和minLengthHeadIndex,导致时间复杂度为 O ( m n ) O(mn) O(mn),空间复杂度为 O ( m ) O(m) O(m)。
这种实现通不过leetcode的最后一个测试用例,所以还要做改进。
实现(改进后):
int capitalLetters[26];
int letters[26];
void init(char* t)
{
int i = 0;
for(i = 0; i < 26; i++)
{
letters[i] = 0;
capitalLetters[i] = 0;
}
i = 0;
while(t[i])
{
if(t[i]<='z' && t[i] >='a') ++letters[t[i]-'a'];
else ++capitalLetters[t[i]-'A'];
++i;
}
for(i = 0; i < 26; i++)
{
if(letters[i] == 0) letters[i] = INT32_MIN;
if(capitalLetters[i] == 0) capitalLetters[i] = INT32_MIN;
}
}
int catch(char a)
{
if(a <='z' && a >='a')
{
if(letters[a-'a'] == INT32_MIN) return 0;
--letters[a-'a'];
}
else
{
if(capitalLetters[a-'A'] == INT32_MIN) return 0;
--capitalLetters[a-'A'];
}
return 1;
}
int noSurplus()
{
for(int i = 0; i < 26; i++)
{
if(letters[i] > 0 || capitalLetters[i] > 0) return 0;
}
return 1;
}
void release(char a)
{
if(a <='z' && a >='a')
{
if(letters[a-'a'] != INT32_MIN) ++letters[a-'a'];
}
else
{
if(capitalLetters[a-'A'] != INT32_MIN) ++capitalLetters[a-'A'];
}
}
char* minWindow(char * s, char * t){
int headIndex = 0, rearIndex = 0;
int minLengthHeadIndex = 0, minLengthRearIndex = INT32_MAX;
char * result;
init(t);
for(; s[rearIndex]; ++rearIndex)
{
if(!catch(s[rearIndex])) continue;
while(noSurplus())
{
if(rearIndex - headIndex < minLengthRearIndex - minLengthHeadIndex)
{
minLengthRearIndex = rearIndex;
minLengthHeadIndex = headIndex;
}
release(s[headIndex++]);
}
}
if(minLengthRearIndex == INT32_MAX)
{
result = (char*)malloc(sizeof(char));
result[0] = 0;
return result;
}
else
{
result = (char*)malloc(sizeof(char)*(minLengthRearIndex - minLengthHeadIndex + 2));
for(int i = 0; i <= minLengthRearIndex - minLengthHeadIndex; i++) result[i] = s[i + minLengthHeadIndex];
result[minLengthRearIndex - minLengthHeadIndex + 1] = 0;
return result;
}
}
通过capitalLetters和letters数组来记录t中出现的字母的频次,这样的话每次更新rearIndex和headIndex只需要遍历capitalLetters和letters数组,这样的话时间复杂度是 O ( n ) O(n) O(n),空间复杂度是 O ( 1 ) O(1) O(1)。