2025-08-23:找出长度为 K 的特殊子字符串。用go语言,给定一个字符串 s 和一个整数 k,判断 s 中是否存在一个长度正好为 k 的连续片段,且满足两点:片段内全部由同一个字符组成;若该片段左侧或右侧有相邻字符,则这些相邻字符都不能与片段内的字符相同。若存在这样的片段返回 true,否则返回 false。这里“子字符串”指的是字符串中一段连续且非空的字符序列。
1 <= k <= s.length <= 100。
s 仅由小写英文字母组成。
输入: s = “aaabaaa”, k = 3。
输出: true。
解释:
子字符串 s[4…6] == “aaa” 满足条件:
长度为 3。
所有字符相同。
子串 “aaa” 前的字符是 ‘b’,与 ‘a’ 不同。
子串 “aaa” 后没有字符。
题目来自力扣3456。
分步骤描述过程:
-
问题理解:我们需要判断字符串
s
中是否存在一个长度为k
的连续子串(片段),该子串必须满足:- 子串中的所有字符都相同。
- 如果该子串左边有相邻字符,则该相邻字符不能与子串中的字符相同(即不能扩展)。
- 如果该子串右边有相邻字符,则该相邻字符也不能与子串中的字符相同(即不能扩展)。
-
遍历字符串:我们将遍历字符串
s
,逐个检查每个字符,并尝试找到满足条件的连续片段。 -
计数连续相同字符:
- 初始化一个计数器
cnt
为 0,用于记录当前连续相同字符的长度。 - 从字符串的第一个字符开始,依次检查每个字符。
- 对于每个位置
i
,首先增加计数器cnt
(表示当前连续相同字符的长度增加1)。 - 然后检查当前字符
s[i]
是否与下一个字符s[i+1]
不同(或者当前是最后一个字符),这表示当前连续相同字符的片段结束。
- 初始化一个计数器
-
检查片段是否满足条件:
- 当遇到片段结束(即当前字符与下一个字符不同,或者到达字符串末尾)时,检查当前计数
cnt
是否等于k
。- 如果
cnt == k
,则说明我们找到了一个长度为k
的连续相同字符的片段。
- 如果
- 但还需要检查该片段的左右相邻字符是否与片段内的字符不同(以确保不能扩展):
- 对于片段起始位置(假设片段从
start
开始,长度为k
):- 左边相邻字符是
s[start-1]
(如果存在),必须与片段字符不同。 - 右边相邻字符是
s[start+k]
(如果存在),必须与片段字符不同。
- 左边相邻字符是
- 对于片段起始位置(假设片段从
- 然而,在给定的代码中,并没有显式检查左右相邻字符?实际上,代码中的逻辑是:
- 它只统计了连续相同字符的长度,并在片段结束时检查长度是否恰好为
k
。 - 但题目要求左右相邻字符(如果存在)必须与片段字符不同。
- 它只统计了连续相同字符的长度,并在片段结束时检查长度是否恰好为
- 注意:原代码实际上有缺陷?因为它没有检查左右相邻字符。例如,对于
s = "aaabaaa", k=3
,片段s[0..2]="aaa"
的长度为3,但左边没有字符(所以忽略左边),右边相邻字符是'b'
(与'a'
不同),所以满足条件?但实际上片段s[0..2]
的右边相邻字符是s[3]='b'
,确实不同,所以应该满足?然而,片段s[4..6]="aaa"
也是满足的(左边是'b'
,右边没有字符)。 - 但原代码并没有区分不同的片段?它只是统计连续相同字符的长度,并在片段结束时检查长度是否等于
k
。然而,如果连续相同字符的长度大于k
,那么其中可能包含多个长度为k
的子串?但题目要求的是“连续片段”,且要求左右不能扩展(即左右相邻字符不能相同),所以只有那些恰好长度为k
且左右都不能扩展的片段才有效?实际上,如果连续相同字符的长度为L
(L>=k),那么其中长度为k
的子串可能有很多,但只有那些位置合适的(即左右边界恰好是片段边界)才满足?例如,连续5个 ‘a’,k=3:那么中间的子串(比如从第二个字符开始长度为3)的左右相邻字符都是 ‘a’,所以不满足(因为可以扩展)。只有整个片段恰好长度为3(且左右相邻字符不是’a’)才满足?或者如果片段在开头(左边无字符)且右边相邻字符不是’a’?或者片段在结尾(右边无字符)且左边相邻字符不是’a’?)?
- 当遇到片段结束(即当前字符与下一个字符不同,或者到达字符串末尾)时,检查当前计数
-
原代码的缺陷:实际上,原代码没有检查左右相邻字符,因此可能会错误地返回 true。例如,考虑
s = "aaaa", k=3
:- 连续相同字符的长度为4,当片段结束时(即到达末尾),计数为4,不等于3?但实际上,其中包含长度为3的子串(比如前3个字符),但前3个字符的右边相邻字符是 ‘a’(相同),所以不满足条件。但原代码会返回 false(因为整个片段长度为4,不等于3)?但如果我们有
s = "aaab", k=3
:连续相同字符片段(前3个)结束时(因为第3个和第4个不同),计数为3,然后返回 true。但这里前3个字符的右边相邻字符是 ‘b’(不同),左边无字符,所以满足?正确。
然而,如果连续相同字符的长度大于k,但其中恰好有一个长度为k的子串满足左右相邻字符不同?实际上,只有在整个连续相同字符的片段恰好长度为k时(即左右边界恰好是片段的边界)才满足?因为如果连续相同字符的长度为L(L>k),那么其中任何一个长度为k的子串的左右相邻字符( within the same run)都是相同的(因为都在同一个连续片段内),所以都不满足条件(除非该子串位于片段的边界?但即使位于边界,其一边相邻字符仍然是相同字符?)。例如,连续5个 ‘a’(索引0到4),k=3:
- 子串0-2:左边无字符,右边相邻字符是索引3的 ‘a’(相同),所以不满足。
- 子串1-3:左边是索引0的 ‘a’(相同),右边是索引4的 ‘a’(相同),不满足。
- 子串2-4:左边是索引1的 ‘a’(相同),右边无字符(如果字符串长度就是5)?但右边无字符,但左边相邻字符相同,所以不满足。
因此,只有当整个连续相同字符的片段长度恰好为k时,才可能满足条件(因为左右相邻字符(如果存在)必须不同?)。所以原代码的做法(只检查连续相同字符的长度是否恰好为k)实际上是正确的?因为如果长度大于k,其中任何长度为k的子串都不满足(左右相邻字符相同);如果长度小于k,当然不满足。所以只需要检查连续相同字符的片段长度是否恰好等于k?而且,还需要检查该片段的左右相邻字符是否与片段字符不同?但实际上,当连续相同字符的片段长度恰好为k时,它的左右相邻字符(如果存在)自然就是不同字符(因为片段结束的原因就是遇到了不同字符)?例如,在遍历中,我们是在遇到字符变化(或字符串结束)时检查片段长度的。所以片段左右边界外的字符自然就是不同的(左边:如果片段不是从开头,那么左边相邻字符是前一个片段的最后一个字符,必然不同?因为片段之间字符不同;右边:如果片段不是到结尾,那么右边相邻字符是下一个片段的第一个字符,必然不同)。因此,原代码不需要显式检查左右相邻字符?因为遍历逻辑已经保证了:我们统计的连续相同字符片段,其左右相邻字符(如果存在)必然是不同的(因为片段结束就是因为遇到了不同字符)。 - 连续相同字符的长度为4,当片段结束时(即到达末尾),计数为4,不等于3?但实际上,其中包含长度为3的子串(比如前3个字符),但前3个字符的右边相邻字符是 ‘a’(相同),所以不满足条件。但原代码会返回 false(因为整个片段长度为4,不等于3)?但如果我们有
-
因此,原代码的正确性:实际上,原代码是正确的。因为:
- 它统计连续相同字符的长度(比如遇到字符变化或字符串结束)。
- 当片段结束时,检查该片段的长度是否恰好为k。如果是,则返回true。
- 由于片段结束的原因就是遇到了不同字符(或者边界),所以该片段的左右相邻字符(如果存在)必然与片段内字符不同。因此,不需要显式检查。
-
例子验证:以
s = "aaabaaa", k=3
为例:- 第一个片段:连续3个 ‘a’(索引0-2),然后遇到 ‘b’(不同),所以片段结束,长度=3,等于k,返回true?但这里片段索引0-2的右边相邻字符是 ‘b’(不同),左边无字符,满足条件。
- 注意:实际上这里有两个片段满足?第一个片段(前3个’a’)已经满足,所以直接返回true。第二个片段(后3个’a’)同样满足,但代码在第一个片段时就已经返回了。
-
算法流程:
- 初始化计数器
cnt = 0
。 - 遍历字符串
s
的每个索引i
(从0到len(s)-1):cnt++
(当前连续相同字符长度加1)。- 如果当前字符
s[i]
与下一个字符s[i+1]
不同(或者i是最后一个字符),说明当前连续片段结束:- 如果
cnt == k
,则返回true。 - 重置
cnt = 0
(为下一个片段准备)。
- 如果
- 如果遍历结束都没有找到,返回false。
- 初始化计数器
-
边界情况:
- 如果k=1:需要检查单个字符的片段,且左右相邻字符(如果存在)必须不同。
- 例如,s=“aab”,k=1:第一个片段是连续两个’a’,长度2≠1,不返回;第二个片段是单个’b’,长度1==1,且左边是’a’(不同),右边无字符,所以返回true。
- 但注意:单个字符的片段,如果左右相邻字符相同?比如s=“bab”,k=1:中间’a’的左右都是’b’(相同),所以不满足?但原代码会如何?
- 遍历到索引0:字符’b’,cnt=1;然后检查下一个字符是’a’(不同),所以片段结束,长度1==1?但这里左边无字符(索引0是开头),右边是’a’(不同),所以满足?正确。
- 遍历到索引1:字符’a’,cnt=1;然后检查下一个字符是’b’(不同),所以片段结束,长度1==1?但左边是’b’(相同),右边是’b’(相同)?所以不应该满足。但原代码会返回true?因为索引0的片段已经返回了?所以实际上,原代码会提前返回(在索引0处就返回true了),所以不会检查到索引1?但如果我们有s=“aa”,k=1:第一个片段是连续两个’a’,长度2≠1,然后第二个字符?实际上遍历到索引0时,片段还未结束(因为下一个字符相同),所以不会检查。直到索引1:片段结束,长度2≠1,返回false。正确(因为两个’a’都不满足:左边无右边有相同字符,或者左边有相同字符右边无)。
然而,对于s=“bab”,k=1:第一个字符’b’(索引0)的片段结束(因为下一个字符是’a’),长度1,返回true。正确(因为第一个字符’b’满足:左边无,右边是’a’(不同))。
所以原代码正确。
总的时间复杂度和总的额外空间复杂度:
- 时间复杂度:O(n),其中n是字符串s的长度。我们只遍历字符串一次。
- 额外空间复杂度:O(1),只使用了常数级别的额外变量(如cnt和循环变量)。
Go完整代码如下:
package main
import (
"fmt"
)
func hasSpecialSubstring(s string, k int) bool {
cnt := 0
for i := range s {
cnt++
if i == len(s)-1 || s[i] != s[i+1] {
if cnt == k {
return true
}
cnt = 0
}
}
return false
}
func main() {
s := "aaabaaa"
k := 3
result := hasSpecialSubstring(s, k)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def has_special_substring(s: str, k: int) -> bool:
cnt = 0
for i in range(len(s)):
cnt += 1
if i == len(s) - 1 or s[i] != s[i + 1]:
if cnt == k:
return True
cnt = 0
return False
def main():
s = "aaabaaa"
k = 3
result = has_special_substring(s, k)
print(result)
if __name__ == "__main__":
main()