Leetcode算法——17、电话号码的字符组合

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/HappyRocking/article/details/83025308

题目

给定一个字符串 digits,包含了2~9的整数,要求返回所有可能的数字对应的字母组合。

每一个数字都对应一些字母,和电话拨号器相对应,如下:

1      2abc   3def
4ghi   5jkl   6mno
7pqrs  8tuv   9wxyz
*+     0      #

示例:
Input: “23”
Output: [“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].

思路

1、计数法

如果字符串的长度是固定的,比如 n = 3,那么我们可以写3个for循环,每个循环取某一位的所有可能字符,进行枚举。

但问题是长度不固定,不知道要写几个for循环。

可以重新考虑一种遍历方法,类似于数学上的手算加法,每次在个位+1,如果溢出则需要进位。

比如:digits=‘27’,分别含有3个和4个字母,共有12种枚举情况。可以定义一个两位数,个位满4进1,十位满3进1。从00开始遍历,每次+1,直至最高位溢出为止,遍历顺序为:

[00,01,02,03,10,11,12,13,20,21,22,23]

然后提前规定好每一位的数字代表哪个字符,便可以得到12中字符组合。

等价地,为了便于编码,可以从最高位开始+1,直至最低位溢出为止。

2、分治法

使用递归,将字符串分为两半,每一部分都使用同样的分治法得到一系列字符组合,最后将这两部分的所有组合进行笛卡尔积(两重循环),就是所求结果。

这样,就将 n n 重循环化为了( 1 + 2 + 4 + . . . + n / 2 = n 1 1+2+4+...+n/2=n-1 )次两重循环,解决了不能一次性写出 n n 重循环的问题。

扫描二维码关注公众号,回复: 3807255 查看本文章

比如:digits = ‘2345’,则先分为 ‘23’ 和 ‘45’ 两部分分别进行枚举,其中 ‘23’ 又可以分为 ‘2’ 和 ‘3’ 两部分,而这两部分的笛卡尔积可以使用双重循环得到,同理 ‘45’ 也可以得到,最后将 ‘23’ 和 ‘45’ 的结果再进行一次笛卡尔积,得到了最终结果。

3、动态规划

不断使用笛卡尔积:前 k k 位的遍历结果(长度为 a a ),分别都加上第 k + 1 k+1 位的所有可能的字符(字符个数为 b b ),就得到了前 k + 1 k+1 位的遍历结果(长度为 n m n*m )。

这样做和分治法的好处相同,将 n n 重循环化为了 n 1 n-1 次两重循环。

比如:digits = ‘234’,则先枚举出第1位的所有情况,即 ‘2’ 的所有字符,然后与 ‘3’ 的所有字符进行笛卡尔积,得到了 ‘23’ 的所有情况,然后继续与 ‘4’ 的所有字符进行笛卡尔积,得到了 ‘234’ 的所有情况。

python实现

def letterCombinations(digits):
    """
    :type digits: str
    :rtype: List[str]
    计数法
    """
    
    if not digits:
        return []
    
    # 获取每一位的所有可能子母
    letters = []
    nums = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
    for i in digits:
        letters.append(nums[int(i)])
    indexs = [0] * len(digits) # 初始化索引集合
    result = []
    while(True):
        # 将当前索引对应的字母加入到结果中
        tmp = ''
        for i,j in enumerate(indexs):
            tmp += letters[i][j]
        result.append(tmp)
        # 索引+1
        overflow = 0
        for i in range(len(indexs)):
            if i == 0: # 最高位+1
                indexs[0] += 1
            else: # 其他位加上进位1
                indexs[i] += overflow
            if indexs[i] >= len(letters[i]): # 溢出,下一位进1
                indexs[i] -= len(letters[i])
                overflow = 1
            else:
                overflow = 0
        if overflow == 1: # 说明最后一位也溢出了,则遍历结束
            break
    return result

def letterCombinations2(digits):
    """
    :type digits: str
    :rtype: List[str]
    分治法,递归。
    """
    nums = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
    if not digits:
        return []
    
    def fun_rec(l, u):
        '''
        寻找digits[l~u]之间的所有可能性
        '''
        # 递归结束条件
        if l > u:
            return []
        if l == u:
            return list(nums[int(digits[l])])
        
        # 分为两部分,分别得到遍历结果,然后整合
        mid = int((l + u) / 2)
        result1 = fun_rec(l, mid)
        result2 = fun_rec(mid+1, u)
        result = []
        for str1 in result1:
            for str2 in result2:
                result.append(str1 + str2)
        return result
    return fun_rec(0, len(digits)-1)

def letterCombinations3(digits):
    """
    :type digits: str
    :rtype: List[str]
    动态规划。
    """
    if not digits:
        return []
    nums = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
    result = []
    for i in digits:
        if not result:
            result = list(nums[int(i)])
        else:
            tmp = []
            for p in nums[int(i)]: # 遍历当前位的所有字母
                for exist in result: # 遍历之前的结果
                    tmp.append(exist + p)
            result = tmp
    return result

if '__main__' == __name__:
    digits = '237'
    print(letterCombinations3(digits))

猜你喜欢

转载自blog.csdn.net/HappyRocking/article/details/83025308
今日推荐