【详细解析】长度为n的开心字符串中字典序第k小的字符串
一、问题描述
1. 核心定义
「开心字符串」需满足两个条件:
-
仅由小写字母
a、b、c组成; -
字符串中相邻字符不相同(下标从1开始)。
2. 问题要求
给定整数 n(字符串长度)和 k(字典序位次),返回长度为 n 的所有开心字符串按字典序排序后的第 k 个字符串;若开心字符串总数少于 k,返回空字符串。
3. 示例分析
| 输入 | 输出 | 说明 |
|---|---|---|
| n=1, k=3 | "c" | 长度为1的开心字符串:["a","b","c"],第3个为"c" |
| n=1, k=4 | "" | 仅3个开心字符串,无第4个 |
| n=3, k=9 | "cab" | 长度为3的开心字符串共12个,按序第9个为"cab" |
二、解题思路
1. 先计算开心字符串总数
长度为 n 的开心字符串总数规律:
-
第1位:有3种选择(a/b/c);
-
第2位及以后:每位不能与前一位相同,故有2种选择;
-
总数 =
3 * (2^(n-1))。
若 k > 总数,直接返回空字符串。
2. 字典序构造第k个字符串
核心逻辑:逐位确定字符 ,每一步计算当前选择该字符后剩余位置能生成的开心字符串数量,判断 k 落在哪个分支,逐步缩小范围。
举例(n=3, k=9):
-
总数 = 322 = 12,9 ≤ 12,有效;
-
第1位候选:a、b、c,每个字符对应后续2位的组合数=2*2=4;
-
a分支:1-4,9不在此范围,k -= 4 → k=5;
-
b分支:5-8,9不在此范围,k -= 4 → k=1;
-
c分支:9-12,9在此范围,确定第1位为c;
-
-
第2位候选:不能为c,即a、b,每个字符对应后续1位的组合数=2;
- a分支:9-10,k=1在此范围,确定第2位为a;
-
第3位候选:不能为a,即b、c,组合数=1;
- b分支:9,确定第3位为b;
-
最终结果:cab(与示例一致)。
三、完整代码实现
Python
class Solution:
def getHappyString(self, n: int, k: int) -> str:
# 步骤1:计算长度为n的开心字符串总数
total = 3 * (2 ** (n - 1))
if k > total:
return ""
# 步骤2:逐位构造结果字符串
res = []
prev_char = None # 记录前一位字符,避免相邻重复
remaining = n # 剩余需要构造的位数
current_k = k # 当前需要找的位次
while remaining > 0:
# 计算当前位选一个有效字符后,剩余位能生成的组合数
comb = 2 ** (remaining - 1)
# 按字典序遍历候选字符(a、b、c),排除前一位字符
for char in ['a', 'b', 'c']:
if char == prev_char:
continue # 跳过与前一位重复的字符
# 判断当前k是否落在该字符对应的分支中
if current_k <= comb:
res.append(char)
prev_char = char
remaining -= 1
break
else:
# 不在当前分支,减去该分支的组合数,继续找下一个字符
current_k -= comb
return ''.join(res)
四、代码核心解析
1. 总数校验
Python
total = 3 * (2 ** (n - 1))
if k > total:
return ""
- 先通过公式计算所有可能的开心字符串数量,若k超出范围,直接返回空字符串,避免无效计算。
2. 逐位构造逻辑
Python
while remaining > 0:
comb = 2 ** (remaining - 1)
for char in ['a', 'b', 'c']:
if char == prev_char:
continue
if current_k <= comb:
res.append(char)
prev_char = char
remaining -= 1
break
else:
current_k -= comb
-
comb:当前字符确定后,剩余remaining-1位能生成的开心字符串数量(每位2种选择); -
按字典序遍历
a/b/c,排除与前一位重复的字符,保证满足开心字符串定义; -
若
current_k落在当前字符的分支内,确定该字符,更新状态(前一位字符、剩余位数); -
若不在,减去该分支的数量,继续检查下一个字符。
五、测试用例验证
测试用例1:n=1, k=3
-
total = 3*1=3,k=3 ≤3;
-
remaining=1,comb=1;
-
遍历a:3>1 → k=2;
-
遍历b:2>1 → k=1;
-
遍历c:1≤1 → 确定c,返回"c"。
测试用例2:n=3, k=9
-
total=3*4=12,k=9 ≤12;
-
第1位:comb=4;
-
a:9>4 → k=5;
-
b:5>4 → k=1;
-
c:1≤4 → 确定c,remaining=2,prev_char=c;
-
-
第2位:comb=2;
- a(≠c):1≤2 → 确定a,remaining=1,prev_char=a;
-
第3位:comb=1;
- b(≠a):1≤1 → 确定b;
-
最终结果:"cab"(与示例一致)。
测试用例3:n=2, k=7
- total=3*2=6,k=7>6 → 返回""。
测试用例4:n=10, k=100
- 执行代码后返回"abacbabacb"(与示例一致)。
六、复杂度分析
时间复杂度
-
外层循环:执行n次(逐位构造);
-
内层循环:最多遍历3个字符(a/b/c);
-
总复杂度:O(n),n≤10,效率极高。
空间复杂度
- 仅使用有限变量(res列表、prev_char等),空间复杂度O(n)(存储结果字符串)。
七、总结
关键点回顾
-
总数预判 :通过公式
3*2^(n-1)快速判断k是否有效,避免无效计算; -
逐位贪心:按字典序遍历候选字符,通过计算分支组合数确定k所在分支,逐步构造结果;
-
去重逻辑:每一步排除与前一位相同的字符,保证满足开心字符串的定义。
该方法无需生成所有开心字符串,而是通过数学计算直接定位第k个字符串,在n≤10的约束下,时间和空间效率均达到最优。