【LeetCode】17. Letter Combinations of a Phone Number 集合元素配对

一、概述

给出2-9共八个数字与字母的映射,然后输入一个数字序列,输出所有可能的字母序列。

本题的背景为九宫格输入法,第一眼看到这题有点蛋疼。因为暴力穷举实在是太好想了,但是时间复杂度过于夸张——就算按小了算,每个数字对应三个字母,那一个有10个数字的序列则会有3^{10}个结果,时间复杂度为O\left ( 3^{n} \right ),这太大了。

然后开始想有没有什么好方法。

比如说先把两个数字的情况列出来,遇到相同的数字对可以简化操作等。但都没有在本质上减少时间。为什么?因为你n个数字,最后的结果一定是3^{n}个,那为了得到这些结果,循环一定要这么多次。无论你什么算法,这都是不可避免的。因此找不到更好的方法。

但是我在看到题目的时候没想这么多,就想找到一些好的方法,无果。不得不去看discuss。

主要思路有两个,其一就是暴力求解,其二则是DFS,另外还有回溯剪枝的方法。

偷偷说一句,我当时是想到了DFS的方法的,但是由于太久没刷题,做DFS还在建树,建树的过程把我卡住了。真丢人。

其实DFS不用建树的,迭代就好了。

二、分析

1、暴力求解

我们先来看输入,string digits,是一个字符串。整体思路就是遍历整个字符串,每次拿出一个数字。初始化一个字符串vector作为结果result。拿出的数字会对应另一个字符串Seq。我们要做的工作就是对于结果result中的每个字符串,在它后面加上Seq中的一个元素组成一个新的字符串,重新存在result中。

举例来说,对于digits为234的情况:

首先我们拿出2,2对应的是abc。然后初始化result。此时result为空。然后在abc中取出a,将a放在result中的每个元素的后面。嗯,result中没有元素,所以将a自己存在result中。然后拿出b,同理把b存进去,然后是c。这样数字2对应的字符串就处理完了。

接下来拿出3,3对应的是def。首先取出d,将d放在result中的每个元素后面。首先放在a后面,是ad;然后放在b后面,bd;接下来cd。这样d处理完了。接下来是e。仍然取出a,得到ae;然后是be,ce。同理得到af,bf,cf。

注意一点,不要得到ad、bd、cd就存回result,因为a、b、c接下来还有用呢,要把Seq这个序列都处理完了再都放回result里面。因此在处理Seq的时候,每次先新建一个结果tmp,然后将tmp赋值给result比较好。

代码如下:

class Solution {
public:
    string PhoneNum[10]={{""},{""},{"abc"},{"def"},{"ghi"},{"jkl"},{"mno"},{"pqrs"},{"tuv"},{"wxyz"}};
    //注意这里,string数组初始化必须用PhoneNum[10]而不能用PhoneNum[],否则报错“无法从类内初始值设定项推导数组界限”                  
    vector<string> letterCombinations(string digits) {
        vector<string> result;
        if (digits.length()==0)//C++中,string的length和size没有区别,都是返回字节数
            return result;
        result.push_back("");//这一句必须要有,没有这一句,只声明的话,result的size是0;有这一句就是1
        for(int i=0;i<digits.length();i++)
        {
            int NowNum=digits[i]-'0';
            vector<string> tmp;
            if(NowNum<2)
                continue;
            string& Seq=PhoneNum[NowNum];
            for(int j=0;j<Seq.length();j++)
                for(int k=0;k<result.size();k++)
                {
                    tmp.push_back(result[k]+Seq[j]);
                }
            result=tmp;
        }
        return result;
    }
};

但是结果有点差强人意。

修改几处可以得到很好的结果:

修改的地方是这里:

所有的i++都变成++i;这样会让时间加速极多。

将赋值操作result=tmp;变为调用函数result.swap(tmp);可以减少空间的使用。原因是赋值操作是把tmp复制然后赋值给result;而swap则是指针的变化。

但我觉得这个时间消耗0ms仍然有点怪怪的。

2、DFS求解

DFS不用建树!!!记住。

这个DFS的方法还是很简单的。法1的总体框架是一层一层的叠上去,最后统一输出所有元素;而法2则是先输出第一个元素,再输出第二个...在这一点上有区别。

对于普通的二叉树DFS我们都很熟悉:

先是确定递归出口,然后循环调用DFS。对于本题也一样。

先确定几个要点:对于输出的字符串数组中的每个字符串元素,其大小与输入的数字字符串大小相同,比如说你输入的是23,那无论输出的是ad还是cf,长度都是2。明确了这一点我们就可以确定递归出口了。

本题中的DFS要维护以下几个变量:tmp、Dig、nowDep、maxDep。其中,tmp是某个输出元素,它在DFS到某个叶节点时就变成输出元素的完全体;Dig就是输入的数字字符串;nowDep是现在tmp处理到第几个字母;maxDep是tmp最大要有几个字母。

在循环调用DFS时,循环结构是Dig中对应位对应的字母的字符串,就像2对应abc,那就对abc分别DFS。循环体就是调用函数。

同样以输入23为例。

首先判断递归出口,nowDep=0,maxDep=2;不退出,然后开始循环:Dig的第nowDep个元素是2,由于2对应的是abc,因此拿出a,把a放入tmp,然后调用DFS。

首先判断递归出口,nowDep=1,maxDep=2;不退出,然后开始循环:Dig的第nowDep个元素是3,由于3对应的是def,因此拿出d,把d放入tmp,然后调用DFS。

首先判断递归出口,nowDep=2,maxDep=2;因此将tmp压入result,退出。从而得到第一个元素ad。

代码如下:

class Solution {
public:
    string PhoneNum[10]={{""},{""},{"abc"},{"def"},{"ghi"},{"jkl"},{"mno"},{"pqrs"},{"tuv"},{"wxyz"}};     vector<string> result; //注意这里的result是全局变量              
    vector<string> letterCombinations(string digits)
    {
	    if (digits.length() == 0)
		    return result;
	    int maxDep = digits.size();
	    string tmp(maxDep, 0);
	    DFS(tmp, digits, 0, maxDep);
	    return result;
    }
    void DFS(string &tmp, string Dig, int nowDep, int maxDep)
    {
	    if (nowDep == maxDep)
		    result.push_back(tmp);
	    else
	    {
		    for (int i = 0; i<PhoneNum[Dig[nowDep]-'0'].size(); i++)//注意这里要-'0'
		    {
		    	tmp[nowDep] = PhoneNum[Dig[nowDep]-'0'][i];//注意这里要-'0'
		    	DFS(tmp, Dig, nowDep + 1, maxDep);
	    	}
    	}
    }
};

时间复杂度不错,空间复杂度有点大。

有两个地方要注意:

其一是string的静态初始化,可以用string tmp(a,b)来初始化,这样的效果是string中有a个b元素;

其二是char转换成int一定注意减去'0',这个debug有点难看出来。

三、总结

算是水题,只要明确了时间复杂度没法降下来就好做了。主要是DFS的应用。

几个月没刷题现在手很生,所以做的有点慢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值