回溯法与迭代法详解:如何从手机数字键盘生成字母组合

在这篇文章中,我们将详细介绍如何基于手机数字键盘的映射,给定一个仅包含数字 2-9 的字符串,输出它能够表示的所有字母组合。这是一个经典的回溯算法问题,适合初学者理解和掌握。

问题描述

给定一个数字字符串,比如 digits = "23",手机数字键盘的映射如下:
在这里插入图片描述

2 -> "abc"
3 -> "def"
4 -> "ghi"
5 -> "jkl"
6 -> "mno"
7 -> "pqrs"
8 -> "tuv"
9 -> "wxyz"

我们需要找到所有可能的字母组合。举个例子:

"23" -> ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]

17. 电话号码的字母组合 - 力扣(LeetCode)

1. 问题分析

从问题的角度看,这是一个组合问题。手机键盘上的每个数字对应一组字母,我们要找到给定数字的所有字母组合。关键在于如何构建这些组合。我们可以通过回溯法或者迭代法来解决。

1.1 回溯法的思路

回溯算法是递归算法的一种,适合用来枚举所有可能的结果。在这个问题中,回溯的关键点是从第一个数字开始,遍历所有可能的字母组合,逐步构建字符串,直到所有数字都被处理完毕。

具体步骤如下:

  1. 选择:我们从 digits 中每个数字开始,找到对应的字母集。
  2. 递归:对于字母集中的每个字母,我们递归地去构建字符串,直到遍历完所有的数字。
  3. 回退:在递归过程中,构建的字符串达到一定长度后,我们会回退(撤销上一步操作),尝试其他的可能性。
1.2 多种解法
  • 回溯法:利用递归去枚举所有可能的组合。复杂度为 O ( 3 n ) O(3^n) O(3n) O ( 4 n ) O(4^n) O(4n),取决于输入的数字个数。
  • 迭代法:使用队列的方式逐步构造出所有的字母组合。每处理一个数字,都会将当前已有的组合与对应的新字母结合。

接下来我们详细介绍这两种解法。


2. 解法一:回溯法(Backtracking)

2.1 解题思路

回溯算法的基本思路是递归地构建可能的组合,并在递归结束时撤销选择。具体来说,我们会从第一个数字开始,对应的字母集选择一个字母,然后递归处理下一个数字。

回溯的步骤:

  1. 如果已经构建的字符串长度等于 digits 的长度,我们就将它加入结果集。
  2. 否则,继续处理下一个数字。
  3. 每次递归返回时,撤销上一次的选择,尝试下一个字母。
2.2 代码实现
#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

class Solution {
public:
    vector<string> ans;  // 存储结果
    string combination = "";  // 当前的字母组合

    void backtrack(const string& digits, unordered_map<char, string>& phone, int idx) {
        if (idx == digits.size()) {  // 当达到数字字符串的长度时
            ans.push_back(combination);  // 将组合加入结果集
            return;
        }
        
        char digit = digits[idx];  // 获取当前数字
        const string& letters = phone[digit];  // 获取当前数字对应的字母集
        
        for (char letter : letters) {  // 遍历字母集中的每个字母
            combination += letter;  // 做出选择
            backtrack(digits, phone, idx + 1);  // 递归处理下一个数字
            combination.pop_back();  // 回溯,撤销选择
        }
    }

    vector<string> letterCombinations(string digits) {
        if (digits.empty()) return {};  // 边界情况处理,空输入返回空数组
        
        // 建立数字到字母的映射
        unordered_map<char, string> phone = {
            {'2', "abc"}, {'3', "def"}, {'4', "ghi"}, {'5', "jkl"},
            {'6', "mno"}, {'7', "pqrs"}, {'8', "tuv"}, {'9', "wxyz"}
        };
        
        // 调用回溯函数
        backtrack(digits, phone, 0);
        
        return ans;  // 返回结果
    }
};
2.3 详细解释
  1. 递归终止条件:当索引 idx 达到 digits 的长度时,说明我们已经构造了一个完整的字母组合,将其加入结果集。
  2. 递归过程:对于当前数字,找到对应的字母集,遍历字母集中的每一个字母,加入到当前的组合字符串中,然后递归处理下一个数字。
  3. 回溯撤销选择:在递归返回后,我们调用 combination.pop_back() 撤销上一次递归中加入的字符,回退到上一个状态,从而尝试下一个字母。
