LC 784. 字母大小写全排列
题目描述
这是 LeetCode 上的 784. 字母大小写全排列 ,难度为 中等。
给定一个字符串 s
,通过将字符串 s
中的每个字母转变大小写,我们可以获得一个新的字符串。
返回 所有可能得到的字符串集合 。以 任意顺序 返回输出。
示例 1:1
2
3输入:s = "a1b2"
输出:["a1b2", "a1B2", "A1b2", "A1B2"]
示例 2:1
2
3输入: s = "3z4"
输出: ["3z4","3Z4"]
提示:
- $1 <= s.length <= 12$
s
由小写英文字母、大写英文字母和数字组成
DFS
数据范围为 $12$,同时要我们求所有具体方案,容易想到使用 DFS
进行「爆搜」。
我们可以从前往后考虑每个 $s[i]$,根据 $s[i]$ 是否为字母进行分情况讨论:
- 若 $s[i]$ 不是字母,直接保留
- 若 $s[i]$ 是字母,则有「保留原字母」或「进行大小写转换」两种决策
设计 DFS
函数为 void dfs(int idx, int n, String cur)
:其中 $n$ 固定为具体方案的长度(即原字符串长度),而 idx
和 cur
分别为当前处理到哪一个 $s[idx]$,而 cur
则是当前具体方案。
根据上述分析可知,当 $s[idx]$ 不为字母,将其直接追加到 cur
上,并决策下一个位置 $idx + 1$;而当 $s[idx]$ 为字母时,我们可以选择将 $s[idx]$ 追加到 cur
上(保留原字母)或是将 $s[idx]$ 进行翻转后再追加到 cur
上(进行大小写转换)。
最后当我们满足 idx = n
时,说明已经对原字符串的每一位置决策完成,将当前具体方案 cur
加入答案。
一些细节:我们可以通过与
32
异或来进行大小写转换
Java 代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Solution {
char[] cs;
List<String> ans = new ArrayList<>();
public List<String> letterCasePermutation(String s) {
cs = s.toCharArray();
dfs(0, s.length(), new char[s.length()]);
return ans;
}
void dfs(int idx, int n, char[] cur) {
if (idx == n) {
ans.add(String.valueOf(cur));
return ;
}
cur[idx] = cs[idx];
dfs(idx + 1, n, cur);
if (Character.isLetter(cs[idx])) {
cur[idx] = (char) (cs[idx] ^ 32);
dfs(idx + 1, n, cur);
}
}
}
C++ 代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class Solution {
public:
vector<string> ans;
string s;
void dfs(int idx, int n, string cur) {
if (idx == n) {
ans.push_back(cur);
return;
}
cur.push_back(s[idx]);
dfs(idx + 1, n, cur);
if (isalpha(s[idx])) {
cur[idx] = s[idx] ^ 32;
dfs(idx + 1, n, cur);
}
}
vector<string> letterCasePermutation(string s) {
this->s = s;
dfs(0, s.length(), "");
return ans;
}
};
Python 代码:1
2
3
4
5
6
7
8
9
10
11
12class Solution:
def letterCasePermutation(self, s: str) -> List[str]:
ans = []
def dfs(idx, n, cur):
if idx == n:
ans.append(cur)
return
dfs(idx + 1, n, cur + s[idx])
if 'a' <= s[idx] <= 'z' or 'A' <= s[idx] <= 'Z':
dfs(idx + 1, n, cur + s[idx].swapcase())
dfs(0, len(s), '')
return ans
TypeScript 代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function letterCasePermutation(s: string): string[] {
const ans = new Array<string>()
function dfs(idx: number, n: number, cur: string): void {
if (idx == n) {
ans.push(cur)
return
}
dfs(idx + 1, n, cur + s[idx])
if ((s[idx] >= 'a' && s[idx] <= 'z') || (s[idx] >= 'A' && s[idx] <= 'Z')) {
dfs(idx + 1, n, cur + String.fromCharCode(s.charCodeAt(idx) ^ 32))
}
}
dfs(0, s.length, "")
return ans
}
- 时间复杂度:最坏情况下原串
s
的每一位均为字母(均有保留和转换两种决策),此时具体方案数量共 $2^n$ 种;同时每一种具体方案我们都需要用与原串长度相同的复杂度来进行构造。复杂度为 $O(n \times 2^n)$ - 空间复杂度:$O(n \times 2^n)$
二进制枚举
根据解法一可知,具体方案的个数与字符串 s1
存在的字母个数相关,当 s1
存在 m
个字母时,由于每个字母存在两种决策,总的方案数个数为 $2^m$ 个。
因此可以使用「二进制枚举」的方式来做:使用变量 s
代表字符串 s1
的字母翻转状态,s
的取值范围为 [0, 1 << m)
。若 s
的第 $j$ 位为 0
代表在 s1
中第 $j$ 个字母不进行翻转,而当为 1
时则代表翻转。
每一个状态 s
对应了一个具体方案,通过枚举所有翻转状态 s
,可构造出所有具体方案。
Java 代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Solution {
public List<String> letterCasePermutation(String str) {
List<String> ans = new ArrayList<String>();
int n = str.length(), m = 0;
for (int i = 0; i < n; i++) m += Character.isLetter(str.charAt(i)) ? 1 : 0;
for (int s = 0; s < (1 << m); s++) {
char[] cs = str.toCharArray();
for (int i = 0, j = 0; i < n; i++) {
if (!Character.isLetter(cs[i])) continue;
cs[i] = ((s >> j) & 1) == 1 ? (char) (cs[i] ^ 32) : cs[i];
j++;
}
ans.add(String.valueOf(cs));
}
return ans;
}
}
C++ 代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Solution {
public:
vector<string> letterCasePermutation(string str) {
vector<string> ans;
int n = str.length(), m = 0;
for (int i = 0; i < n; i++) m += isalpha(str[i]) ? 1 : 0;
for (int s = 0; s < (1 << m); s++) {
string cs = str;
for (int i = 0, j = 0; i < n; i++) {
if (!isalpha(cs[i])) continue;
cs[i] = ((s >> j) & 1) ? (cs[i] ^ 32) : cs[i];
j++;
}
ans.push_back(cs);
}
return ans;
}
};
Python 代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Solution:
def letterCasePermutation(self, s1: str) -> List[str]:
def isLetter(ch):
return 'a' <= ch <= 'z' or 'A' <= ch <= 'Z'
ans = []
n, m = len(s1), len([ch for ch in s1 if isLetter(ch)])
for s in range(1 << m):
cur = ''
j = 0
for i in range(n):
if not isLetter(s1[i]) or not ((s >> j) & 1):
cur += s1[i]
else:
cur += s1[i].swapcase()
if isLetter(s1[i]):
j += 1
ans.append(cur)
return ans
TypeScript 代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function letterCasePermutation(str: string): string[] {
function isLetter(ch: string): boolean {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
}
const ans = new Array<string>()
let n = str.length, m = 0
for (let i = 0; i < n; i++) m += isLetter(str[i]) ? 1 : 0
for (let s = 0; s < (1 << m); s++) {
let cur = ''
for (let i = 0, j = 0; i < n; i++) {
if (!isLetter(str[i]) || ((s >> j) & 1) == 0) cur += str[i]
else cur += String.fromCharCode(str.charCodeAt(i) ^ 32)
if (isLetter(str[i])) j++
}
ans.push(cur)
}
return ans
}
- 时间复杂度:最坏情况下原串
s
的每一位均为字母(均有保留和转换两种决策),此时具体方案数量共 $2^n$ 种;同时每一种具体方案我们都需要用与原串长度相同的复杂度来进行构造。复杂度为 $O(n \times 2^n)$ - 空间复杂度:$O(n \times 2^n)$
最后
这是我们「刷穿 LeetCode」系列文章的第 No.784
篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!