17. Letter Combinations of a Phone Number,手机输入法的字母组合

Last Updated: 2023-12-25 07:47:28 Monday

-- TOC --

题目

Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order. A mapping of digits to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.

输入一个数字字符串,仅包含[2-9],输出所有可能的数字代表的字母组合,任意顺序。数字到字母的mapping,与手机上的输入法一样,如下:

2: abc
3: def
4: ghi
5: jkl
6: mno
7: pqrs
8: tuv
9: wxyz
Example 1:
Input: digits = "23"
Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

Example 2:
Input: digits = ""
Output: []

Example 3:
Input: digits = "2"
Output: ["a","b","c"]

Constraints:

题目并没有说明输入的字符串中,是否会存在重复的数字,是可以重复的。

组合计算

Python

class Solution:

    digit2letter = {'2':('a','b','c'),
                    '3':('d','e','f'),
                    '4':('g','h','i'),
                    '5':('j','k','l'),
                    '6':('m','n','o'),
                    '7':('p','q','r','s'),
                    '8':('t','u','v'),
                    '9':('w','x','y','z')}

    def letterCombinations(self, digits: str) -> List[str]:
        a = [Solution.digit2letter[d] for d in digits]
        r = ['']
        for i in range(len(a)):
            r = [s+c for c in a[i]
                     for s in r]
        return [] if len(r)==1 else r  # cannot return ['']

从一个初始状态开始,一层层循环构建,每一层循环,都利用了上一次计算的结果。这个思路类似DP算法,但是这里有个,如果此题的digit很长很长,采用此思路,保存中间状态所需要的空间会非常庞大,空间复杂度太高!

C++

static unordered_map<char,string> digit2letter \
{{'2',"abc"},
 {'3',"def"},
 {'4',"ghi"},
 {'5',"jkl"},
 {'6',"mno"},
 {'7',"qprs"},
 {'8',"tuv"},
 {'9',"wxyz"}};

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        vector<string> r;
        if(digits == "")
            return r;
        r.reserve(64);
        for(auto c: digit2letter[digits[0]])
            r.emplace_back(1,c);
        vector<string> m;
        m.reserve(64);
        for(auto d: digits.substr(1)){
            m.clear();
            for(size_t i{}; i<r.size(); ++i)
                for(auto c: digit2letter[d])
                    m.emplace_back(r[i]+c);
            swap(r,m);
        }
        return r;
    }
};

由于类型的原因,unordered_map的key是char型。to_string不能支持char类型,char类型会被当做int来处理,比较坑!

C

const static char *d2s[] = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
const static char d2slen[] = {3,3,3,3,3,4,3,4};

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
char ** letterCombinations(char * digits, int* returnSize){
    if(digits[0] == 0){
        *returnSize = 0;
        return NULL;
    }
    int ndigit = strlen(digits);
    int ndigitp1 = ndigit + 1;

#define I(i) (digits[i]-'2')

    *returnSize = d2slen[I(0)];
    for(int i=1; i<ndigit; ++i)
        *returnSize *= d2slen[I(i)];

    char (*mr)[ndigitp1] = calloc(*returnSize, ndigitp1);
    int mrsize = d2slen[I(0)];
    for(int i=0; i<mrsize; ++i)
        mr[i][0] = d2s[I(0)][i];

    char (*mr2)[ndigitp1] = calloc(*returnSize, ndigitp1);
    for(int i=1; i<ndigit; ++i){
        int mr2size = 0;
        for(int j=0; j<mrsize; ++j){
            for(int k=0; k<d2slen[I(i)]; ++k){
                memcpy(mr2[mr2size], mr[j], i);
                mr2[mr2size][i] = d2s[I(i)][k];
                ++mr2size;
            }
        }
        memcpy(mr, mr2, mr2size*ndigitp1);
        mrsize = mr2size;
        memset(mr2, 0, mr2size*ndigitp1);
    }
    free(mr2);

#undef I

    char **r = malloc(sizeof(char*)*(*returnSize));
    for(int i=0; i<*returnSize; ++i){
        r[i] = malloc(ndigitp1);
        memcpy(r[i], mr[i], ndigitp1);
    }
    free(mr);

    return r;
}