2.4 示例模拟过程
  • 输入:digits = "23"
    • 递归过程:
      1. 处理第一个数字 2,对应的字母集是 ['a', 'b', 'c']
      2. 选中字母 'a',递归处理下一个数字 3,对应的字母集是 ['d', 'e', 'f']
      3. 选中字母 'd',完成组合 'ad',将其加入结果集。
      4. 回退到字母 'a',选择下一个字母 'e',构造组合 'ae',继续加入结果集。
      5. 继续这种方式,构造出 'af',然后回退选择字母 'b'
      6. 重复上述过程直到遍历所有字母,得到组合 ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]

3. 解法二:迭代法(使用队列)

3.1 解题思路

另一种方法是利用队列。我们可以使用一个队列来逐步生成组合:

  1. 从数字字符串的第一个数字开始,把对应的字母放入队列。
  2. 对于每一个数字,取出队列中所有的已有组合,然后在每个组合后附加当前数字对应的每一个字母,形成新的组合,再放回队列。
  3. 当处理完所有的数字时,队列中保存的就是所有可能的组合。
3.2 代码实现
#include <iostream>
#include <vector>
#include <unordered_map>
#include <queue>

using namespace std;

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        if (digits.empty()) return {};  // 边界情况处理,空输入返回空数组
        
        // 建立数字到字母的映射
        unordered_map<char, string> phone = {
            {'2', "abc"}, {'3', "def"}, {'4', "ghi"}, {'5', "jkl"},
            {'6', "mno"}, {'7', "pqrs"}, {'8', "tuv"}, {'9', "wxyz"}
        };
        
        queue<string> q;  // 定义一个队列来保存组合
        q.push("");  // 先往队列中放入一个空字符串
        
        // 遍历输入数字字符串中的每个数字
        for (char digit : digits) {
            int n = q.size();  // 记录当前队列的大小
            const string& letters = phone[digit];  // 获取当前数字对应的字母集
            
            for (int i = 0; i < n; i++) {  // 对于当前队列中每个已有组合
                string current = q.front();  // 取出队首元素
                q.pop();  // 移除队首
                
                // 对当前数字的每个字母,将其加到已有组合的后面
                for (char letter : letters) {
                    q.push(current + letter);  // 将新组合放回队列
                }
            }
        }
        
        // 最终队列中的所有元素就是结果
        vector<string> ans;
        while (!q.empty()) {
            ans.push_back(q.front());
            q.pop();
        }
        
        return ans;
    }
};
3.3 示例讲解
  • 输入:digits = "23"
    1. 初始化队列:q = [""]
    2. 处理

第一个数字 2,对应的字母集 ['a', 'b', 'c']。将每个字母加到队列中的每一个已有组合(当前为空字符串 ""),得到队列 q = ["a", "b", "c"]
3. 处理第二个数字 3,对应的字母集 ['d', 'e', 'f']。将每个字母加到队列中的每一个已有组合,得到 q = ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]
4. 最终队列中的元素就是结果。


4. 总结

  • 回溯法:通过递归逐步构建可能的组合,并在递归结束后撤销选择,时间复杂度接近 O ( 3 n ) O(3^n) O(3n) O ( 4 n ) O(4^n) O(4n),取决于数字对应的字母数量。回溯算法的优点是易于实现,适合处理这种组合问题。

  • 迭代法(队列):通过队列逐步构建组合,时间复杂度也是 O ( 3 n ) O(3^n) O(3n) O ( 4 n ) O(4^n) O(4n)。虽然有时实现起来可能比递归稍微复杂,但它在一些情况下的性能表现会更好。

