1. 题目解析:理解问题本质
LeetCode 984题"String Without AAA or BBB"是一个字符串构造问题,要求我们生成一个不包含连续三个相同字符的字符串。具体来说,给定两个整数a和b,分别代表字母'A'和'B'的数量,我们需要构造一个字符串,使得其中不出现"AAA"或"BBB"这样的子串,同时用完所有的'A'和'B'。
这个问题的核心在于如何在保证不违反规则的前提下,合理地安排'A'和'B'的排列顺序。乍看简单,但实际编码时会遇到不少边界条件和特殊情况需要考虑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解题思路分析
2.1 贪心算法选择
对于这类构造型问题,贪心算法通常是首选。我们可以每次选择剩余数量较多的字母添加到字符串中,但需要确保不会连续添加三个相同的字母。
具体策略如下:
- 比较当前剩余的a和b数量
- 如果a > b,优先添加'A',但要确保不会连续添加三个'A'
- 反之亦然
- 当a和b数量相等时,可以交替添加
这种策略之所以有效,是因为它总是尝试平衡两种字母的数量差,防止一种字母过多导致必须连续添加。
2.2 边界条件考虑
在实际编码中,我们需要特别注意以下几种边界情况:
- a或b为0的情况
- |a - b| > 1的情况(即两种字母数量差过大)
- 当一种字母数量远多于另一种时如何安排
例如,当a = 4,b = 1时,唯一合法的排列是"AABAA",这需要特殊处理。
3. C语言实现详解
3.1 数据结构设计
我们需要一个字符数组来存储结果字符串。由于题目保证有解,数组大小可以设为a + b + 1(+1用于字符串结束符'\0')。
c复制char* strWithout3a3b(int a, int b) {
char* res = (char*)malloc(sizeof(char) * (a + b + 1));
int index = 0;
// 实现逻辑将在这里填充
res[index] = '\0';
return res;
}
3.2 核心算法实现
下面是完整的C语言实现,包含详细注释:
c复制char* strWithout3a3b(int a, int b) {
char* res = (char*)malloc(sizeof(char) * (a + b + 1));
int index = 0;
while (a > 0 || b > 0) {
bool writeA = false;
// 决定当前是写A还是B
if (a > b) {
// 确保不会连续写三个A
if (index >= 2 && res[index-1] == 'A' && res[index-2] == 'A') {
writeA = false;
} else {
writeA = true;
}
} else {
// 确保不会连续写三个B
if (index >= 2 && res[index-1] == 'B' && res[index-2] == 'B') {
writeA = true;
} else {
writeA = false;
}
}
if (writeA) {
res[index++] = 'A';
a--;
} else {
res[index++] = 'B';
b--;
}
}
res[index] = '\0';
return res;
}
3.3 复杂度分析
- 时间复杂度:O(a + b),因为我们只需要遍历一次所有字符
- 空间复杂度:O(a + b),用于存储结果字符串
4. 测试用例与验证
4.1 常规测试用例
c复制void test() {
printf("%s\n", strWithout3a3b(1, 2)); // 预期输出:"BAB"或"BBA"等合法组合
printf("%s\n", strWithout3a3b(4, 1)); // 预期输出:"AABAA"
printf("%s\n", strWithout3a3b(2, 5)); // 预期输出:"BBABBAB"
}
4.2 边界测试用例
c复制void edgeTest() {
printf("%s\n", strWithout3a3b(0, 2)); // 只有B的情况
printf("%s\n", strWithout3a3b(3, 0)); // 只有A的情况
printf("%s\n", strWithout3a3b(1, 1)); // 数量相等
}
4.3 大规模测试
对于大规模输入(如a=100, b=100),我们的算法也应该高效运行。可以通过循环验证生成的字符串确实不包含"AAA"或"BBB"。
5. 常见错误与调试技巧
5.1 常见错误
- 数组越界:忘记为字符串结束符'\0'分配空间
- 逻辑错误:没有正确检查前两个字符导致连续三个相同字符
- 边界处理不当:当a或b为0时没有正确处理
5.2 调试技巧
- 在每次添加字符后打印当前字符串,观察构建过程
- 对于特殊输入(如a=4,b=1),单步调试查看决策过程
- 使用assert验证结果字符串不包含"AAA"或"BBB"
c复制#include <assert.h>
#include <string.h>
void validate(char* s) {
assert(strstr(s, "AAA") == NULL);
assert(strstr(s, "BBB") == NULL);
}
6. 算法优化与变种
6.1 更简洁的实现
我们可以简化条件判断,下面是优化后的版本:
c复制char* strWithout3a3b(int a, int b) {
char* res = (char*)malloc(a + b + 1);
int index = 0;
while (a + b > 0) {
if (a > b) {
if (index < 2 || res[index-1] != 'A' || res[index-2] != 'A') {
res[index++] = 'A';
a--;
} else {
res[index++] = 'B';
b--;
}
} else {
if (index < 2 || res[index-1] != 'B' || res[index-2] != 'B') {
res[index++] = 'B';
b--;
} else {
res[index++] = 'A';
a--;
}
}
}
res[index] = '\0';
return res;
}
6.2 问题变种思考
- 如果允许最多连续k个相同字符,如何修改算法?
- 如果有超过两种字符(如A、B、C),如何解决?
- 如果要求字典序最小或最大的解,如何调整?
这些变种可以帮助我们更深入理解贪心算法在字符串构造问题中的应用。
7. 实际应用场景
虽然这个问题看起来是纯理论性的,但它所体现的算法思想在实际中有广泛应用:
- 资源调度:在有限资源下安排任务,避免某些任务长时间占用资源
- 数据编码:设计编码方案避免特定模式出现
- 游戏设计:随机生成关卡时避免某些元素过于集中
- 通信协议:设计帧结构避免特定比特模式
理解这类问题的解法有助于我们在面对实际工程问题时快速找到解决方案。
