【基础算法】回溯算法相关题目

news2024/11/24 18:29:02

系列综述:
💞目的:本系列是个人整理为了秋招算法的,整理期间苛求每个知识点,平衡理解简易度与深入程度。
🥰来源:材料主要源于代码随想录进行的,每个算法代码参考leetcode高赞回答和其他平台热门博客,其中也可能含有一些的个人思考。
🤭结语:如果有帮到你的地方,就点个赞关注一下呗,谢谢🎈🎄🌷!!!
🌈数据结构基础知识总结篇


文章目录

    • 一、回溯算法理论基础
      • 定义
    • 二、回溯算法基本题目
      • 77. 组合
      • 39. 组合总和
    • [39. 组合总和](https://leetcode.cn/problems/combination-sum/description/)
      • 40.组合总和II
      • 131. 分割回文串
      • 93. 复原 IP 地址
      • 1005. K 次取反后最大化的数组和
      • 135. 分发糖果
      • 406. 根据身高重建队列
      • 452. 用最少数量的箭引爆气球
      • 763. 划分字母区间
      • 435. 无重叠区间
      • 56. 合并区间
      • 738. 单调递增的数字
    • 参考博客


😊点此到文末惊喜↩︎


一、回溯算法理论基础

定义

  1. 回溯算法 = 穷举 + 剪枝
  2. 回溯算法解决的问题一般为npc问题,难以使用常规算法进行解决
    • 组合问题:N个数里面按一定规则找出k个数的集合
    • 切割问题:一个字符串按一定规则有几种切割方式
    • 子集问题:一个N个数的集合里有多少符合条件的子集
    • 排列问题:N个数按一定规则全排列,有几种排列方式
    • 棋盘问题:N皇后,解数独等等
  3. 组合是不强调元素顺序的,排列是强调元素顺序。
  4. 所有的回溯法解决的问题都可以抽象为树形结构
  5. 回溯基本结构
    • 根节点是总数据集合,树枝节点是可选数据集合
    • 叶子节点为根节点到叶子节点的路径的选择集合
      在这里插入图片描述
    // 合法性判断
    bool isValid(const type &data){
    	// type中数据项的合法性判断
    }
    
    // 回溯函数
    vector<vector<type> res;
    vector<type> path;
    void backtracking(vecotr<type> candidates, int startIndex) {
    	// 路径值判断
        if (符合条件isValid) {
            存放结果;
            return;
        }
    	// 延申和回撤路径时,可能涉及多个状态标记变量的改动
        for (int i = startIndex; i < candidates.size(); ++i) {
        	剪枝判断;
        	// 状态延申改动
            path.push_back(candidates[i]);// 向下延申
            backtracking(路径,选择列表); // 回溯
            // 状态回撤改动
            path.pop_back();// 回撤延申
        }
    }
    // 主函数
    vector<vector<int>> combine(vector<type>& candidates) {
        res.clear(); // 可以不写
        path.clear();// 可以不写
        backtracking(candidates, 0);
        return result;
    }
    

二、回溯算法基本题目

77. 组合

  1. 77. 组合
    • 组合中的元素不能重复
    // 函数式编程?
    vector<vector<int>> result; // 存放符合条件结果的集合
    vector<int> path; // 用来存放符合条件结果
    void backtracking(int n, int k, int startIndex) {
        // 递归结束条件:组合树的叶子节点的条件
        if (path.size() == k) {
            result.push_back(path);
            return;
        }
        // 回溯的递归:
        for (int i = startIndex; i <= n; i++) {
            path.push_back(i); // 处理节点 
            backtracking(n, k, i + 1); // 递归
            path.pop_back(); // 回溯,撤销处理的节点
        }
    }
    vector<vector<int>> combine(int n, int k) {
        result.clear(); // 可以不写
        path.clear();   // 可以不写
        backtracking(n, k, 1);
        return result;
    }
    

39. 组合总和

  1. 39. 组合总和

    vector<vector<int>> res;
    vector<int> path;
    void backtracking(vector<int> &candidates, int startIndex, int target, int sum){
    	// 结束条件
        if (sum > target) {
            return ;
        }
        if (sum == target) {
            res.push_back(path);
            return ;
        }
    	// 路径回溯
        for (int i = startIndex; i < candidates.size(); ++i) {
            sum += candidates[i];// 路径值累加
            path.push_back(candidates[i]);// 路径延申
            backtracking(candidates, i, target, sum);
            sum -= candidates[i];
            path.pop_back();
        }
        
    }
    
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        res.clear();
        path.clear();
        backtracking(candidates, 0, target, 0);
        return res;
    }
    

