力扣hot100-->递归/回溯

目录

递归/回溯

1. 17. 电话号码的字母组合

2. 22. 括号生成

3. 39. 组合总和

4. 46. 全排列

 5. 78. 子集


递归/回溯

1. 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'] 的一个数字。

// 定义一个类 Solution
class Solution {
public:
    // 定义一个字符串向量 v,存储数字到字母的映射关系,数字 2 到 9 分别对应不同的字母组合
    vector<string> v{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    
    // 定义一个字符串向量 result,用于存储所有可能的字母组合结果
    vector<string> result;
    
    // 定义一个字符串 path,用于记录当前组合的路径
    string path;

    // 递归函数 recursive,用于生成所有可能的字母组合
    void recursive(string digits, int index) {
        // 递归结束条件:当 path 的长度等于输入的数字串长度时,将当前组合添加到结果中
        if (path.size() == digits.size()) {
            result.push_back(path);
            return;
        }

        // 根据当前数字获取对应的字母字符串
        string s = v[digits[index] - '0'];

        // 遍历当前数字对应的每个字母
        for (int i = 0; i < s.size(); ++i) {
            // 将当前字母添加到 path
            path.push_back(s[i]);

            // 递归调用,下一个数字
            recursive(digits, index + 1);

            // 回溯:移除最后一个添加的字母,以便尝试下一个字母组合
            path.pop_back();
        }
    }

    // 主函数 letterCombinations,调用递归函数并返回结果
    vector<string> letterCombinations(string digits) {
        // 如果输入为空,返回空的结果集
        if (digits.size() == 0) return {};
        
        // 调用递归函数,开始生成字母组合
        recursive(digits, 0);

        // 返回所有生成的字母组合
        return result;
    }
};
 

2. 22. 括号生成

中等

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]

提示:

  • 1 <= n <= 8

class Solution {
public:
    vector<string> ans;  // 存储所有有效的括号组合
    string cur;          // 当前构建的括号组合字符串

    // 回溯函数
    void backtrack(string& cur, int open, int close, int n) {
        // 基线条件:当当前组合的长度等于 2*n 时,意味着已生成一个有效的括号组合
        if (cur.size() == 2 * n) {
            ans.push_back(cur);  // 将当前组合添加到结果中
            return;              // 返回,结束当前递归
        }

        // 1. 尝试添加左括号
        if (open < n) {  // 只有在左括号数量小于 n 时才能添加左括号
            cur.push_back('(');  // 将左括号添加到当前组合
            backtrack(cur, open + 1, close, n);  // 递归调用,更新 open 数量
            cur.pop_back();  // 回溯:移除最后一个字符,尝试其他组合
        }

        // 2. 尝试添加右括号
        if (close < open) {  // 只有在右括号数量小于左括号数量时才能添加右括号
            cur.push_back(')');  // 将右括号添加到当前组合
            backtrack(cur, open, close + 1, n);  // 递归调用,更新 close 数量
            cur.pop_back();  // 回溯:移除最后一个字符,尝试其他组合
        }
    }

    // 生成 n 对括号的主函数
    vector<string> generateParenthesis(int n) {
        backtrack(cur, 0, 0, n);  // 初始调用,open 和 close 都为 0
        return ans;  // 返回所有生成的有效括号组合
    }
};

 解释:

  • 条件 if (close < open) 确保右括号的数量不会超过左括号的数量,从而保持组合的有效性。

3. 39. 组合总和

中等

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7

输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5],target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

提示:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同
  • 1 <= target <= 40

class Solution {
public:
    // 用于存储所有符合条件的组合
    vector<vector<int>> result;
    // 用于存储当前的组合路径
    vector<int> path;

    // 递归+回溯函数
    // candidates: 数组中的可选数字
    // target: 目标和
    // sum: 当前路径的和
    // index: 当前选择开始的索引位置
    void backTracking(vector<int>& candidates, int target, int sum, int index) {
        // 如果当前和已经超过目标值,结束该路径
        if(sum > target){
            return;
        }

        // 如果找到一个符合条件的组合
        if(target == sum){
            result.push_back(path);  // 将当前路径加入结果集中
            return;
        }

        // 遍历候选数组,从 index 开始
        for(int i = index; i < candidates.size(); ++i) {
            // 将当前选择加入路径和 sum 中
            sum += candidates[i];
            path.push_back(candidates[i]);

            // 递归调用自身,传入i而不是index或index+1,以允许重复使用相同的元素
            backTracking(candidates, target, sum, i);

            // 回溯,撤销当前选择
            sum -= candidates[i];
            path.pop_back();
        }
    }

