给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
解题思路:
我想的是按照树的dfs做,不知道怎么动手
打开评论区,先做出来再理解吧。
本题是一个电话号码组合的问题,或者说是一个全排列问题。
路径:也就是已经做出的选择。
选择列表:也就是你当前可以做的选择。
结束条件:也就是到达决策树底层,无法再做选择的条件。
我们所使用的框架基本就是:
其中最关键的点就是:在递归之前做选择,在递归之后撤销选择。
LinkedList result = new LinkedList();
public void backtrack(路径,选择列表){if(满足结束条件){result.add(结果);}for(选择:选择列表){做出选择;backtrack(路径,选择列表);撤销选择;}
}
对于本题来说,我们只需要将数字对应的字母映射到map当中,之后按照回溯的模板来写代码即可。
代码
class Solution {ArrayList res = new ArrayList<>();public List<String> letterCombinations(String digits) {if(digits == null ||digits.length() == 0){return res;}HashMap<Character,char[]> map = new HashMap<>();map.put('2',new char[]{'a','b','c'});map.put('3',new char[]{'d','e','f'});map.put('4',new char[]{'g','h','i'});map.put('5',new char[]{'j','k','l'});map.put('6',new char[]{'m','n','o'});map.put('7',new char[]{'p','q','r','s'});map.put('8',new char[]{'t','u','v'});map.put('9',new char[]{'w','x','y','z'});conbin(digits,0,new StringBuilder().append(""),map);return res;}private void conbin(String digits, int depth, StringBuilder str,HashMap<Character,char[]> map){if(depth == digits.length()){res.add(str.toString());return;}char temp = digits.charAt(depth);char[] charArr = map.get(temp);for(char c: charArr){str.append(c);conbin(digits,depth+1,str,map);//撤销选择str.deleteCharAt(str.length() - 1);}}
}
作者:ming-zhi-shan-you–m9RfkvKDad
这个回溯算法跟dfs好像一样的,树的有结构直接遍历,这个需要自己创建列表选择,保存选择,先记住这个回溯算法再说。最后这个撤销什么意思?
解决一个回溯问题,实际上就是一个决策树的遍历过程。你只需要思考 3 个问题:
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
如果你不理解这三个词语的解释,没关系,我们后面会用「全排列」和「N 皇后问题」这两个经典的回溯算法问题来帮你理解这些词语是什么意思,现在你先留着印象。
代码方面,回溯算法的框架:
result = []
def backtrack(路径, 选择列表):if 满足结束条件:result.add(路径)returnfor 选择 in 选择列表:做选择backtrack(路径, 选择列表)撤销选择
其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单。
什么叫做选择和撤销选择呢,这个框架的底层原理是什么呢?下面我们就通过「全排列」这个问题来解开之前的疑惑,详细探究一下其中的奥妙!
一、全排列问题
你现在就在做决策,可以选择 1 那条树枝,也可以选择 3 那条树枝。为啥只能在 1 和 3 之中选择呢?因为 2 这个树枝在你身后,这个选择你之前做过了,而全排列是不允许重复使用数字的。
现在可以解答开头的几个名词:[2] 就是「路径」,记录你已经做过的选择;[1,3] 就是「选择列表」,表示你当前可以做出的选择;「结束条件」就是遍历到树的底层,在这里就是选择列表为空的时候。
如果明白了这几个名词,可以把「路径」和「选择」列表作为决策树上每个节点的属性,比如下图列出了几个节点的属性:我们定义的 backtrack 函数其实就像一个指针,在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层,其「路径」就是一个全排列。
再进一步,如何遍历一棵树?这个应该不难吧。回忆一下之前「学习数据结构的框架思维」写过,各种搜索问题其实都是树的遍历问题,而多叉树的遍历框架就是这样:
void traverse(TreeNode root) {for (TreeNode child : root.childern)// 前序遍历需要的操作traverse(child);// 后序遍历需要的操作
}
而所谓的前序遍历和后序遍历,他们只是两个很有用的时间点,我给你画张图你就明白了:前序遍历的代码在进入某一个节点之前的那个时间点执行,后序遍历代码在离开某个节点之后的那个时间点执行。
回想我们刚才说的,「路径」和「选择」是每个节点的属性,函数在树上游走要正确维护节点的属性,那么就要在这两个特殊时间点搞点动作:现在,你是否理解了回溯算法的这段核心框架?
for 选择 in 选择列表:# 做选择将该选择从选择列表移除路径.add(选择)backtrack(路径, 选择列表)# 撤销选择路径.remove(选择)将该选择再加入选择列表
我们只要在递归之前做出选择,在递归之后撤销刚才的选择,就能正确得到每个节点的选择列表和路径。
下面,直接看全排列代码:
List<List<Integer>> res = new LinkedList<>();/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {// 记录「路径」LinkedList<Integer> track = new LinkedList<>();backtrack(nums, track);return res;
}// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {// 触发结束条件if (track.size() == nums.length) {res.add(new LinkedList(track));return;}for (int i = 0; i < nums.length; i++) {// 排除不合法的选择if (track.contains(nums[i]))continue;// 做选择track.add(nums[i]);// 进入下一层决策树backtrack(nums, track);// 取消选择track.removeLast();}
}
我们就通过全排列问题详解了回溯算法的底层原理。当然,这个算法解决全排列不是很高效,应为对链表使用 contains 方法需要 O(N) 的时间复杂度。有更好的方法通过交换元素达到目的,但是难理解一些,这里就不写了,有兴趣可以自行搜索一下。
但是必须说明的是,不管怎么优化,都符合回溯框架,而且时间复杂度都不可能低于 O(N!),因为穷举整棵决策树是无法避免的。这也是回溯算法的一个特点,不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。
明白了全排列问题,就可以直接套回溯算法框架了
最后总结
回溯算法就是个多叉树的遍历问题,关键就是在前序遍历和后序遍历的位置做一些操作,算法框架如下:
def backtrack(...):for 选择 in 选择列表:做选择backtrack(...)撤销选择
写 backtrack 函数时,需要维护走过的「路径」和当前可以做的「选择列表」,当触发「结束条件」时,将「路径」记入结果集。
其实想想看,回溯算法和动态规划是不是有点像呢?我们在动态规划系列文章中多次强调,动态规划的三个需要明确的点就是「状态」「选择」和「base case」,是不是就对应着走过的「路径」,当前的「选择列表」和「结束条件」?
某种程度上说,动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table 或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划。而今天的两个问题,都没有重叠子问题,也就是回溯算法问题了,复杂度非常高是不可避免的。
这个链接讲的很清楚 ,刷题换地方了
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。相关推荐
- 算法---给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合
题目 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合.答案可以按 任意顺序 返回.给出数字到字母的映射如下(与电话按键相同).注意 1 不对应任何字母.示例 1:输入:digits ...
- C++ leetcode 17. 电话号码的字母组合 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
一.解题思路 用map可以加快查询速度,DFS深度搜索,DFS是一种很常见的对于多循环或者n!的好的搜索算法. 二.代码:C++ void init(map<char, string> & ...
- 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 示例: 输入: "25525511135" 输出: ["255.255.11.135", ...
- 【算法-Java】给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
一.示例 示例 1: 输入:s = "25525511135" 输出:["255.255.11.135","255.255.111.35"] ...
- 给定一个最多包含40亿个随机排列的32位整数的顺序文件,找出一个不在文件中的32位整数
给定一个最多包含40亿个随机排列的32位整数的顺序文件,找出一个不在文件中的32位整数. 1.在文件中至少存在这样一个数? 2.如果有足够的内存,如何处理? ...
- 01迷宫:有一个仅由数字0与1组成的n×n格迷宫。若你位于一格0上,那么你可以移动到相邻4格中的某一格1上,同样若你位于一格1上,那么你可以移动到相邻4格中的某一格0上。
题目描述 有一个仅由数字0与1组成的n×n格迷宫.若你位于一格0上,那么你可以移动到相邻4格中的某一格1上,同样若你位于一格1上,那么你可以移动到相邻4格中的某一格0上. 你的任务是:对于给定的迷宫, ...
- java 不包含 字母和数字_Java String - 查看字符串是否仅包含数字而不包含字母
性能方面parseInt等等比其他解决方案要糟糕得多,因为至少需要异常处理. 我已经运行了jmh测试并且发现使用charAt迭代字符串并且将字符与边界字符进行比较是测试字符串是否仅包含数字的最快方法. ...
- java小编程----给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。
package com.henu;import java.util.Arrays;/*** @author limengdong* @date 2019年7月19日* @classroom 208bi ...
- 给定一个9位数字的ISBN,求其校验位
问题描述: 给定给定一个9位数字的ISBN,求其校验位.ISBN格式为2-02-033598,校验位的计算方法如下:从左到右依次将各位数字乘10,9,8,--,2,求出其和S,作模运算得M=S m ...
最新文章
- 「2017 山东一轮集训 Day2」Pair (霍尔定理+线段树)
- 是银弹吗?业务基线方法论
- Libcurl的介绍
- Mac上MacVim安装与配置
- Filecoin Gas基础费率涨至4.78 nanoFIL
- java反射机制中的getDeclaredField()
- 如何区别文本是BIG5还是GB?
- Linux实战教学笔记53:开源虚拟化KVM(一)搭建部署与概述
- 企业建行手机银行怎么对公转账限额
- java环境_linu安装jdk
- MVNO忽略国内漫游(ignore national roaming)
- K-Means聚类算法---C++
- mysql查询未使用索引,监控MySQL中未使用的索引
- Spring Cloud教程(十二)加密和解密
- HTML中上传与读取图片或文件(input file)----在路上(25)
- 支持向量机 (三): 优化方法与支持向量回归
- java 打印十字图
- react组件深度解读
- uniapp-微信小程序 分包--详解
- 计算机设备替换方案,解决方案:可以更换计算机上的cpu吗?如何替换cpu(台式机,笔记本)-全文...