40.组合总和II

  1. 40.组合总和II
    • 集合(数组candidates)有重复元素,但还不能有重复的组合。
    • 同一个层不可重复选取两个相同的元素
    // 结果容器
    vector<vector<int>> result;
    vector<int> path;
    // 回溯函数
    void backtracking(vector<int>& candidates, int target, int sum, 
    	int startIndex, vector<bool>& used) {
    	// 
        if (sum == target) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            
            // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            // 要对同一树层使用过的元素进行跳过
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
                continue;
            }
            // 延申路径:改变状态机中路径相关变量,sum、path、used
            sum += candidates[i];
            path.push_back(candidates[i]);
            used[i] = true;
            // 回溯函数
            backtracking(candidates, target, sum, i + 1, used); 
            // 回缩路径
            used[i] = false;
            sum -= candidates[i];
            path.pop_back();
        }
    }
    
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size(), false);
        path.clear();
        result.clear();
        // 首先把给candidates排序,让其相同的元素都挨在一起。
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, 0, used);
        return result;
    }
    

131. 分割回文串

  1. 131. 分割回文串
    • 获取[startIndex,i]在s中的子串s.substr(startIndex, i - startIndex + 1)
    // 判断是否为回文字符串
    bool isPalindrome(const string& s, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            if (s[i] != s[j]) {
                return false;
            }
        }
        return true;
    }
    // 基本的回溯
    vector<vector<string>> result;
    vector<string> path; // 放已经回文的子串
    void backtracking (const string& s, int startIndex) {
        // 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
        if (startIndex >= s.size()) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i < s.size(); i++) {
        	// 剪枝与枝的延长
            if (isPalindrome(s, startIndex, i)) {   // 是回文子串
                // 获取[startIndex,i]在s中的子串
                string str = s.substr(startIndex, i - startIndex + 1);
                path.push_back(str);
            } else {  // 不是回文,跳过
                continue;
            }
            backtracking(s, i + 1); // 寻找i+1为起始位置的子串
            path.pop_back(); // 回溯过程,弹出本次已经填在的子串
        }
    }
    
    
    vector<vector<string>> partition(string s) {
        result.clear();
        path.clear();
        backtracking(s, 0);
        return result;
    }
    

93. 复原 IP 地址

  1. 93. 复原 IP 地址
    • str.insert(1,s);在原串下标为1的字符e前插入字符串s
    • str.erase(0);删除下标为0的字符
    vector<string> result;// 记录结果
    // startIndex: 搜索的起始位置,pointNum:添加逗点的数量
    void backtracking(string& s, int startIndex, int pointNum) {
        if (pointNum == 3) { // 逗点数量为3时,分隔结束
            // 判断第四段子字符串是否合法,如果合法就放进result中
            if (isValid(s, startIndex, s.size() - 1)) {
                result.push_back(s);
            }
            return;
        }
        
        for (int i = startIndex; i < s.size(); i++) {
            if (isValid(s, startIndex, i)) { // 判断 [startIndex,i] 这个区间的子串是否合法
                s.insert(s.begin() + i + 1 , '.');  // 在i的后面插入一个逗点
                pointNum++;
                backtracking(s, i + 2, pointNum);   // 插入逗点之后下一个子串的起始位置为i+2
                pointNum--;                         // 回溯
                s.erase(s.begin() + i + 1);         // 回溯删掉逗点
            } else break; // 不合法,直接结束本层循环
        }
    }
    // 判断字符串s在左闭又闭区间[start, end]所组成的数字是否合法
    bool isValid(const string& s, int start, int end) {
        if (start > end) {
            return false;
        }
        if (s[start] == '0' && start != end) { // 0开头的数字不合法
                return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
                return false;
            }
            num = num * 10 + (s[i] - '0');
            if (num > 255) { // 如果大于255了不合法
                return false;
            }
        }
        return true;
    }
    
    vector<string> restoreIpAddresses(string s) {
        result.clear();
        if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
        backtracking(s, 0, 0);
        return result;
    }
    

1005. K 次取反后最大化的数组和

  1. 1005. K 次取反后最大化的数组和
    • sort的使用:第三个参数为自定义的排序队则,在头文件#include
    • accumulate的使用:第三个参数为累加的初值,在头文件include
    static bool cmp(int a, int b) {
        return abs(a) > abs(b);// 绝对值的从大到小进行排序
    }
    int largestSumAfterKNegations(vector<int>& A, int K) {
    	// 将容器内的元素按照绝对值从大到小进行排序
        sort(A.begin(), A.end(), cmp); 
        // 在K>0的情况下,将负值按照绝对值从大到小依次取反
        for (int i = 0; i < A.size(); i++) { 
            if (A[i] < 0 && K > 0) {
                A[i] *= -1;
                K--;
            }
        }
        // 如果K为奇数,将最小的正数取反
        if (K % 2 == 1) 
        	A[A.size() - 1] *= -1; 
       	// 求和
        return accumulate(A.begin(),A.end(),0);
        // 第三个参数为累加的初值,在头文件include<numeric>
    }
    