    // 主函数,用于调用回溯函数并返回结果
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backTracking(candidates, target, 0, 0); // 初始化 sum 为 0,从索引 0 开始
        return result;  // 返回符合条件的所有组合
    }
};

 解释:

关于for循环中int=index而不是i=0问题:

考虑一个具体的例子,假设我们有以下输入:

candidates = [2, 3, 6, 7] target = 7

1. 使用 index 控制

index 传入 0,第一次迭代将会是:

  • 第一个元素 2index = 0
    • 选择 2,接下来可以再次选择 2 或选择 3(因为 i0 开始)。你将得到 [2, 2, 3] 这种组合。
  • 第二个元素 3index = 1
    • 当递归调用回来后,当前元素 2 被弹出,index 继续向前推进。
    • 选择 3 时,i1 开始,因此可以选择 [3, 4],形成组合 [3, 4]

这个方法避免了相同元素在同一层级的选择,比如 2 被重复选择。

2. 使用 0 开始的情况

如果 for 循环从 0 开始:

  • 第一个元素 2i = 0
    • 选择 2,然后 backTracking(candidates, target, sum, 0) 再次递归调用,允许再次选择 2
  • 第二个元素 2 再次选择i = 0
    • 你将得到路径 [2, 2, 2],但也会通过选择 2、然后 2、然后 3 再生成组合。
造成的后果

通过从 0 开始,每一层递归都允许选择之前已经被选中的元素,导致生成的组合可能重复。例如 [2, 2, 3][2, 3, 2] 被认为是不同的组合,但实际上它们是相同的组合。

总结

  • index 变量的设计:允许算法在递归过程中控制哪些元素可以被选择,同时避免在同一递归层级重复选择已经在路径中的元素。
  • 0 开始的后果:如果每次递归都从 0 开始,可能会产生重复组合,并增加计算的复杂度。

4. 46. 全排列

中等

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

class Solution {
public:
    // 存储所有的排列结果
    vector<vector<int>> result;
    // 存储当前排列的路径
    vector<int> path;

    // 回溯函数
    void backTracking(vector<int>& nums, vector<bool>& used) {
        // 终止条件:当前路径的长度等于数组的长度,表示形成了一个完整排列
        if (nums.size() == path.size()) {
            result.push_back(path);  // 将当前排列加入结果集
            return;
        }

        // 遍历所有元素,尝试每种可能
        for (int i = 0; i < nums.size(); ++i) {
            // 如果该元素已经使用过,则跳过
            if (used[i]) continue;

            // 标记当前元素为已使用
            used[i] = true;
            path.push_back(nums[i]);  // 将元素加入当前排列路径
            
            // 递归调用自身,继续生成排列
            backTracking(nums, used);

            // 回溯,撤销当前选择,准备尝试其他排列
            path.pop_back();
            used[i] = false;
        }
    }

    // 主函数,返回所有的排列
    vector<vector<int>> permute(vector<int>& nums) {
        // 初始化标记数组,长度和 nums 一致,初始值为 false
        vector<bool> used(nums.size(), false);
        
        // 开始回溯
        backTracking(nums, used);
        return result;
    }
};

5. 78. 子集

中等

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同

class Solution {
public:
    vector<vector<int>> result; // 用于存储所有子集的结果
    vector<int> path; // 当前的子集路径

    void backtrack(vector<int>& nums, int index) {
        // 结束条件:当 index 等于数组大小时返回
        if (index == nums.size()) {
            return;
        }

        // 将当前子集加入结果中
        result.push_back(path);

        // 遍历剩余的元素,生成子集
        for (int i = index; i < nums.size(); ++i) {
            path.push_back(nums[i]); // 选择当前元素
            backtrack(nums, i + 1); // 递归调用,选择下一个元素
            path.pop_back(); // 回溯,撤销选择
        }
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        backtrack(nums, 0); // 从索引 0 开始
        return result; // 返回结果
    }
};

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

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

