Loading

回溯算法详解与代码模版-leetcode 17.电话号码的字母组合举例

1. 回溯算法思想

回溯算法其实是一种暴力搜索的思想,提到回溯算法也不能绕开递归,大部分情况下的回溯算法就是一个递归函数。回溯的思想就是先处理逻辑,然后在递归调用自己,在这之后,将之前处理的逻辑撤销掉,这里的撤销会导致的结果就是把原来的结果集的一部分删除,相当于回头重新添加结果,这也是回溯名字的由来。

2. 代码模版

public void backTrack(候选集, 结果集, 临时表) {
  // 代码的终止条件 递归出口
  if (终止条件) {
    // 将临时表放到结果集中
  }
  // 遍历当前的候选集的所有元素
  for (int i = 0; i < 候选集长度; i++) {
    // 处理 一般是将元素添加到临时表中
    临时表.add(val);
    backTrack(候选集, 结果集, 临时表);
    // 撤销选择 回溯的根源
    临时表.delete(val);
  }
}

注意点:

  • 回溯算法的递归函数一般是void返回值
  • 一定要注意终止条件,每个具体的题不一样,一般的终止条件就是当前的候选集已经遍历完全了,即没有可以选择的值了

3. 举例说明

采用力扣17.电话号码的字母组合举例说明回溯算法
题目:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字。

思路:

  • 保存每个按键所对应的字母,构成候选集keyMap
  • 在回溯函数参数传递中增加一个index用于判断当前是按下的哪个按键,当index按到了最后一个按键,则说明到达了终止条件
  • 使用for遍历每个按键中的每个字母,首先将字母添加到临时表sb中,然后递归到下一个按键,接下来将这个字母从临时表中删除,完成回溯
  • 这里的注意点是需要将第几个按键index和候选集keyMap中的数字对应起来,因为index始终是从0digits.length的,因此需要找到digits中的index对应的数字,然后将其作为keyMap的索引,即(int)digits.charAt(index) - 50,这里减50是因为将具体的char换成int需要减去48,但这里的数字是从2开始的,因此要减去50
    代码如下:
class Solution {
    // 结果列表
    LinkedList<String> res = new LinkedList();
    // 维护一个选择表
    char[][] keyMap = new char[][]{{'a', 'b', 'c'}, {'d', 'e', 'f'}, {'g', 'h', 'i'}, 
                        {'j', 'k', 'l'}, {'m', 'n', 'o'}, {'p', 'q', 'r', 's'}, 
                        {'t', 'u', 'v'}, {'w', 'x', 'y', 'z'}};

    public List<String> letterCombinations(String digits) {
        // 边界条件
        if (digits.equals("")) return new ArrayList();
        // 调用回溯函数
        backTrack(digits, 0, new StringBuilder());
        // 返回结果
        return res;
    }

    private void backTrack(String digits, int index, StringBuilder sb) {
        // 如果当前index已经到了最后一个数字 则保存退出
        if (index == digits.length()) {
            // 将sb的结果 (叶子节点上的) 放到结果集
            res.add(sb.toString()); 
            return;
        }
        // 否则先处理再递归
        int i = (int)digits.charAt(index) - 50; // 找到按下的第i个数字
        int len = keyMap[i].length; // 记录第i个数字中有几个字母 3 or 4
        for (int j = 0; j < len; j++) {
            // 把当前字母加到sb中
            sb.append(keyMap[i][j]);
            // 递归下去 到下一个按键 添加下一个字母
            backTrack(digits, ++index, sb);
            // 把刚刚添加的字母删除 完成回溯 
            sb.deleteCharAt(sb.length() - 1);
            // 这里减是为了让递归回来的时候 index保持不变,否则会出现数组的溢出
            index--;    
        }
        
    }
}

4. 今日学习

  • StringBuilder的删除某个索引的函数为sb.deleteCharAt(i)
StringBuilder sb = new StringBuilder();
// 删除sb中的最后一个元素
sb.deleteCharAt(sb.length() - 1);
// StringBuilder的加入元素, 不是add()
sb.append('a');
// StringBuilder的长度, 不是size()
sb.length();
posted @ 2021-11-05 13:43  smallchaochao  阅读(43)  评论(0)    收藏  举报