Backtracking(回溯)

本题的技能点是Backtracking,这个point有点意思:这类递归实现,本质上是将循环递归化。而递归化的原因,是循环嵌套深度的不确定,它是个参数,无法提前写出nested loop的代码。

时间复杂度为:\(O(M^N)\),M为输入中各个组合的元素个数的最大值,N为输入组合的个数。这类算法,M和N的值都不能太大!

回溯虽然使用了递归,但是空间复杂度相比前一个算法,大大降低!如果不算递归消耗的栈空间,那么空间复杂度就是\(O(1)\)。不再保存大量的中间结果,每个中间结果,都直接以参数的形式进行传递。

Python

class Solution:

    digit2letter = {'2':('a','b','c'),
                    '3':('d','e','f'),
                    '4':('g','h','i'),
                    '5':('j','k','l'),
                    '6':('m','n','o'),
                    '7':('p','q','r','s'),
                    '8':('t','u','v'),
                    '9':('w','x','y','z')}

    def letterCombinations(self, digits: str) -> List[str]:
        def _backtrack(i, t):
            if i == n:
                r.append(t)
                return
            for c in Solution.digit2letter[digits[i]]:
                _backtrack(i+1, t+c)

        n = len(digits)
        r = []
        _backtrack(0, '')
        return [] if n==0 else r

Backtracking的感觉在这里:一层层递归深入,先得到一个解后,再返回上一层,然后再深入,再返回......这就是回溯!回溯就是先一口气找到一个解,然后回到前面某个地方(递归调用返回的地方),找下一个解!

C++

static unordered_map<char,string> digit2letter \
{{'2',"abc"},
 {'3',"def"},
 {'4',"ghi"},
 {'5',"jkl"},
 {'6',"mno"},
 {'7',"qprs"},
 {'8',"tuv"},
 {'9',"wxyz"}};

class Solution {
    string digits;
    int size;
    vector<string> r;

    void backtrace(int level, string s){
        if(level == size){
            this->r.push_back(s);
            return;
        }
        for(auto c: digit2letter[digits[level]])
            backtrace(level+1, s+c);
    }

public:
    vector<string> letterCombinations(string digits) {
        this->r.clear();
        if(digits == "")
            return this->r;
        this->digits = digits;
        this->size = digits.size();
        backtrace(0, "");
        return this->r;
    }
};

C

const static char *d2s[] = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
const static char d2slen[] = {3,3,3,3,3,4,3,4};

void _backtrack(int level, char *digits, int ndigit,
                char **r, int *idx, char *p){
    if(level == ndigit){
        p[level] = 0;
        strcpy(r[(*idx)++], p);
        return;
    }
    int i = digits[level] - '2';
    for(int j=0; j<d2slen[i]; ++j){
        p[level] = d2s[i][j];
        _backtrack(level+1, digits, ndigit, 
                   r, idx, p);
    }
}

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
char ** letterCombinations(char * digits, int* returnSize){
    if(digits[0] == 0){
        *returnSize = 0;
        return NULL;
    }

    int ndigit = strlen(digits);
    int ndigitp1 = ndigit + 1;

    *returnSize = d2slen[digits[0]-'2'];
    for(int i=1; i<ndigit; ++i)
        *returnSize *= d2slen[digits[i]-'2'];

    char **r = malloc(sizeof(char*)*(*returnSize));
    for(int i=0; i<*returnSize; ++i)
        r[i] = malloc(ndigitp1);

    char *p = malloc(ndigitp1);
    int idx = 0;
    _backtrack(0, digits, ndigit,
               r, &idx, p);
    free(p);
    return r;
}

本文链接:https://cs.pynote.net/ag/leetcode/202310101/

-- EOF --

-- MORE --