相关文章

快速遍历包含合并单元格的Word表格

Word中的合并表格如下&#xff0c;现在需要根据子类&#xff08;例如&#xff1a;果汁&#xff09;查找对应的品类&#xff0c;如果这是Excel表格&#xff0c;那么即使包含合并单元格&#xff0c;也很容易处理&#xff0c;但是使用Word VBA进行查找&#xff0c;就需要一些技巧。…

js 获取当前时间与前一个月时间

// 获取当前时间的毫秒数 var currentTimeMillis new Date().getTime();// 获取前一个月的Date对象 var dateLastMonth new Date(); dateLastMonth.setMonth(dateLastMonth.getMonth() - 1);// 获取前一个月的毫秒数 var timeMillisLastMonth dateLastMonth.getTime();conso…

C++之多态的深度剖析

目录 前言 1.多态的概念 2.多态的定义及实现 2.1多态的构成条件 2.1.1重要条件 2.1.2 虚函数 2.1.3 虚函数的重写/覆盖 2.1.4 选择题 2.1.5 虚函数其他知识 协变&#xff08;了解&#xff09; 析构函数的重写 override 和 final关键字 3. 重载&#xff0c;重写&…

【linux网络编程】| socket套接字 | 实现UDP协议聊天室

前言&#xff1a;本节内容将带友友们实现一个UDP协议的聊天室。 主要原理是客户端发送数据给服务端。 服务端将数据再转发给所有链接服务端的客户端。 所以&#xff0c; 我们主要就是要实现客户端以及服务端的逻辑代码。 那么&#xff0c; 接下来开始我们的学习吧。 ps:本节内容…

【PTA】4-2 树的同构【数据结构】

给定两棵树 T1​ 和 T2​。如果 T1​ 可以通过若干次左右孩子互换就变成 T2​&#xff0c;则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的&#xff0c;因为我们把其中一棵树的结点A、B、G的左右孩子互换后&#xff0c;就得到另外一棵树。而图2就不是同构的。 图一…

「Mac畅玩鸿蒙与硬件13」鸿蒙UI组件篇3 - TextInput 组件获取用户输入

在鸿蒙应用开发中,TextInput 组件用于接收用户输入,适用于文本、密码等多种输入类型。本文详细介绍鸿蒙 TextInput 组件的使用方法,包括输入限制、样式设置、事件监听及搜索框应用,帮助你灵活处理鸿蒙应用中的用户输入。 关键词 TextInput 组件用户输入输入限制事件监听搜索…

单元测试详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 为什么需要单元测试&#xff1f; 从产品角度而言&#xff0c;常规的功能测试、系统测试都是站在产品局部或全局功能进行测试&#xff0c;能够很好地与用户的需…

如何从PPT中导出600dpi的高清图

Step1. 修改PPT注册表 具体过程&#xff0c;参见如下链接&#xff1a;修改ppt注册表&#xff0c;导出高分辨率图片 Step2. 打开PPT&#xff0c;找到自己想要保存的图&#xff0c;选中图像&#xff0c;查看图像尺寸并记录 Step3. 重新新建一个PPT&#xff0c;并根据记录的图片…

「Mac畅玩鸿蒙与硬件7」鸿蒙开发环境配置篇7 - 使用命令行工具和本地模拟器管理项目

本篇将讲解在 macOS 上配置 HarmonyOS 开发环境的流程&#xff0c;聚焦 hvigorw 命令行工具的使用。我们将以创建 HelloWorld 项目为例&#xff0c;演示使用 hvigorw 进行项目构建、清理操作&#xff0c;并通过 DevEco Studio 的本地模拟器进行预览&#xff0c;帮助提升项目开发…

Linux基础—基础命令及相关知识5(ubuntu网络配置)

网络的配置方法 centos网络配置 centos的网卡位置 /etc/sysconfig/network-scripts/ifcfg-ens33(centos网卡文件) bootproto表示获得IP地址的方式是静态的还是动态 onboot表示启动系统时是否激活该网络接口 设置IP地址&#xff0c;子网掩码&#xff0c;网关&#xff0c;dns…