135. 分发糖果

  1. 135. 分发糖果
    • 双向遍历进行贪心处理
    int candy(vector<int>& ratings) {
        vector<int> candyVec(ratings.size(), 1);
        // 从前向后
        for (int i = 1; i < ratings.size(); i++) {
            if (ratings[i] > ratings[i - 1]) 
                candyVec[i] = candyVec[i - 1] + 1;
        }
        // 从后向前
        for (int i = ratings.size() - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1] ) {
                candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);
            }
        }
        // 统计结果
        int result = 0;
        for (int i = 0; i < candyVec.size(); i++) result += candyVec[i];
        return result;
    }
    

406. 根据身高重建队列

  1. 406. 根据身高重建队列
    • 两个维度遍历进行贪心处理
    • 常用插入操作使用list进行处理
    • 感觉可以局部最优推出整体最优,而且想不到反例。就可以使用贪心算法。
    // 身高从大到小排(身高相同k小的站前面)
    static bool cmp(const vector<int>& a, const vector<int>& b) {
        if (a[0] == b[0]) // 相等的,数量小的在前
        	return a[1] < b[1];
        return a[0] > b[0];// 其他情况身高高的的在前
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort (people.begin(), people.end(), cmp);
        list<vector<int>> que; // list底层是链表实现,插入效率比vector高的多
        
        for (int i = 0; i < people.size(); i++) {// 按序插入
            int position = people[i][1]; // 插入到下标为position的位置
            std::list<vector<int>>::iterator it = que.begin();
            while (position--) { // 寻找在插入位置
                it++;
            }
            que.insert(it, people[i]);
        }
        return vector<vector<int>>(que.begin(), que.end());
    }
    

452. 用最少数量的箭引爆气球

  1. 452. 用最少数量的箭引爆气球
    • 贪心算法通常先进行排序最值处理
    // 基本比较函数的使用:1.const &形参 2. 形参为比较的两个数据元素 3. 返回值为两个形参的比较
    static bool cmp (const vector<int>& a, const vector<int>& b){
        if(a[0] == b[0])
            return a[1] < b[1];
        return a[0] < b[0];
    }
    int findMinArrowShots(vector<vector<int>>& points) {
        if(points.size() == 0)
            return 0;
        int count = 1;// 至少射一支箭
        sort(points.begin(), points.end(), cmp);
        for(int i = 1; i < points.size(); ++i){
            if(points[i-1][1] < points[i][0]){
                count++;             
            }else{// 记录最小下限
                points[i][1] = min(points[i - 1][1], points[i][1]);
            }
        }
        return count;
    }
    

763. 划分字母区间

  1. 763. 划分字母区间
    • 如果只有字母,可以使用数组进行哈希映射。 vector<int> alphabet = {27,0}
    vector<int> partitionLabels(string s) {
        // 统计每个字符的最远坐标
        unordered_map<char, int> umap;
        for(int i = 0; i < s.size(); ++i){
            umap[s[i]] = i;
        }
    	// 初始化
        vector<int> res;
        int left = 0;
        int right = 0;
        // 迭代
        for(int i = 0; i < s.size(); ++i){
            right = max(right, umap[s[i]]); // 找到字符出现的最远边界
            if (i == right) {
                res.push_back(right - left + 1);// 结果记录
                left = i + 1;
            }
        }
        return res;
        
    }
    

435. 无重叠区间

  1. 435. 无重叠区间
    • 能读就不写:求数量的尽量不要改变改变原来数组,减少写操作。
    static bool cmp (const vector<int>& a, const vector<int>& b) {
        return a[1] < b[1];
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        if (intervals.size() == 0) return 0;
        sort(intervals.begin(), intervals.end(), cmp);
        int count = 1; // 记录非交叉区间的个数
        int end = intervals[0][1]; // 记录区间分割点
        for (int i = 1; i < intervals.size(); i++) {
            if (end <= intervals[i][0]) {// 记录最小的右边界
                end = intervals[i][1];
                count++;
            }
        }
        return intervals.size() - count;
    }
    

