1. 程式人生 > >Leetcode演算法——17、電話號碼的字元組合

Leetcode演算法——17、電話號碼的字元組合

題目

給定一個字串 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、分治法

使用遞迴,將字串分為兩半,每一部分都使用同樣的分治法得到一系列字元組合,最後將這兩部分的所有組合進行笛卡爾積(兩重迴圈),就是所求結果。

這樣,就將 nn 重迴圈化為了(1+2+4+...+n/2=n11+2+4+...+n/2=n-1)次兩重迴圈,解決了不能一次性寫出 nn 重迴圈的問題。

比如:digits = ‘2345’,則先分為 ‘23’ 和 ‘45’ 兩部分分別進行列舉,其中 ‘23’ 又可以分為 ‘2’ 和 ‘3’ 兩部分,而這兩部分的笛卡爾積可以使用雙重迴圈得到,同理 ‘45’ 也可以得到,最後將 ‘23’ 和 ‘45’ 的結果再進行一次笛卡爾積,得到了最終結果。

3、動態規劃

不斷使用笛卡爾積:前 kk 位的遍歷結果(長度為 aa),分別都加上第 k+1k+1 位的所有可能的字元(字元個數為b

b),就得到了前 k+1k+1 位的遍歷結果(長度為 nmn*m)。

這樣做和分治法的好處相同,將 nn 重迴圈化為了 n1n-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))