LabVIEW开发的控制阀监控与维护系统

LabVIEW开发一套自动测试软件&#xff0c;用于控制阀的实时监控、数据采集、维护管理以及报警通知。此系统的目标是通过便捷的操作界面、可靠的通信接口和高效的数据管理&#xff0c;为工厂设备管理提供全面的支持。 1. 项目需求 目标是实现一个控制阀管理系统&#xff0c;能够…

第7章 利用CSS和多媒体美化页面作业

2.用表格布局页面&#xff0c;利用CSS技术&#xff0c;及添加多媒体&#xff0c;制作并美化“心灵之音”页面。 浏览效果如下&#xff1a; 实例代码如下&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title>心灵…

Python中的数据可视化:Matplotlib基础与高级技巧

Python中的数据可视化&#xff1a;Matplotlib基础与高级技巧 数据可视化是数据分析和数据科学中不可或缺的一部分。通过图表&#xff0c;我们可以更直观地观察数据的分布和趋势。Matplotlib作为Python最基础、也是最广泛使用的绘图库之一&#xff0c;不仅支持多种常用图表&…

‘cmd‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。

报错描述&#xff1a; 我在使用python执行一个spark任务时&#xff0c;一直报如下错误&#xff0c;检查电脑上所有的环境变量后发现都配置正确&#xff0c;但还是一直报cmd 不是内部或外部命令&#xff0c;也不是可运行的程序这个错误&#xff0c;如果你也有这样的情况&#x…

【网络】传输层协议TCP

目录 四位首部长度 序号 捎带应答 标记位 超时重传机制 连接管理机制&#xff08;RST标记位&#xff09; 三次握手及四次挥手的原因 TCP的全称是传输控制协议&#xff08;Transmission Control Protocol&#xff09;&#xff0c;也就是说&#xff0c;对于放到TCP发送缓冲…

【优选算法篇】前缀之序,后缀之章:于数列深处邂逅算法的光与影

文章目录 C 前缀和详解&#xff1a;基础题解与思维分析前言第一章&#xff1a;前缀和基础应用1.1 一维前缀和模板题解法&#xff08;前缀和&#xff09;图解分析C代码实现易错点提示代码解读题目解析总结 1.2 二维前缀和模板题解法&#xff08;二维前缀和&#xff09;图解分析C…

采用STM32CubeMX和HAL库的定时器应用实例

目录 STM32的通用定时器配置流程 定时器应用的硬件设计 定时器应用的软件设计 1. 通过STM32CubeMX新建工程 通过STM32CubeMX新建工程的步骤如下&#xff1a; 2. 通过Keil MDK实现工程 通过Keil MDK实现工程的步骤如下&#xff1a; STM32的通用定时器配置流程 通用定时器…

开源一套基于若依的wms仓库管理系统,支持lodop和网页打印入库单、出库单的源码

大家好&#xff0c;我是一颗甜苞谷&#xff0c;今天分享一款基于若依的wms仓库管理系统&#xff0c;支持lodop和网页打印入库单、出库单的源码。 前言 在当今快速发展的商业环境中&#xff0c;库存管理对于企业来说至关重要。然而&#xff0c;许多企业仍然依赖于传统的、手动…

算法实现 - 快速排序(Quick Sort) - 理解版

文章目录 算法介绍算法分析核心思想三个版本运行过程挖坑法Hoare 原版前后指针法 算法稳定性和复杂度稳定性时间复杂度平均情况O(nlogn)最差情况O( n 2 n^2 n2) 空间复杂度 算法介绍 快速排序是一种高效的排序算法&#xff0c;由英国计算机科学家C. A. R. Hoare在1960年提出&a…

C语言指针的介绍

零.导言 在日常生活中&#xff0c;我们常常在外出时居住酒店&#xff0c;细心的你一定能发现酒店不同的房间上有着不同的门牌号&#xff0c;上面写着像308&#xff0c;512之类的数字。当你定了酒店之后&#xff0c;你就会拿到一个写有门牌号的钥匙&#xff0c;凭着钥匙就能进入…