LeetCode 784. 字母大小写全排列
题目描述

题意分析
字符串只含英文字母和数字,每个字母可以独立选择大写或小写,数字保持不变。字符位置和顺序不变,要求列出所有大小写选择形成的字符串,结果顺序不限。
解法:按字符分支的 DFS 回溯
核心思路
[!blue]
定义
dfs(idx)表示已经确定前idx个字符,接下来处理下标idx;path保存这段已确定的前缀,长度也恰好为idx。当前字符是字母时,分别选择小写和大写;是数字时,只保留原字符。每次选择后递归处理下一位置。递归返回后删除刚追加的字符,使
path恢复到进入本层之前的状态。这样同一层的另一个选择会从相同前缀出发,不会混入上一分支留下的内容。当
idx到达字符串长度时,每个位置都已选择完毕,把path转成独立字符串加入结果。每种合法结果都对应一条完整选择路径,不会遗漏;不同路径至少在一个字母位置选择了不同大小写,得到的字符串也不同,因此不需要去重。
解题步骤
- 初始化空路径和结果列表,从下标 0 开始递归。
- 到达串尾时复制完整路径并返回。
- 遇到字母,先追加它的小写形式、递归、撤销,再对大写形式做同样处理。
- 遇到数字,只追加原字符、递归、撤销,不产生第二条分支。
Java 调用大小写转换方法;Go 在确认是英文字母后,通过设置或清除 ASCII 的
0x20位转换大小写。无论输入原来是哪种大小写,都生成恰好两种选择;全为数字时只有一条路径,结果就是原字符串。
代码实现
class Solution {
public List<String> letterCasePermutation(String s) {
List<String> res = new ArrayList<>();
backtrack(s.toCharArray(), 0, new StringBuilder(), res);
return res;
}
private void backtrack(char[] chars, int idx, StringBuilder path, List<String> res) {
if (idx == chars.length) {
// 完整路径转成独立字符串,后续回溯不影响已收集结果
res.add(path.toString());
return;
}
char cur = chars[idx];
if (cur >= 'a' && cur <= 'z' || cur >= 'A' && cur <= 'Z') {
path.append((char) Character.toLowerCase(cur));
backtrack(chars, idx + 1, path, res);
// 撤销当前选择,恢复到进入本层之前的路径
path.deleteCharAt(path.length() - 1);
path.append((char) Character.toUpperCase(cur));
backtrack(chars, idx + 1, path, res);
// 撤销当前选择,恢复到进入本层之前的路径
path.deleteCharAt(path.length() - 1);
} else {
path.append(cur);
backtrack(chars, idx + 1, path, res);
// 撤销当前选择,恢复到进入本层之前的路径
path.deleteCharAt(path.length() - 1);
}
}
}
func letterCasePermutation(s string) []string {
res := make([]string, 0)
path := make([]byte, 0, len(s))
var dfs func(int)
dfs = func(idx int) {
if idx == len(s) {
// 完整路径转成独立字符串,后续回溯不影响已收集结果
res = append(res, string(path))
return
}
c := s[idx]
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
path = append(path, c|32)
dfs(idx + 1)
// 撤销当前选择,恢复到进入本层之前的路径
path = path[:len(path)-1]
path = append(path, c&^byte(32))
dfs(idx + 1)
// 撤销当前选择,恢复到进入本层之前的路径
path = path[:len(path)-1]
} else {
path = append(path, c)
dfs(idx + 1)
// 撤销当前选择,恢复到进入本层之前的路径
path = path[:len(path)-1]
}
}
dfs(0)
return res
}
复杂度分析
- 时间复杂度:$O(n2^k)$,$n$ 为字符串长度,$k$ 为字母数量。共有 $2^k$ 个结果,每个结果复制 $n$ 个字符。
- 空间复杂度:不计输出为 $O(n)$,来自递归栈和当前路径;结果本身占 $O(n2^k)$。
关键点总结
[!green]
- 数字不应产生大小写分支。
- 强制转换不依赖输入原来的大小写。
- Go 的大小写位操作只用于本题英文字母,数字分支不修改。
易错点总结
[!yellow]
- 对数字也清除大小写位,会生成控制字符。
- 分支返回不撤销,会使后续结果长度异常。
- 数字分支不追加,会丢失数字。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 78. 子集 | 中等 | 每个字母选择原大小写或翻转,对应逐位置二选一;数字只有保留一种选择。 |
| 17. 电话号码的字母组合 | 中等 | 同样每个位置从独立字符选项中取一个,完整结果是各组选项的笛卡尔积。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!