字母大小写全排列

标签: 位运算 字符串 回溯

难度: Medium

给定一个字符串 s ,通过将字符串 s 中的每个字母转变大小写,我们可以获得一个新的字符串。

返回 所有可能得到的字符串集合 。以 任意顺序 返回输出。

示例 1:

输入:s = "a1b2"
输出:["a1b2", "a1B2", "A1b2", "A1B2"]

示例 2:

输入: s = "3z4"
输出: ["3z4","3Z4"]

提示:

  • 1 <= s.length <= 12
  • s 由小写英文字母、大写英文字母和数字组成

Submission

运行时间: 22 ms

内存: 17.3 MB

from typing import List

class Solution:
    def letterCasePermutation(self, s: str) -> List[str]:
        def backtrack(string, index, current):
            # 如果已经处理完整个字符串,将当前结果添加到结果列表中
            if index == len(string):
                results.append(current)
                return
            
            # 获取当前字符
            char = string[index]
            # 如果是字母,则生成两个分支:小写和大写
            if char.isalpha():
                backtrack(string, index + 1, current + char.lower())
                backtrack(string, index + 1, current + char.upper())
            else:
                # 如果不是字母,则直接添加到当前结果中并继续下一个字符
                backtrack(string, index + 1, current + char)
        
        # 存储所有可能字符串的结果列表
        results = []
        # 从索引0开始回溯
        backtrack(s, 0, "")
        # 返回结果列表
        return results

# 示例测试
solution = Solution()
print(solution.letterCasePermutation("a1b2"))  # 输出: ["a1b2", "a1B2", "A1b2", "A1B2"]
print(solution.letterCasePermutation("3z4"))   # 输出: ["3z4", "3Z4"]

Explain

此题解采用了回溯算法。主要思路是遍历给定字符串s的每个字符,如果字符是字母,则有两个选择:小写形式和大写形式。对于每个字母,都递归地探索这两种可能性。如果字符是数字,则无需改变,直接添加到当前字符串,并继续处理下一个字符。当递归到字符串末尾时,将当前构建的字符串添加到结果列表中。这种方法系统地探索了所有可能的字母大小写组合,从而生成所有可能的字符串。

时间复杂度: O(2^n)

空间复杂度: O(2^n * n)

from typing import List

class Solution:
    def letterCasePermutation(self, s: str) -> List[str]:
        def backtrack(string, index, current):
            # 如果已经处理完整个字符串,将当前结果添加到结果列表中
            if index == len(string):
                results.append(current)
                return
            
            # 获取当前字符
            char = string[index]
            # 如果是字母,则生成两个分支:小写和大写
            if char.isalpha():
                backtrack(string, index + 1, current + char.lower())
                backtrack(string, index + 1, current + char.upper())
            else:
                # 如果不是字母,则直接添加到当前结果中并继续下一个字符
                backtrack(string, index + 1, current + char)
        
        # 存储所有可能字符串的结果列表
        results = []
        # 从索引0开始回溯
        backtrack(s, 0, "")
        # 返回结果列表
        return results

# 示例测试
solution = Solution()
print(solution.letterCasePermutation("a1b2"))  # 输出: ["a1b2", "a1B2", "A1b2", "A1B2"]
print(solution.letterCasePermutation("3z4"))   # 输出: ["3z4", "3Z4"]

Explore

回溯算法在处理字母大小写全排列时,首先遍历输入字符串的每一个字符。对于每个字符,如果它是字母,则会产生两个选择分支:一个是该字符的小写形式,另一个是大写形式。对这两种选择分别进行递归探索,继续处理字符串的下一个字符。如果字符是数字或其他非字母字符,则直接将其添加到当前正在构建的字符串中,并只有一个递归分支继续向下处理。这样,通过逐字符决策,递归地探索所有可能的大小写组合,直至处理完整个字符串,最终达到生成所有可能字符串的目的。

在递归函数中,立即处理下一个字符的索引是为了继续构建从当前字符派生的所有可能的字符串。递归的本质是分解问题为更小的子问题进行解决,而在这个问题中,每处理完一个字符,都需要考虑下一个字符的所有可能性(大小写或直接继承),以确保能够探索到从当前字符开始的所有可能组合。这种连续的递归调用保证了算法能够遍历整个字符串的每一种大小写变体组合。

当字符是数字时,直接将其添加到当前字符串并继续处理不会漏掉任何潜在的字符串组合。这是因为数字和其他非字母字符没有大小写变体,因此不像字母字符那样有多个处理分支。数字的处理只有一种可能性,即保持其原样,所以直接继承到当前字符串是正确且高效的处理方式。

在递归函数中,每当到达字符串的末端(即处理完所有字符),当前构建的字符串就会被添加到结果列表中。这是通过在递归函数中设置一个基本条件来实现的:如果当前的字符索引等于字符串长度,表明已经处理完字符串中的所有字符,此时将当前构建的字符串添加到结果列表。这种方法确保了每一条递归路径(即每一个字符的大小写决策路径)都能在字符串处理完毕时将其结果存储,从而保证了所有可能的字符串都被完整生成并收录。