1.适用范围和题目类型
分组顾名思义就是可以将一堆数字按照题目要求分成一些组,然后按照题目要求进行不同组之间的操作,每个组的判断或者处理逻辑是一样的。
2.核心思想
1.外层循环负责遍历组之前的准备工作(比如记录起始位置),和遍历完之后的统计工作(比如更新长度)
2.内层循环负责遍历组,找出这一组最远在哪里结束
3.解题模板
n = nums.size();
//外层循环
for(int i = 0;i<n;){
int start = i;
//内层循环 找同一组内的元素,直到最远
while(i < n && (按照题目要求写组的判别条件))
{
i++;
}
//下标从start 到i-1 的是一组
//下一组从i开始所以无需i++
//可以根据题目要求写更新答案的逻辑
}
4.例题
4.1 1957. 删除字符使字符串变好 - 力扣(LeetCode)
思路:假设下标为i的字符是x,那么我们需要找到下标i附近的所有的连续的x,它们属于同一组
比如示例一,l单独为一组,三个e为一组,好字符串的要求是不能有三个连续相同的字符,所以对于每一组的处理逻辑是:判断每一组的元素个数是否大于等于3,如果大于等于3就需要删除元素,否则不需要删除,字符串的删除操作我们可以反向变成添加操作,大于等于3就增加两个该组元素,小于3就直接添加该组的全部元素。
代码:
class Solution {
public:
string makeFancyString(string s) {
string ans = "";
int n = s.size();
for(int i = 0;i<n;){
int start = i++;
while(i < n && s[start] == s[i]){
i++;
}
//start到i-1是一组,区间长度为i-start
if(i-start > 2) ans += s.substr(start,2);//加两个改组的元素
else ans += s.substr(start,i-start);//该组元素全部添加
}
return ans;
}
};
4.2 228. 汇总区间 - 力扣(LeetCode)
思路:我们可以将每段连续(即nums[i] - nums[i-1] = 1)的子数组看成一组,然后按照题目要求的格式输出区间即可
代码:
class Solution {
public:
vector<string> summaryRanges(vector<int>& nums) {
vector<string> ans;
int n = nums.size();
for(int i = 0;i<n;){
string tmp = "";
int start = i++;
tmp += to_string(nums[start]);//加入区间起点
//判断后一个数是否是前一个数+1
while(i < n && nums[i] == 1 + nums[i-1]){
i++;
}
//组的长度大于1则需要加 -> 和区间终点
if(i - start > 1) {
tmp.append("->");
tmp += to_string(nums[i-1]);
}
ans.push_back(tmp);
}
return ans;
}
};
4.3 3011. 判断一个数组是否可以变为有序 - 力扣(LeetCode)
思路:该题分组的依据也很明显,我们需要将数组中连续的二进制表示中1的个数相同的数划分为一组。对每组排序,最后看整个数组是否有序。
代码:
class Solution {
public:
int f(int x){
int cnt = 0;
for(int i = 0;i<31;i++){
if(x & (1<<i)) cnt++;
}
return cnt;
}
bool canSortArray(vector<int>& nums) {
int n = nums.size();
//分组循环 外层循环记录开始的位置 内层循环看最远可以到达什么位置(末尾 或者 1的个数不同)
for(int i = 0;i<n;){
int start = i;
int cnt = f(nums[i++]);
while(i<n && f(nums[i]) == cnt){
i++;
}
sort(nums.begin() + start,nums.begin() + i);
}
return ranges:: is_sorted(nums);
}
};
时间复杂度为O(nlongn)
其实这道题还可以优化,其实没有必要对每组排序,对于分好的每个组,我们只需要保证前一个组的最大值都小于等于后一个组中的每个元素,这样最终就可以有序。
代码:
class Solution {
public:
int f(int x){
int cnt = 0;
for(int i = 0;i<31;i++){
if(x & (1<<i)) cnt++;
}
return cnt;
}
bool canSortArray(vector<int>& nums) {
int n = nums.size();
int pre_max = 0;//前一个组的最大值
for(int i = 0;i<n;){
int mx = 0;//记录该组最大值
int cnt = f(nums[i]);
while(i < n && cnt == f(nums[i])){
//如果该组内的元素有小于上一组最大值的,最终就无法有序
if(nums[i] < pre_max) return false;
mx = max(mx,nums[i]);
i++;
}
pre_max = mx;//判断下一个前将该组最大值给pre_max
}
return true;
}
};
这样时间复杂度就成了O(n),注意虽然看起来是两个循环,但是i一直都是自增的(i++),而且i最多只能到n,所以说时间复杂度是O(n)。
5.总结
分组循环不能只掌握模板,需要大量的练习,这样才能得心应手!