目录
- 1. 长度最小的数组
- 2. 无重复字符的最长字符串
- 3. 最大连续1的个数
- 4. 将x减到0的最小操作数
- 5. 水果成篮
- 6. 找到字符串中所有字母异位词
- 7. 串联所有单词的子串
- 8. 最小覆盖子串
1. 长度最小的数组
- 题目信息:
- 题目链接: 长度最小的数组
- 思路:
<1> 暴力枚举法:时间复杂度O( n 2 n^2 n2)
<2> 同向双指针:时间复杂度O(n)
同向双指针:
class Solution
{
public:
int minSubArrayLen(int target, vector<int>& nums)
{
int sum = 0;
int len = 0;
int right = -1;
int left = -1;
int size = nums.size();
while (right < size)
{
if (sum < target)
{
++right;
if (right < nums.size())
{
sum += nums[right];
}
}
else
{
if (len == 0 || len > right - left)
{
len = right - left;
}
++left;
sum -= nums[left];
}
}
return len;
}
};
优化:
class Solution
{
public:
int minSubArrayLen(int target, vector<int>& nums)
{
int count = INT_MAX;
int sum = 0;
for(int right = 0, left = 0; right < nums.size(); right++)
{
sum += nums[right];
while(target <= sum)
{
if(count > right - left + 1)
{
count = right - left + 1;
}
sum -= nums[left++];
}
}
return count == INT_MAX ? 0 : count;
}
};
2. 无重复字符的最长字符串
- 题目信息:
- 题目链接:
无重复字符的最长字符串- 思路:(哈希表)+ 滑动窗口
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
//简单代替哈希表
int hash[128] = {0};
int left = 0;
int right = 0;
int s_len = 0;
int len = 0;
int size = s.length();
while(right < size)
{
hash[s[right]]++;
len++;
while(hash[s[right]] >= 2)
{
hash[s[left]]--;
left++;
len--;
}
if(s_len < len)
{
s_len = len;
}
right++;
}
return s_len;
}
};
3. 最大连续1的个数
- 题目信息:
- 题目链接:
最大连续1的个数- 思路:zero计数器
循环中的逻辑顺序流程:判断 > 调整 > 记录 > 下一轮
class Solution
{
public:
int longestOnes(vector<int>& nums, int k)
{
int zero = 0;
int ret = 0;
//逻辑执行顺序
//判断,++,计算
//哪里是窗口
for(int right = 0, left = 0; right < nums.size(); right++)
{
if (nums[right] == 0)
{
zero++;
}
while (zero > k)
{
if (nums[left] == 0)
{
zero--;
}
left++;
}
ret = max(right - left + 1, ret);
}
return ret;
}
};
4. 将x减到0的最小操作数
- 题目信息:
- 题目链接:
将x减到0的最小操作数- 思路:
<1> (正难则反)此题正向思路非常复杂,我们这里不求从两侧如何得到x,我们反过来求一段连续区间怎样等于sum(数组所有元素之和)- x。因为要得到从两侧出发的最小路径,相反的我们要求的就是最大的连续区间。
<2> (暴力枚举法的优化)将所有等于sum - x的连续区间求出,同向双指针。
图示:
class Solution
{
public:
int minOperations(vector<int>& nums, int x)
{
//正难则反
//暴力枚举法的优化
int sum = 0;
for(auto e : nums)
{
sum += e;
}
//负数
int target = sum - x;
int reverse_sum = 0;
int count = 0;
if(target < 0)
{
return -1;
}
if(target == 0)
{
return nums.size();
}
for(int left = 0, right = 0; right < nums.size(); right++)
{
reverse_sum += nums[right];
while(reverse_sum > target)
{
reverse_sum -= nums[left];
left++;
}
//区间,左闭右闭
//找最大
if(reverse_sum == target && right - left + 1 > count)
{
cout << right << ' ' << left << endl;
count = right - left + 1;
}
}
return count == 0 ? -1 : nums.size() - count;
}
};
注:循环逻辑顺序链条,滑动窗口区间(开闭)
5. 水果成篮
- 题目信息:
- 题目链接:
水果成篮- 思路:滑动窗口 + hash表(暴力求解的优化)
class Solution
{
public:
int totalFruit(vector<int>& fruits)
{
//遍历双指针
//哈希表,种类
int size = fruits.size();
int* hash = (int*)malloc(size * sizeof(int));
memset(hash, 0, size * sizeof(int));
int kind = 0;
int len = 0;
for(int right = 0, left = 0; right < fruits.size(); right++)
{
//进窗口
if(hash[fruits[right]] == 0)
{
kind++;
}
hash[fruits[right]]++;
//出窗口
while(kind > 2)
{
hash[fruits[left]]--;
if(hash[fruits[left]] == 0)
{
kind--;
}
left++;
}
//判断
if(right - left + 1 > len)
{
len = right - left + 1;
}
}
return len;
}
};
6. 找到字符串中所有字母异位词
- 题目信息:
- 题目链接:
找到字符串中所有字母异位的字符- 思路:
尝试:
class Solution
{
public:
vector<int> findAnagrams(string s, string p)
{
vector<int> count;
//暴力求解,列出所有长度为字符串p的字串
//进行匹配,哈希表大小26,下标
//记录,判断
int hash[26] = {0};
//所指定下标处都为1
//遍历的指针
for(int right = 0, left = 0; right < s.length(); right++)
{
//进窗口
hash[s[right] - 'a']++;
//出窗口
while(right - left + 1 > p.length())
{
hash[s[left] - 'a']--;
left++;
}
//判断
if(right - left + 1 == p.length())
{
int flag = 1;
for(int i = 0; i < p.length(); i++)
{
//相同的字符
hash[p[i] - 'a']--;
}
for(int i = 0; i < p.length(); i++)
{
if(hash[p[i] - 'a'] != 0)
{
flag = 0;
break;
}
}
for(int i = 0; i < p.length(); i++)
{
hash[p[i] - 'a']++;
}
if(flag)
{
count.push_back(left);
}
}
}
return count;
}
};
优化1:(判断方式改为两个哈希表)
class Solution
{
public:
vector<int> findAnagrams(string s, string p)
{
vector<int> count;
int hash1[26] = {0};
int hash2[26] = {0};
for(int i = 0; i < p.length(); i++)
{
hash2[p[i] - 'a']++;
}
for(int right = 0, left = 0; right < s.length(); right++)
{
//进窗口
hash1[s[right] - 'a']++;
//出窗口
while(right - left + 1 > p.length())
{
hash1[s[left] - 'a']--;
left++;
}
//判断
//两个哈希表
if(right - left + 1 == p.length())
{
int flag = 1;
for(int i = 0; i < 26; i++)
{
if(hash1[i] !=hash2[i])
{
flag = 0;
break;
}
}
if(flag)
{
count.push_back(left);
}
}
}
return count;
}
};
优化2:(计数:判断优化)
- 创建变量count1记录滑动窗口中的有效字符,以等长与有效字符数量相等的判断标准代替,遍历式的判断
- 当遍历字符确为有效字符,且不为多余的字符时,count1++或count1–
class Solution
{
public:
vector<int> findAnagrams(string s, string p)
{
vector<int> ret;
int hash1[26] = {0};
int hash2[26] = {0};
int count1 = 0;
int count2 = 0;
//标记并计算有多少个有效字符
for(int i = 0; i < p.length(); i++)
{
hash2[p[i] - 'a']++;
count2++;
}
for(int right = 0, left = 0; right < s.length(); right++)
{
//有效字符,且没有多余
if(hash2[s[right] - 'a'] && hash1[s[right] - 'a'] < hash2[s[right] - 'a'])
{
count1++;
}
//进窗口
hash1[s[right] - 'a']++;
//出窗口
while(right - left + 1 > p.length())
{
//是有效字符,且出窗口的有效字符不是多余
if(hash2[s[left] - 'a'] && hash1[s[left] - 'a'] <= hash2[s[left] - 'a'])
{
count1--;
}
hash1[s[left] - 'a']--;
left++;
}
//判断
//两个哈希表
if(right - left + 1 == p.length() && count1 == count2)
{
ret.push_back(left);
}
}
return ret;
}
};
7. 串联所有单词的子串
题目信息:
题目连接:
串联所有单词的子串思路:哈希表 + 滑动窗口(注:遍历方式 + 边界问题)
语法优化:
<1> 获得子串使用string的substr接口,hash.count(字符)
<2> 判断hash表中是否有这个字符,对照表中没有,那么记录表中不创建
class Solution
{
public:
vector<int> findSubstring(string s, vector<string>& words)
{
vector<int> ret;
//right + words[0].length()边界问题
for (int i = 0; i < words[0].length(); i++)
{
map<string, int> m1;
map<string, int> m2;
for (auto e : words)
{
m1[e]++;
//cout << m1[e] << endl;
m2[e] = 0;
}
int count1 = 0;
int count2 = words.size();
//后续再遍历,少遍历一步
for (int right = i, left = i; right + words[0].length() - 1 < s.length(); right += words[0].length())
{
//进窗口
string part1(s.begin() + right, s.begin() + right + words[0].length());
//cout << part << endl;
//有效字符
if (m1[part1] > 0 && m2[part1] < m1[part1])
{
count1++;
}
m2[part1]++;
//cout << m2[part1] << endl;
//出窗口
int size = words.size() * words[0].length();
//cout << size << endl;
while (right + words[0].length() - left > size)
{
string part2(s.begin() + left, s.begin() + left + words[0].length());
//没有发生重复的有效字符
//只要是有效字符就--
if (m1[part2] > 0 && m2[part2] <= m1[part2])
{
count1--;
}
m2[part2]--;
left += words[0].length();
}
//判断
if (right + words[0].length() - left == words.size() * words[0].length() && count1 == count2)
{
count.push_back(left);
}
}
}
return ret;
}
};
8. 最小覆盖子串
- 题目信息:
- 题目链接:
最小覆盖子串- 思路:滑动窗口 + 哈希表
- 优化:数组代替map效率更高,记录字符种类
class Solution {
public:
string minWindow(string s, string t)
{
//可以用数组代替map时,数组更优
int hash1[128] = {0};
int hash2[128] = {0};
//记录有效字符种类
int kind1 = 0;
for(auto e : t)
{
if(hash1[e]++ == 0)
{
kind1++;
}
//hash1[e]++;
}
//用有效字符种类的判断方式优于记录有效字符个数的判断方式
int kind2 = 0;
//记录子串长度
int len = INT_MAX;
//记录子串起始位置
int begin = -1;
for(int right = 0, left = 0; right < s.length(); right++)
{
//先进窗口再判断是否为有效字符
//hash2[s[right]]++;
if(hash1[s[right]] == ++hash2[s[right]])
{
kind2++;
}
//当遍历字符中包含有效字符时,进行判断,记录与出窗口操作
while(kind1 == kind2)
{
//判断
if(right - left + 1 < len)
{
begin = left;
len = right - left + 1;
}
//出窗口
//先判断再出窗口
if(hash1[s[left]] && hash1[s[left]] == hash2[s[left]])
{
kind2--;
}
hash2[s[left]]--;
left++;
}
}
//判断有无子串
if(begin == -1)
{
return "";
}
else
{
//取子串
return s.substr(begin, len);
}
}
};