对于新手来说,回溯法可能是更直观的解法,因为它的思路清晰,非常适合处理类似的组合问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/890428.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024 第一次周赛

A: 题目大意 骑士每连续 i 天每天会得到 i 个金币&#xff0c;&#xff08;i 1&#xff0c; 2&#xff0c; 3 &#xff0c; …&#xff09;,那么展开看每一天可以得到的金币数&#xff1a;1 2 2 3 3 3 4 4 4 5 5 5 5 5 … 可以发现就是1个1 &#xff0c;2个2, 3个3…,那么我…

关于md5强比较和弱比较绕过的实验

在ctf比赛题中我们的md5强弱比较的绕过题型很多&#xff0c;大部分都是结合了PHP来进行一个考核。这一篇文章我将讲解一下最基础的绕过知识。 MD5弱比较 比较的步骤 在进行弱比较时&#xff0c;PHP会按照以下步骤执行&#xff1a; 确定数据类型&#xff1a;检查参与比较的两…

Django的请求与响应

Django的请求与响应 1、常见的请求2、常见的响应3、案例 1、常见的请求 函数的参数request是一个对象&#xff0c;封装了用户发送过来的所有请求相关数据。 get请求一般用来请求获取数据&#xff0c;get请求也可以传参到后台&#xff0c;但是传递的参数显示在地址栏。 post请求…

vue3 高德地图标注(飞线,呼吸点)效果

装下这两个 npm 忘了具体命令了&#xff0c;百度一下就行 “loca”: “^1.0.1”, “amap/amap-jsapi-loader”: “^1.0.1”, <template><div id"map" style"width: 100%;height: 100%;"></div> </template><script setup> …

论文笔记:RelationPrompt :Zero-Shot Relation Triplet Extraction

论文来源: ACL Findings 2022 论文链接:https://arxiv.org/pdf/2203.09101.pdf 论文代码:http://github.com/declare-lab/RelationPrompt 本篇论文是由阿里达摩院自然语言智能实验室于2022年发表的关于零样本关系抽取的顶会论文,本篇博客将记录我在阅读过程中的一些笔记…

​ceph掉电后无法启动osd,pgs unknown

处理办法&#xff1a; 进一步osd.0的日志检查发现提示unable to read osd superblock&#xff1a; 尝试fsck操作&#xff1a; ceph-objectstore-tool --data-path /var/lib/ceph/osd/ceph-0/ --type bluestore --op fsck 如果成功&#xff0c;则到此为止。 如果失败&#xf…

K8s简介及环境搭建

一、Kubernetes简介 kubernetes 的本质是一组服务器集群&#xff0c;它可以在集群的每个节点上运行特定的程序&#xff0c;来对节点中的容器进行管理。目的是实现资源管理的自动化&#xff0c;主要提供了如下的主要功能&#xff1a; 自我修复&#xff1a;一旦某一个容器崩溃&a…

游戏加速器最新口令兑换码,最低50小时免费领取

不是月卡买不起&#xff0c;而是薅羊毛更有性价比&#xff01;游戏党福音&#xff0c;今天为玩家们分享最新一批雷雷口令兑换码&#xff0c;为您的游戏之旅全面保驾护航&#xff01; 兑换码&#xff1a;8521 兑换码&#xff1a;9989 兑换码&#xff1a;211314 兑换码&#…

springmvc的处理流程

用户把请求发到前端控制器&#xff0c;前端控制器通过handlerMapping找到controller&#xff0c;controller调用service&#xff0c;service调用dao&#xff0c;从数据库拿到要获取的数据&#xff0c;然后modelandview给前端控制器&#xff0c;前端控制器通过viewresolver解析视…

仿IOS桌面悬浮球(支持拖拽、自动吸附、自动改变透明度与点击、兼容PC端与移动端)