56. 合并区间

  1. 56. 合并区间
    • 排序参数lambda表达式的使用
    • 遍历的容器尽量不用动,使用新的结果容器进行处理
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
    	// 健壮性检查
    	if (intervals.size() == 0) 
    		return result; // 区间集合为空直接返回
        vector<vector<int>> result;
        // 排序的参数使用了lambda表达式
        sort(intervals.begin(), intervals.end(), 
        	[](const vector<int>& a, const vector<int>& b){
        		return a[0] < b[0];
        	}
        );
        // 第一个区间就可以放进结果集里,后面如果重叠,在result上直接合并
        result.push_back(intervals[0]); 
    	// 合并区间,只更新右边界就好,
    	// 因为result.back()的左边界一定是最小值,因为我们按照左边界排序的
        for (int i = 1; i < intervals.size(); i++) {
            if (result.back()[1] >= intervals[i][0]) { // 发现重叠区间
                result.back()[1] = max(result.back()[1], intervals[i][1]); 
            } else {
                result.push_back(intervals[i]); // 区间不重叠 
            }
        }
        return result;
    }
    

738. 单调递增的数字

  1. 738. 单调递增的数字
    • 数字转换成字符串处理
    int monotoneIncreasingDigits(int N) {
        string strNum = to_string(N);
        // flag用来标记赋值9从哪里开始
        // 设置为这个默认值,为了防止第二个for循环在flag没有被赋值的情况下执行
        int flag = strNum.size();
        for (int i = strNum.size() - 1; i > 0; i--) {
            if (strNum[i - 1] > strNum[i] ) {
                flag = i;
                strNum[i - 1]--;
            }
        }
        for (int i = flag; i < strNum.size(); i++) {
            strNum[i] = '9';
        }
        return stoi(strNum);
    }
    

少年,我观你骨骼清奇,颖悟绝伦,必成人中龙凤。
不如点赞·收藏·关注一波


🚩点此跳转到首行↩︎

参考博客

  1. 代码随想录
  2. letcode

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android RecyclerView AsyncListUtil手动刷新fillData,kotlin

Android RecyclerView AsyncListUtil手动刷新fillData&#xff0c;kotlin implementation com.github.bumptech.glide:glide:4.15.1implementation androidx.constraintlayout:constraintlayout:1.1.3 import android.os.Bundle import android.util.Log import android.view.V…

百万连接实现02:使用epoll实现的服务器

使用的操作系统&#xff1a; t$ cat /proc/version Linux version 4.19.260 (lkmaoubuntu) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.12)) #1 SMP Thu Sep 29 14:19:07 CST 2022 文件句柄的限制 如果不修改连接测试&#xff0c;会报错 一个tcp连接就需要…

北京大学2015计算机学科夏令营上机考试

目录 A:整数的个数 B:过滤多余的空格 C:二维数组右上左下遍历 D 合影效果 E:Simple prefix compression【做不起】 F:To Europe! To Europe!【做不起】 G:The Game【做不起】 H:Falling Leaves A:整数的个数 #include<iostream> using namespace std; int main(…

VSCode 免安装及中文设置

前言&#xff1a;VSCode作为目前最强大的文本编辑器&#xff0c;通过内部的插件市场可满足各种开发需求。使用免安装版可以自定义插件安装位置等&#xff0c;而使用安装包安装只能通过修改快捷方式自定义&#xff0c;十分不方便。因此这里分享如何安装免安装版的VSCode。 下载…

ETL是什么?怎样更快的学习ETL?

ETL是英文Extract-Transform-Load的缩写&#xff0c;用来描述将数据从源端经过抽取(extract)、转换(transform)、加载(load)至目的端的过程&#xff0c;它能够对各种分布的、异构的源数据(如关系数据)进行抽取&#xff0c;按照预先设计的规则将不完整数据、重复数据以及错误数据…

滤波后点云的个数和之前相同,只是有的点云坐标是nan

进行点云的条件滤波&#xff0c;滤波前后点云的个数不变&#xff0c;只是被滤掉的点坐标显示为nan。代码片段如下&#xff1a; pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_cond(new pcl::ConditionAnd<pcl::PointXYZ>()); range_cond->addComparison(pcl::Fi…

李宏毅transformer讲解;B站内测“搜索AI助手”功能

&#x1f989; AI新闻 &#x1f680; B站内测“搜索AI助手”功能 摘要&#xff1a;据反馈&#xff0c;B站正在内测“搜索 AI 助手”功能。用户在搜索框内输入问句或在搜索词中添加“?”即可体验此新功能。截图显示&#xff0c;该功能会为用户的搜索提供一个生成的答案&#…

