从leetcode-Letter Combinations of a Phone Number归纳递归操作

【原创文章  作者:NicoleHe】

这是一道非常简单的递归调用的题,但是我在思考时产生了一些混乱,隐隐约约觉得很熟悉,就是不能用代码实现出来。

因此这里针对这类“依次添加”的问题给出大致的递归操作步骤。以后会更新对应的栈操作实现。

首先放出原题:Letter Combinations of a Phone Number

Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent.

A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.

  • 这个题目的意思是,就像我们用九键手机输入法,输入几个数字,那么我们需要给出所有的字母可能组合。
  • 初看起来,题目非常简单,只要一个一个循环就好了。但是要怎么做到“一个一个循环”呢?如果只用for循环,我们是无法知道需要多少层for的,因为层数取决于输入数字的个数,是一个不确定的值。那么这个时候,就需要递归调用了。
  • 其实这个问题和汉诺塔问题存在着类似的关系,因为汉诺塔问题虽然也是来回折腾每个盘片,初看起来需要循环,但是盘片数目不确定,因此使用递归。
  • 那么是不是可以在脑海里有这样的印象:对于需要逐个解决的元素,元素的个数是不确定的,而又需要记录元素处理的过程,这个时候就可以考虑递归。
  • 这里的元素是指:在这个问题中是数字对应的字母,在汉诺塔问题中就是不同位置的盘片。

当然,汉诺塔和数字字母转换问题有一点微妙的区别,那就是汉诺塔的解决方案只有一种,但是数字字母转换问题有多个可能结果,也就是需要保存每次调用的“路径”。这个对算法结构没有影响,只是在多个可能值多条路径的时候,可以考虑把上一轮处理后的临时结果作为参数,传到下一轮递归中,递归终止的时候,把得到的可能结果/路径之一记录到result中。(当然,这里的result需要作为递归调用的引用参数,这样在递归止的时候才能把结果记录进去;如果需要逆序,就将元素处理结果作为递归函数的返回值,调用返回后即可得到该元素对应的结果)

现在回到这个数字转成字母的问题:首先我构造了一个字符串向量,记录每个数字可能对应的字母。(Letters[0]表示数字2对应的可能字母,用字符串"abc"表示)

扫描二维码关注公众号,回复: 4383701 查看本文章
 1 vector<string> letterCombinations(string digits) {
 2         int len = digits.size();
 3         vector<string> ans;
 4         if(len == 0) return ans;
 5         
 6         string Letters[8];
 7         Letters[0] = "abc";
 8         Letters[1] = "def";
 9         Letters[2] = "ghi";
10         Letters[3] = "jkl";
11         Letters[4] = "mno";
12         Letters[5] = "pqrs";
13         Letters[6] = "tuv";
14         Letters[7] = "wxyz";
15         
16         string s = "";
17         getLetter(0, ans, s, digits, Letters);
18         return ans;
19  }

注意:实际上的数字和vector索引是相差2的。

来分析一下这个递归函数的参数:

  1. 需要保存临时结果。那么这个空字符串s就起到这个作用。
  2. 其次就是ans,也是前面提到的能保存最终结果的数据结构result,把它的引用作为递归函数的参数,那么递归要结束的时候,就可以把得到的字母序列保存进去了。
  3. 0是输入digits的索引,表示现在将要对第0个数字进行处理。这个Index说明了这个递归进行到了第几个数字,这样才能确定是否已经开始对最后一个数字进行操作了,便于确定递归结束条件。
  4. digits作为参数,是为了在递归函数内部能够方便地取到即将要进行操作的数字。
  5. Letters就是保存数字-字母对应关系的数据结构,我的初衷是将其作为全局变量,这里每次都作为引用参数传进递归函数,效果是一样的。

现在具体来看getLetter递归函数的实现:

 1 void getLetter(int index, vector<string>& ans, string s, string& digits, string* Letters) {
 2         if(index == digits.size()) {
 3             ans.push_back(s);
 4             return;
 5         }
 6         int num = digits[index] - '0' - 2;
 7         int sub_len = Letters[num].size();
 8         for(int i = 0; i < sub_len; i++) {
 9             getLetter(index+1, ans, s+Letters[num][i], digits, Letters);
10         }
11   }

来看递归函数的两大元素:

  1. 递归条件:用Index和digits的长度进行比较,index表示即将处理第index+1个数字(index从0开始),但是总共只有Index==digits.size()个数字,说明此时对所有的数字都处理完了,将临时结果s保存到ans中就可以返回了。
  2. 递归体:如果还不能返回,那么首先通过传进来的index和digits确定我们这个时候需要处理的数字digits[index],用表示,通过Letters得到num所有可能对应的字母(我这里是将所有可能的字母用一个string来标识),这个时候就可以使用for循环,得到每个可能的字母Letters[num][i],与已有的临时结果s相加,作为新的临时结果传进下一次递归调用中。

再来解释下s+Letters[num][i]; Letter是我定义的字符串数组,这个数组的索引+2就是对应的数字,所以第0个字符串对应数字2的可能取值,第1个字符串对应数字3的可能取值等,Letters[num]就是对应数字可能取到的所有字母组成的字符串;字符串也是支持索引操作的,所以我用了for循环,i表示这个字符串的第i+1个字母,也就是用for循环把这个数字所有可能取到的字母遍历一遍。

大体流程如下:

取第1个数字 ==> 对第1个数字的所有可能字母,新建临时结果s(此时只有一个字母)==> 递归调用,取第2个数字 ==> 对第二个数字的所有可能字母,新建临时结果为s+a,其中a为可能取到的字母(通过for循环遍历所有情况)==> 递归调用,取第3个数字 ==> ... ==> 取完了最后一个数字,将最终的临时结果s保存到ans中。

如输入234:

其中每个处理数字后的花括号都是一次递归调用过程,花括号内部是循环遍历每种可能的字母情况,其中的s是每次递归调用作为临时结果的传递参数,走到最后一步的时候,将临时结果s保存到ans中,这样,ans就保存了每种可能的结果。

我的第一篇原创博文~如果大家有任何对于格式或者内容上的建议或评价,请不吝赐教,谢谢!

猜你喜欢

转载自www.cnblogs.com/nicolelfhe/p/10072988.html