Given a string s
consisting of letters and digits, you are asked to return a list of all possible strings we can obtain by transforming every letter individually to be either lowercase or uppercase. Digits must remain unchanged. The order of returned permutations does not matter.
s
can be changed independently to lowercase or uppercase.s
are not altered.
Example:
Input: s = "a1b2"
Output: ["a1b2", "a1B2", "A1b2", "A1B2"]
At first glance, the problem seems to require generating all combinations of upper and lower case for each letter in the string, while leaving digits unchanged. A brute-force approach would try every possible combination by toggling each letter between its two cases, leading to 2number of letters total permutations.
The main challenge is to systematically generate all these combinations without missing any or introducing duplicates, and to do so efficiently. Rather than generating all possible strings and filtering out duplicates, we can use recursion or iteration to build each permutation step by step, making a choice at each character.
Optimization comes from recognizing that only letters have two choices (lowercase or uppercase), while digits have a single choice (remain as is). Therefore, our approach should efficiently branch only when necessary.
To solve this problem, we can use either backtracking (recursion) or iterative methods (like BFS with a queue). The core idea is to process the string character by character, and for each letter, branch into two possibilities: one with the letter in lowercase, and one in uppercase. For digits, we simply continue without branching.
This approach ensures that all permutations are generated, and no duplicates are created since each path represents a unique combination of letter cases.
Let's walk through the example s = "a1b2"
:
""
'a'
(letter)
"a"
"A"
'1'
(digit)
"a1"
, "A1"
'b'
(letter)
"a1b"
, "a1B"
"A1b"
, "A1B"
'2'
(digit)
"a1b2"
, "a1B2"
, "A1b2"
, "A1B2"
Final output: ["a1b2", "a1B2", "A1b2", "A1B2"]
L
be the number of letters in the string. Each letter has 2 possibilities (lowercase or uppercase), so there are 2^L
permutations.
O(2^L * N)
, where N
is the length of the string. For each of the 2^L
permutations, we build a string of length N
.
O(2^L * N)
for storing all permutations, plus recursion stack space of O(N)
if using recursion.
The Letter Case Permutation problem asks us to generate all possible strings by toggling the case of each letter in the input, while leaving digits unchanged. By using backtracking or an iterative approach, we efficiently explore all valid permutations, branching only when letters are encountered. The total number of permutations grows exponentially with the number of letters, but the algorithm is both simple and elegant, leveraging the natural structure of the problem to avoid unnecessary work.
class Solution:
def letterCasePermutation(self, s: str):
res = []
def backtrack(path, idx):
if idx == len(s):
res.append(path)
return
if s[idx].isdigit():
backtrack(path + s[idx], idx + 1)
else:
backtrack(path + s[idx].lower(), idx + 1)
backtrack(path + s[idx].upper(), idx + 1)
backtrack("", 0)
return res
class Solution {
public:
vector<string> letterCasePermutation(string s) {
vector<string> res;
backtrack(s, 0, res);
return res;
}
void backtrack(string s, int idx, vector<string>& res) {
if (idx == s.size()) {
res.push_back(s);
return;
}
if (isdigit(s[idx])) {
backtrack(s, idx + 1, res);
} else {
s[idx] = tolower(s[idx]);
backtrack(s, idx + 1, res);
s[idx] = toupper(s[idx]);
backtrack(s, idx + 1, res);
}
}
};
class Solution {
public List<String> letterCasePermutation(String s) {
List<String> res = new ArrayList<>();
backtrack(s.toCharArray(), 0, res);
return res;
}
private void backtrack(char[] arr, int idx, List<String> res) {
if (idx == arr.length) {
res.add(new String(arr));
return;
}
if (Character.isDigit(arr[idx])) {
backtrack(arr, idx + 1, res);
} else {
arr[idx] = Character.toLowerCase(arr[idx]);
backtrack(arr, idx + 1, res);
arr[idx] = Character.toUpperCase(arr[idx]);
backtrack(arr, idx + 1, res);
}
}
}
var letterCasePermutation = function(s) {
let res = [];
function backtrack(path, idx) {
if (idx === s.length) {
res.push(path);
return;
}
if (/[0-9]/.test(s[idx])) {
backtrack(path + s[idx], idx + 1);
} else {
backtrack(path + s[idx].toLowerCase(), idx + 1);
backtrack(path + s[idx].toUpperCase(), idx + 1);
}
}
backtrack("", 0);
return res;
};