使用 pointerdown/pointermove/pointerup 实现仿IOS桌面悬浮球效果&#xff0c;支持拖拽、指定拖拽选对容器&#xff0c;指定拖拽安全区、自动吸附、自动改变透明度与点击&#xff0c;兼容PC端与移动端。 效果展示 https://code.juejin.cn/pen/7423757568268304421 代码实现 …

计算机网络:数据链路层 —— PPP 点对点协议

文章目录 PPP 帧PPP帧的格式PPP帧的透明传输面向字节的异步链路面向比特的同步链路 PPP帧的差错检测 PPP 的工作状态 点对点协议&#xff08;Point-to-Point Protocol&#xff0c;PPP&#xff09;是目前使用最广泛的点对点数据链路层协议&#xff0c;用于在两个节点之间进行数据…

双目视觉搭配YOLO实现3D测量

一、简介 双目&#xff08;Stereo Vision&#xff09;技术是一种利用两个相机来模拟人眼视觉的技术。通过对两个相机获取到的图像进行分析和匹配&#xff0c;可以计算出物体的深度信息。双目技术可以实现物体的三维重建、距离测量、运动分析等应用。 双目技术的原理是通过两…

【最新华为OD机试E卷-支持在线评测】英文输入法(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 💻 ACM金牌🏅️团队 | 大厂实习经历 | 多年算法竞赛经历 ✨ 本系列打算持续跟新华为OD-E/D卷的多语言AC题解 🧩 大部分包含 Python / C / Javascript / Java / Cpp 多语言代码 👏 感谢大家的订阅➕ 和 喜欢�…

大数据-158 Apache Kylin 安装配置详解 集群模式启动

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

多态常见面试问题

1、什么是多态&#xff1f; 多态&#xff08;Polymorphism&#xff09;是面向对象编程中的一个重要概念&#xff0c;它允许同一个接口表现出不同的行为。在C中&#xff0c;多态性主要通过虚函数来实现&#xff0c;分为编译时多态&#xff08;静态多态&#xff09;和运行时多态…

Qt事件——鼠标事件

通过label来显示各种事件 鼠标按下事件 //按下显示坐标 void MyLabel::mousePressEvent(QMouseEvent * ev) {int i ev->x();int j ev->y();//判断按下的鼠标键位if (ev->button() Qt::LeftButton) {qDebug() << "LeftButton";}else if (ev->bu…

HAL库常用的函数:

目录 HAL库&#xff1a; 1.GPIO常用函数&#xff1a; 1.HAL_GPIO_ReadPin( ) 2.HAL_GPIO_WritePin( ) 3.HAL_GPIO_TogglePin( ) 4.HAL_GPIO_EXTI_IRQHandler( ) 5.HAL_GPIO_EXTI_Callback( ) 2.UART常用函数&#xff1a; 1.HAL_U…

数通--3

一、动态路由 内部 路由器之间要互联互通&#xff0c;必须遵循相同的协议 企业内部用 IGP&#xff0c;企业之间用BGP RIP&#xff08;已淘汰&#xff0c;不考&#xff09; 距离就是长短&#xff0c;矢量就是方向&#xff0c;即路由的出接口 一台路由器 A 配好RIP&#xff0c;…

JavaWeb 17.过滤器

目录 一、过滤器概述 生活举例&#xff1a;公司前台&#xff0c;停车场安保系统&#xff0c;地铁检票闸机 过滤器开发中应用场景 过滤器工作位置图解 Filter接口API&#xff1a; 二、过滤器过滤过程图解 三、过滤器生命周期 四、过滤器链的使用 工作流程图解 注解方式配置过滤…

map和set(一)

首先模拟一下key形式类 使用的结构是搜索二叉树 结点中有左孩子和右孩子 还有一个存储的值 template <class K>struct BSTnode//搜索二叉树不支持修改 中序遍历是有序的{K _key;BSTnode<K>* _left;BSTnode<K>* _right;BSTnode(const K& key):_key(key…