hcip作业二

实验要求&#xff1a; 要求&#xff1a;R1-R2-R3-R4-R5 RIP 100运行版本2&#xff1b;R6-R7 RIP 200 运行版本1 1.使用合理IP地址规划网络&#xff0c;各自创建环回接口 2.R1创建环回 172.16.1.1/24 172.16.2.1/24 172.16.3.1/24 3.要求R3使用R2访问R1环回 4.减少路由条目数量&…

盘点一个Python网络爬虫抓取股票代码问题(上篇)

点击上方“Python爬虫与数据挖掘”&#xff0c;进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 去来江口守空船&#xff0c;绕船月明江水寒。 大家好&#xff0c;我是皮皮。 一、前言 前几天在Python白银群【厚德载物】问了一个Python网络爬虫的问题…

新手入门深度学习 | 3-2:激活函数activation

一、什么是激活函数 生物神经网络启发了人工神经网络(ANN)的发展。但是,人工神经网络并非大脑运作的近似表示。不过在我们了解为什么在人工神经网络中使用激活函数之前,先了解生物神经网络与激活函数的相关性是很有用处的。 典型神经元的物理结构包括细胞体(cell body)、…

软件的兼容性测试确保良好稳定运行的用户体验

在数字化时代&#xff0c;各种软件应用的开发和推广越来越普遍。然而&#xff0c;由于不同的操作系统、不同的设备配置和不同的软件版本&#xff0c;软件的兼容性成为了一个重要的问题&#xff0c;可以说软件的兼容性测试确保良好稳定运行的用户体验。 首先&#xff0c;软件的兼…

MySQL Optimization Learning(三)

一、通过索引进行优化 数据结构 Data Structure Visualizations 数据可视化效果展示 Binary Search Tree 插入数据可视化效果展示 AVL Tree Red/Black Tree --MYISAM存储引擎数据和引用分开存储 DROP TABLE IF EXISTS t_test; CREATE TABLE t_test (id int(11) NOT NULL,…

websocket 发送的消息超过默认限制就会自动断开连接

springboot集成websocket需要三步&#xff1a; 添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.1.6.RELEASE</version></dependency>…

【GIT】Git常用命令学习

Git常用命令学习 说明&#xff1a;<>表示占位符的说明&#xff0c;[]表示可选&#xff0c;/表示“或” 仓库操作 初始化创建一个本地仓库 git init克隆远程仓库至本地 git clone <远程仓库地址> [仓库名称] #可以加上自定义仓库名称配置仓库 git config user.name…

Linux的动静态库

动静态库 1. 见一见动静态库2. 动静态库概念2.1 为什么要有动静态库2.2 定义 3. 写一写——库的设计角度打包成静态库打包成动态库 4. 用一用——使用者角度4.1 直接使用头文件和源文件&#xff08;直接给源代码&#xff09;4.2 得到头文件和源文件进过处理后形成的二进制文件.…

C++ - 20230703

一. 思维导图 二.练习 全局变量&#xff0c;int monster 10000;定义英雄类hero&#xff0c;受保护的属性string name&#xff0c;int hp,int attck&#xff1b;公有的无参构造&#xff0c;有参构造&#xff0c;虚成员函数 void Atk(){blood-0;}&#xff0c;法师类继承自英雄类…

SpringCloud:微服务技术

一、认识微服务&#xff1a; 首先&#xff0c;微服务架构不等于SpringCloud&#xff0c;微服务架构是一种经过良好架构设计的分布式架构方案&#xff0c; &#xff0c;它将应用构建成一系列按业务领域划分模块的&#xff0c;小的自治服务&#xff0c;并解决服务拆分所产生的各种…

css基础知识十五:如果要做优化,CSS提高性能的方法有哪些?

一、前言 每一个网页都离不开css&#xff0c;但是很多人又认为&#xff0c;css主要是用来完成页面布局的&#xff0c;像一些细节或者优化&#xff0c;就不需要怎么考虑&#xff0c;实际上这种想法是不正确的 作为页面渲染和内容展现的重要环节&#xff0c;css影响着用户对整个…

DL环境安装之GCC9,Python9与IDE连接远程环境:python notebook,解释器,C toolchain

文章目录 一.安装gcc91. 设置x86 centos7 yum源2. 编译安装 二、安装python3.91.前置依赖2. 编译安装3.建立软连接或环境变量 三、IDE连接远程环境1.IDE 远程notebook2.IDE 远程Python解释器3.远程toolchain &#xff08;后续可能有用&#xff09; 一.安装gcc9 系统自带的cc可…