分组循环
https://leetcode.cn/problems/longest-even-odd-subarray-with-threshold/solutions/2528771/jiao-ni-yi-ci-xing-ba-dai-ma-xie-dui-on-zuspx/?envType=daily-question&envId=2023-11-16
分组循环
适用场景: 按照题目要求,数组会被分割成若干组,且每一组的判断/处理逻辑是一样的。
核心思想:
-
外层循环负责遍历组之前的准备工作(记录开始位置),和遍历组之后的统计工作(更新答案最大值)。
-
内层循环负责遍历组,找出这一组在哪结束。
这个写法的好处是,各个逻辑块分工明确,也不需要特判最后一组(易错点)。这个写法是所有写法中最不容易出 bug 的,推荐大家记住。
时间复杂度,注意变量 i
只会增加,不会重置也不会减少,所以时间复杂度 O(n)
0x3f:一般来说,分组循环的模板如下
n = len(nums)
i = 0
while i < n:
start = i
while i < n and ...:
i += 1
# 从 start 到 i-1 是一组
# 下一组从 i 开始,无需 i += 1
作者:灵茶山艾府
链接:https://leetcode.cn/problems/longest-even-odd-subarray-with-threshold/solutions/2528771/jiao-ni-yi-ci-xing-ba-dai-ma-xie-dui-on-zuspx/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
题单:
- 1446. 连续字符
- 1869. 哪种连续子字符串更长
- 1957. 删除字符使字符串变好
- 2038. 如果相邻两个颜色均相同则删除当前颜色
- 1759. 统计同质子字符串的数目
- 2110. 股票平滑下跌阶段的数目
- 1578. 使绳子变成彩色的最短时间
- 1839. 所有元音按顺序排布的最长子字符串
- 228. 汇总区间
- 2765. 最长交替子序列
2760. 最长奇偶子数组【学习模板题】
简单
给你一个下标从 0 开始的整数数组 nums
和一个整数 threshold
。
请你从 nums
的子数组中找出以下标 l
开头、下标 r
结尾 (0 <= l <= r < nums.length)
且满足以下条件的 最长子数组 :
nums[l] % 2 == 0
- 对于范围
[l, r - 1]
内的所有下标i
,nums[i] % 2 != nums[i + 1] % 2
- 对于范围
[l, r]
内的所有下标i
,nums[i] <= threshold
以整数形式返回满足题目要求的最长子数组的长度。
注意:子数组 是数组中的一个连续非空元素序列。
示例 1:
输入:nums = [3,2,5,4], threshold = 5
输出:3
解释:在这个示例中,我们选择从 l = 1 开始、到 r = 3 结束的子数组 => [2,5,4] ,满足上述条件。
因此,答案就是这个子数组的长度 3 。可以证明 3 是满足题目要求的最大长度。
示例 2:
输入:nums = [1,2], threshold = 2
输出:1
解释:
在这个示例中,我们选择从 l = 1 开始、到 r = 1 结束的子数组 => [2] 。
该子数组满足上述全部条件。可以证明 1 是满足题目要求的最大长度。
示例 3:
输入:nums = [2,3,4,5], threshold = 4
输出:3
解释:
在这个示例中,我们选择从 l = 0 开始、到 r = 2 结束的子数组 => [2,3,4] 。
该子数组满足上述全部条件。
因此,答案就是这个子数组的长度 3 。可以证明 3 是满足题目要求的最大长度。
提示:
1 <= nums.length <= 100
1 <= nums[i] <= 100
1 <= threshold <= 100
class Solution {
public int longestAlternatingSubarray(int[] nums, int threshold) {
int n = nums.length;
int ans = 0, i = 0;
while(i < n){
if(nums[i] > threshold || nums[i] % 2 != 0){
i++; // 直接跳过
continue;
}
int start = i; // 记录这一组的开始位置
i++; // 此时 i 表示区间的右端点 开始位置已经满足要求,从下一个位置开始判断
while(i < n && nums[i] <= threshold && nums[i] % 2 != nums[i-1] % 2){
i++;
}
// 从 start 到 i-1 是满足题目要求的子数组
ans = Math.max(ans, i - start);
}
return ans;
}
}
1446. 连续字符
简单
给你一个字符串 s
,字符串的**「能量」**定义为:只包含一种字符的最长非空子字符串的长度。
请你返回字符串 s
的 能量。
示例 1:
输入:s = "leetcode"
输出:2
解释:子字符串 "ee" 长度为 2 ,只包含字符 'e' 。
示例 2:
输入:s = "abbcccddddeeeeedcba"
输出:5
解释:子字符串 "eeeee" 长度为 5 ,只包含字符 'e' 。
提示:
1 <= s.length <= 500
s
只包含小写英文字母。
class Solution {
public int maxPower(String s) {
int res = 0, n = s.length();
int i = 0;
while(i < n){
int start = i;
i++;
while(i < n && s.charAt(start) == s.charAt(i))
i++;
res = Math.max(res, i - start);
}
return res;
}
}
1869. 哪种连续子字符串更长
简单
给你一个二进制字符串 s
。如果字符串中由 1
组成的 最长 连续子字符串 严格长于 由 0
组成的 最长 连续子字符串,返回 true
;否则,返回 false
。
- 例如,
s = "**11**01**000**10"
中,由1
组成的最长连续子字符串的长度是2
,由0
组成的最长连续子字符串的长度是3
。
注意,如果字符串中不存在 0
,此时认为由 0
组成的最长连续子字符串的长度是 0
。字符串中不存在 1
的情况也适用此规则。
示例 1:
输入:s = "1101"
输出:true
解释:
由 1 组成的最长连续子字符串的长度是 2:"1101"
由 0 组成的最长连续子字符串的长度是 1:"1101"
由 1 组成的子字符串更长,故返回 true 。
示例 2:
输入:s = "111000"
输出:false
解释:
由 1 组成的最长连续子字符串的长度是 3:"111000"
由 0 组成的最长连续子字符串的长度是 3:"111000"
由 1 组成的子字符串不比由 0 组成的子字符串长,故返回 false 。
示例 3:
输入:s = "110100010"
输出:false
解释:
由 1 组成的最长连续子字符串的长度是 2:"110100010"
由 0 组成的最长连续子字符串的长度是 3:"110100010"
由 1 组成的子字符串不比由 0 组成的子字符串长,故返回 false 。
提示:
1 <= s.length <= 100
s[i]
不是'0'
就是'1'
class Solution {
public boolean checkZeroOnes(String s) {
int len0 = 0, len1 = 0;
int n = s.length(), i = 0;
while(i < n){
int start = i;
i++;
while(i < n && s.charAt(i) == s.charAt(start))
i++;
if(s.charAt(start) == '0')
len0 = Math.max(len0, i - start);
else
len1 = Math.max(len1, i - start);
}
return len1 > len0;
}
}
1957. 删除字符使字符串变好
简单
一个字符串如果没有 三个连续 相同字符,那么它就是一个 好字符串 。
给你一个字符串 s
,请你从 s
删除 最少 的字符,使它变成一个 好字符串 。
请你返回删除后的字符串。题目数据保证答案总是 唯一的 。
示例 1:
输入:s = "leeetcode"
输出:"leetcode"
解释:
从第一组 'e' 里面删除一个 'e' ,得到 "leetcode" 。
没有连续三个相同字符,所以返回 "leetcode" 。
示例 2:
输入:s = "aaabaaaa"
输出:"aabaa"
解释:
从第一组 'a' 里面删除一个 'a' ,得到 "aabaaaa" 。
从第二组 'a' 里面删除两个 'a' ,得到 "aabaa" 。
没有连续三个相同字符,所以返回 "aabaa" 。
示例 3:
输入:s = "aab"
输出:"aab"
解释:没有连续三个相同字符,所以返回 "aab" 。
提示:
1 <= s.length <= 105
s
只包含小写英文字母。
class Solution {
public String makeFancyString(String s) {
int n = s.length(), i = 0;
StringBuilder sb = new StringBuilder();
while(i < n){
int start = i;
while(i < n && s.charAt(i) == s.charAt(start))
i++;
for(int j = 0; j < Math.min(2, i - start); j++)
sb.append(s.charAt(start));
}
return sb.toString();
}
}
2038. 如果相邻两个颜色均相同则删除当前颜色
中等
总共有 n
个颜色片段排成一列,每个颜色片段要么是 'A'
要么是 'B'
。给你一个长度为 n
的字符串 colors
,其中 colors[i]
表示第 i
个颜色片段的颜色。
Alice 和 Bob 在玩一个游戏,他们 轮流 从这个字符串中删除颜色。Alice 先手 。
- 如果一个颜色片段为
'A'
且 相邻两个颜色 都是颜色'A'
,那么 Alice 可以删除该颜色片段。Alice 不可以 删除任何颜色'B'
片段。 - 如果一个颜色片段为
'B'
且 相邻两个颜色 都是颜色'B'
,那么 Bob 可以删除该颜色片段。Bob 不可以 删除任何颜色'A'
片段。 - Alice 和 Bob 不能 从字符串两端删除颜色片段。
- 如果其中一人无法继续操作,则该玩家 输 掉游戏且另一玩家 获胜 。
假设 Alice 和 Bob 都采用最优策略,如果 Alice 获胜,请返回 true
,否则 Bob 获胜,返回 false
。
示例 1:
输入:colors = "AAABABB"
输出:true
解释:
AAABABB -> AABABB
Alice 先操作。
她删除从左数第二个 'A' ,这也是唯一一个相邻颜色片段都是 'A' 的 'A' 。
现在轮到 Bob 操作。
Bob 无法执行任何操作,因为没有相邻位置都是 'B' 的颜色片段 'B' 。
因此,Alice 获胜,返回 true 。
示例 2:
输入:colors = "AA"
输出:false
解释:
Alice 先操作。
只有 2 个 'A' 且它们都在字符串的两端,所以她无法执行任何操作。
因此,Bob 获胜,返回 false 。
示例 3:
输入:colors = "ABBBBBBBAAA"
输出:false
解释:
ABBBBBBBAAA -> ABBBBBBBAA
Alice 先操作。
她唯一的选择是删除从右数起第二个 'A' 。
ABBBBBBBAA -> ABBBBBBAA
接下来轮到 Bob 操作。
他有许多选择,他可以选择任何一个 'B' 删除。
然后轮到 Alice 操作,她无法删除任何片段。
所以 Bob 获胜,返回 false 。
提示:
1 <= colors.length <= 105
colors
只包含字母'A'
和'B'
class Solution {
public boolean winnerOfGame(String colors) {
int cnta = 0, cntb = 0;
int n = colors.length(), i = 0;
while(i < n){
int start = i;
while(i < n && colors.charAt(i) == colors.charAt(start))
i++;
if(colors.charAt(start) == 'A')
cnta += Math.max(i - start - 2, 0);
else
cntb += Math.max(i - start - 2, 0);
}
return cnta > cntb;
}
}
1759. 统计同质子字符串的数目
中等
给你一个字符串 s
,返回 s
中 同质子字符串 的数目。由于答案可能很大,只需返回对 109 + 7
取余 后的结果。
同质字符串 的定义为:如果一个字符串中的所有字符都相同,那么该字符串就是同质字符串。
子字符串 是字符串中的一个连续字符序列。
示例 1:
输入:s = "abbcccaa"
输出:13
解释:同质子字符串如下所列:
"a" 出现 3 次。
"aa" 出现 1 次。
"b" 出现 2 次。
"bb" 出现 1 次。
"c" 出现 3 次。
"cc" 出现 2 次。
"ccc" 出现 1 次。
3 + 1 + 2 + 1 + 3 + 2 + 1 = 13
示例 2:
输入:s = "xy"
输出:2
解释:同质子字符串是 "x" 和 "y" 。
示例 3:
输入:s = "zzzzz"
输出:15
提示:
1 <= s.length <= 105
s
由小写字符串组成。
class Solution {
/**
n = 3
3 + 2 + 1 = n(1+n)/2
*/
private static final int MOD = (int)1e9+7;
public int countHomogenous(String s) {
int n = s.length(), i = 0;
int res = 0;
while(i < n){
int start = i;
while(i < n && s.charAt(i) == s.charAt(start))
i++;
double len = i - start;
double add = (len * (len + 1) / 2) % MOD;
res = (res + (int)(add % MOD)) % MOD;
}
return res % MOD;
}
}
2110. 股票平滑下跌阶段的数目
中等
给你一个整数数组 prices
,表示一支股票的历史每日股价,其中 prices[i]
是这支股票第 i
天的价格。
一个 平滑下降的阶段 定义为:对于 连续一天或者多天 ,每日股价都比 前一日股价恰好少 1
,这个阶段第一天的股价没有限制。
请你返回 平滑下降阶段 的数目。
示例 1:
输入:prices = [3,2,1,4]
输出:7
解释:总共有 7 个平滑下降阶段:
[3], [2], [1], [4], [3,2], [2,1] 和 [3,2,1]
注意,仅一天按照定义也是平滑下降阶段。
示例 2:
输入:prices = [8,6,7,7]
输出:4
解释:总共有 4 个连续平滑下降阶段:[8], [6], [7] 和 [7]
由于 8 - 6 ≠ 1 ,所以 [8,6] 不是平滑下降阶段。
示例 3:
输入:prices = [1]
输出:1
解释:总共有 1 个平滑下降阶段:[1]
提示:
1 <= prices.length <= 105
1 <= prices[i] <= 105
class Solution {
/**
n = 3
3 + 2 + 1 = n(1+n)/2
*/
public long getDescentPeriods(int[] prices) {
int n = prices.length, i = 0;
long res = 0;
while(i < n){
int start = i;
i++;
while(i < n && prices[i] - prices[i-1] == -1)
i++;
long len = i - start;
res += (len * (len + 1)) / 2;
}
return res;
}
}
1578. 使绳子变成彩色的最短时间
中等
Alice 把 n
个气球排列在一根绳子上。给你一个下标从 0 开始的字符串 colors
,其中 colors[i]
是第 i
个气球的颜色。
Alice 想要把绳子装扮成 彩色 ,且她不希望两个连续的气球涂着相同的颜色,所以她喊来 Bob 帮忙。Bob 可以从绳子上移除一些气球使绳子变成 彩色 。给你一个下标从 0 开始的整数数组 neededTime
,其中 neededTime[i]
是 Bob 从绳子上移除第 i
个气球需要的时间(以秒为单位)。
返回 Bob 使绳子变成 彩色 需要的 最少时间 。
示例 1:
输入:colors = "abaac", neededTime = [1,2,3,4,5]
输出:3
解释:在上图中,'a' 是蓝色,'b' 是红色且 'c' 是绿色。
Bob 可以移除下标 2 的蓝色气球。这将花费 3 秒。
移除后,不存在两个连续的气球涂着相同的颜色。总时间 = 3 。
示例 2:
输入:colors = "abc", neededTime = [1,2,3]
输出:0
解释:绳子已经是彩色的,Bob 不需要从绳子上移除任何气球。
示例 3:
输入:colors = "aabaa", neededTime = [1,2,3,4,1]
输出:2
解释:Bob 会移除下标 0 和下标 4 处的气球。这两个气球各需要 1 秒来移除。
移除后,不存在两个连续的气球涂着相同的颜色。总时间 = 1 + 1 = 2 。
提示:
n == colors.length == neededTime.length
1 <= n <= 105
1 <= neededTime[i] <= 104
colors
仅由小写英文字母组成
class Solution {
public int minCost(String colors, int[] neededTime) {
int n = colors.length(), i = 0;
int res = 0;
while(i < n){
int start = i;
int s = 0, maxcost = 0;
while(i < n && colors.charAt(i) == colors.charAt(start)){
s += neededTime[i];
maxcost = Math.max(maxcost, neededTime[i]);
i++;
}
res += s - maxcost;
}
return res;
}
}s
1839. 所有元音按顺序排布的最长子字符串
中等
当一个字符串满足如下条件时,我们称它是 美丽的 :
- 所有 5 个英文元音字母(
'a'
,'e'
,'i'
,'o'
,'u'
)都必须 至少 出现一次。 - 这些元音字母的顺序都必须按照 字典序 升序排布(也就是说所有的
'a'
都在'e'
前面,所有的'e'
都在'i'
前面,以此类推)
比方说,字符串 "aeiou"
和 "aaaaaaeiiiioou"
都是 美丽的 ,但是 "uaeio"
,"aeoiu"
和 "aaaeeeooo"
不是美丽的 。
给你一个只包含英文元音字母的字符串 word
,请你返回 word
中 最长美丽子字符串的长度 。如果不存在这样的子字符串,请返回 0
。
子字符串 是字符串中一个连续的字符序列。
示例 1:
输入:word = "aeiaaioaaaaeiiiiouuuooaauuaeiu"
输出:13
解释:最长子字符串是 "aaaaeiiiiouuu" ,长度为 13 。
示例 2:
输入:word = "aeeeiiiioooauuuaeiou"
输出:5
解释:最长子字符串是 "aeiou" ,长度为 5 。
示例 3:
输入:word = "a"
输出:0
解释:没有美丽子字符串,所以返回 0 。
提示:
1 <= word.length <= 5 * 105
word
只包含字符'a'
,'e'
,'i'
,'o'
和'u'
。
class Solution {
public int longestBeautifulSubstring(String word) {
int n = word.length(), i = 0;
int pre = 0; // 1a 2e 3i 4o 5u
int startidx = 0;
int res = 0;
while(i < n){
int start = i;
while(i < n && word.charAt(i) == word.charAt(start))
i++;
char c = word.charAt(start);
if(c == 'a'){
startidx = start;
pre = 1;
}else if(c == 'e'){
if(pre == 1)
pre = 2;
else
pre = 0;
}else if(c == 'i'){
if(pre == 2)
pre = 3;
else
pre = 0;
}else if(c == 'o'){
if(pre == 3)
pre = 4;
else
pre = 0;
}else{
if(pre == 4){
pre = 5;
res = Math.max(res, i - startidx);
}else
pre = 0;
}
}
return res;
}
}
228. 汇总区间
简单
给定一个 无重复元素 的 有序 整数数组 nums
。
返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums
的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums
的数字 x
。
列表中的每个区间范围 [a,b]
应该按如下格式输出:
"a->b"
,如果a != b
"a"
,如果a == b
示例 1:
输入:nums = [0,1,2,4,5,7]
输出:["0->2","4->5","7"]
解释:区间范围是:
[0,2] --> "0->2"
[4,5] --> "4->5"
[7,7] --> "7"
示例 2:
输入:nums = [0,2,3,4,6,8,9]
输出:["0","2->4","6","8->9"]
解释:区间范围是:
[0,0] --> "0"
[2,4] --> "2->4"
[6,6] --> "6"
[8,9] --> "8->9"
提示:
0 <= nums.length <= 20
-231 <= nums[i] <= 231 - 1
nums
中的所有值都 互不相同nums
按升序排列
class Solution {
public List<String> summaryRanges(int[] nums) {
int n = nums.length, i = 0;
List<String> res = new ArrayList<>();
while(i < n){
int start = i;
i++;
while(i < n && nums[i] - nums[i-1] == 1)
i++;
if(i == start+1)
res.add(String.valueOf(nums[start]));
else
res.add(nums[start] + "->" + nums[i-1]);
}
return res;
}
}
2765. 最长交替子序列
简单
给你一个下标从 0 开始的整数数组 nums
。如果 nums
中长度为 m
的子数组 s
满足以下条件,我们称它是一个 交替子序列 :
m
大于1
。s1 = s0 + 1
。- 下标从 0 开始的子数组
s
与数组[s0, s1, s0, s1,...,s(m-1) % 2]
一样。也就是说,s1 - s0 = 1
,s2 - s1 = -1
,s3 - s2 = 1
,s4 - s3 = -1
,以此类推,直到s[m - 1] - s[m - 2] = (-1)m
。
请你返回 nums
中所有 交替 子数组中,最长的长度,如果不存在交替子数组,请你返回 -1
。
子数组是一个数组中一段连续 非空 的元素序列。
示例 1:
输入:nums = [2,3,4,3,4]
输出:4
解释:交替子数组有 [3,4] ,[3,4,3] 和 [3,4,3,4] 。最长的子数组为 [3,4,3,4] ,长度为4 。
示例 2:
输入:nums = [4,5,6]
输出:2
解释:[4,5] 和 [5,6] 是仅有的两个交替子数组。它们长度都为 2 。
提示:
2 <= nums.length <= 100
1 <= nums[i] <= 104
class Solution {
/**
注意到:[4,5,6] 有 [4, 5] 和 [5, 6]
所以循环结束 i 要 -1
*/
public int alternatingSubarray(int[] nums) {
int n = nums.length, i = 0;
int res = -1;
while(i < n-1){
if(nums[i+1] - nums[i] != 1){
i++;
continue;
}
int start = i, flag = 1;
i++;
while(i < n && nums[i] - nums[i-1] == 1 * flag){
i++;
flag = -flag;
}
res = Math.max(res, i - start);
i--;
}
return res;
}
}