【算法打卡day26(2026-03-18 周三)今日算法:「回溯算法」& 蓝桥杯真题(简单题型)】7个

- 第 193 篇 -
Date: 2026 - 03- 18 | 周三
Author: 郑龙浩(仟墨)
今日算法or技巧:回溯算法 & 蓝桥杯真题(简单题型)

文章目录

2026-03-18-算法刷题day26 - 回溯算法

本来昨晚想好了不刷回溯的题了,把重点放到基础题和其他算法上面,今天没忍住还是刷了

今天是第三天练习「回溯算法」,前两天对于回溯的题还是模模糊糊,今天可以做出简单的回溯了,但是依然需要模板 和 画图才能理解

但是有一点我依然掌握的不好,就是 一旦遇到「去重」的回溯,我就不会了将,就是可以写出不去重的版本,但是去重总是理解错误

后来我发现,我太注重于「递归」的所有过程了,我在写的时候,应该只关注「当前层的递归」,而不需要关注「递归的所有过程」,

否则,在大脑里面模拟如何递归嵌套,再如何从最底层一层层的return上去然后从某个节点再往下递归下去

这种模拟,如果简单还可以,那种复杂的,我的大脑很难模拟出来,然后就非常的难受

其实之前做普通递归和dfs的时候已经知道了这个抽象的理解方法,但是做回溯的时候我太想搞明白回溯的具体过程了,这对于目前的我来说,还是非常困难的,所以还是先不要理解细节了,抽象的理解就可以了

之前其实思考过这个问题,不要太关注与这个递归的所有过程,应该抽象的相信该递归函数已经实现了,也就是在函数中嵌套的使用递归的时候,应该赋予递归意义,比如

当前的这个函数已经可以实现某个功能了,相信该函数可以处理好后面的所有事情,这样抽象的去理解,可以节省很多大脑的使用空间,反而理解明白了

文章目录

1-力扣131-分割回文串

难度:中等

算法:回溯

【题目】

给你一个字符串 s,请你将 s 分割成一些 子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

示例 1:

**输入:**s = "aab"
输出: [["a","a","b"],["aa","b"]]

示例 2:

输入: s = "a"
输出: [["a"]]

提示:

  • 1 <= s.length <= 16
  • s 仅由小写英文字母组成

【思路】

注意:是 分割 出m个回文字符串,而不是 取出 m 个回文字符串

我刚开始理解错了,我以为取出所有的回文字符串

求的是所有的回文子串

所以,就要不断的去分割字符串

递归就是不断的拆分字符串

  • 递归的startIndex表示的是拆分的字符串的 头下标(left)
  • 递归中的for遍历的是拆分的字符串的 尾下标(right)
  • 然后每次拆分都进行一次判断,如果拆分的[left, right]是回文串,就插入结果集
  • 题目中有个地方我差点忽视了,就是「分割」,而非「取出」

【代码】

cpp 复制代码
/* 2026-03-18-算法打卡day26
 * 1-力扣131-分割回文串
 * Author:郑龙浩
 * Date:2026-03-18
 * 算法/技巧:回溯算法 & 递归
 * 用时:30min
 */

#include "bits/stdc++.h"
using namespace std;

class Solution {
public:
    vector <vector <string>> results;
    vector <string> path;
    vector<vector<string>> partition(string s) {
        backtracking(s, 0);
        return results;
    }

    // left是分割的右侧字符串的起始位置
    void backtracking(string s, int left) {
        if (left == s.size()) {
            results.push_back(path);
            return;
        }
        for (int right = left; right < s.size(); right++) {
            string s2 = s.substr(left, right - left + 1);
            if (IS(s2)) {
                path.push_back(s2);
                backtracking(s, right + 1); // 向下寻找下一个字符串
                path.pop_back();
            } 
        }
    }

    // 判断回文
    bool IS(string s) {
        int len = s.size();
        for (int i = 0, j = len - 1; i < j; i++, j--) {
            if (s[i] != s[j]) return false;
        }
        return true;
    }
};

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); 
    cout.tie(0);
    return 0;
}

2-力扣93-复原IP地址

【题目】

有效 IP 地址 正好由四个整数(每个整数位于 0255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

  • 例如:"0.1.2.201""192.168.1.1"有效 IP 地址,但是 "0.011.255.245""192.168.1.312""192.168@1.1"无效 IP 地址。

给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址 ,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

示例 1:

**输入:**s = "25525511135"
输出: ["255.255.11.135","255.255.111.35"]

示例 2:

输入: s = "0000"
输出: ["0.0.0.0"]

示例 3:

输入: s = "101023"
输出: ["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]

提示:

  • 1 <= s.length <= 20
  • s 仅由数字组成

【思路】

核心思想:回溯枚举所有分割方式,验证每段字符的合法性,如果合法,就加入

剪枝思路:如果剩余字符串长度 > 还需段数 * 3剩余字符串长度 < 还需段数,就不需要再找后面合法字符串了

说人话就是:因为每一段要求是,长度>=1且<=3,所以每段最长为3,最短为1,剩余字符如果不够段数也不行,如果太多了,也不行

然后写一个IS函数用于判断截取的字符串是否合法

想象一个「树」结构

递归用于:定位每段截取的字符串的 头

for用于: 定位每段截取的字符串的 尾

这样就好理解一些

这道题算是模模糊糊做出来的吧,不能保证完全明白,只能希望比赛的时候可以根据些许分析 + 模板 + 题感 来套出来这种回溯吧,能对部分分就行

我第一版的代码是有问题的,后来让AI给我改了改才对,只能说模板上面没用错,大部分代码没错,错的是那种边界或者某些关键的点,思维还是不够活跃

【代码】

cpp 复制代码
/* 2026-03-18-算法打卡day26
 * 2-力扣93-复原IP地址-回溯算法
 * Author:郑龙浩
 * Date:2026-03-18
 * 算法/技巧:回溯算法 & 递归
 * 用时:
 */

#include "bits/stdc++.h"
using namespace std;

class Solution {
public:
    vector <string> results;
    string path;
    vector<string> restoreIpAddresses(string s) {
        backtracking(s, 0, 0);
        return results;
    }
    // left 表示截取的字符串的头
    // pointCnt 表示点的数量
    void backtracking(string s, int left, int pointCnt) {
        // 终止条件:到达字符串末尾 && 已经有了四个点(相当于有了四个字符串)
        // 说人话就是:已经将字符串分为了四个部分了
        if (left == s.size() && pointCnt == 4) { 
            // 先去掉path最后的'.'
            path.pop_back();
            results.push_back(path);
            // 要恢复最后的点,否则返回上一层时的数据就是出错的
            path.push_back('.');
            return;
        }

        // 剪枝:如果剩余字符不能满足长度,直接return
        int Cnt = 4 - pointCnt; // 还需要几段
        int Cnt2 = s.substr(left).size(); // s剩余的字符数
        //(因为每段最多3个字符,最少1个字符)
        // 如果剩余字符数,不够那几段,直接return || 字符数量 > 还需段数 * 3
        if (Cnt2 < Cnt || Cnt2 > Cnt * 3) return; 

        for (int right = left; right < s.size(); right++) {
            string s2 = s.substr(left, right - left + 1);
            if (IS(s2)) {
                int originalSize = path.size(); // 还没加入下一个字符串时的path长度
                path.append(s2 + '.');
                backtracking(s, right + 1, pointCnt + 1);
                path.erase(originalSize); // 删除刚才加入的字符-回溯
            } else break; // 如果当前段无效,后面也不用看了,也绝对无效
        }
    }
    // 判断截取的字符串是否是个有效整数
    bool IS(string s) {
        int len = s.size();
        if (len == 1 && s[0] == '0') return true; // 如果长度1,字符为0,则是符合条件的
        if (len > 1 && s[0] == '0') return false;// 如果长度>1,且s[0]为0,有前导0,则必须是false
        
        int sum = 0;
        // 判断是否全是数字
        for (char ch : s) {
            if (ch < '0' || ch > '9') return false;
            sum = sum* 10 + (ch - '0');
        }
        // 判断加起来是否>255
        if (sum > 255) return false;
        return true;
    }
};
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); 
    cout.tie(0);
    return 0;
}

3-力扣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 中的所有元素 互不相同

【思路】

就是套个模板,唯一不同的就是,这次存储 「子集」的时候,要从if条件外存储

也就是,存储的不只是最终走到size的子集,中间的变换过程的子集也要算在内

插入子集在条件上面,这样能保证收集到所有组合,包括:

  1. 空子集(第一次进入递归时path为空)

  2. 中间状态的子集(每个递归层次的path)就是还没选完的组合

    • 比如选了 [1] 还想继续选,但先记录下来
    • 比如选了 [1,2] 还想继续选,但先记录下来
    • 这些都是"半成品",但也是合法的子集
  3. 完整的子集(递归到底层时自动包含)

    • 比如 [1,2,3] 三个都选了
    • 比如 [1,3] 跳过了2
    • 这些都"成品"

【代码】

cpp 复制代码
/* 2026-03-18-算法打卡day26
 * 3-力扣78子集-回溯算法
 * Author:郑龙浩
 * Date:2026-03-18
 * 算法/技巧:回溯算法 & 递归
 * 用时:
 */

#include "bits/stdc++.h"
using namespace std;

class Solution {
public:
    vector <vector <int>> results;
    vector <int> path;
    vector<vector<int>> subsets(vector<int>& nums) {
        backtracking(nums, 0);
        return results;
    }
    void backtracking(vector <int>& nums, int startIndex) {
        /* 正确写法:插入组合时,要写在条件上面
        // 这样能保证收集到所有组合,包括:
            // 1. 空子集(第一次进入递归时path为空)
            // 2. 中间状态的子集(每个递归层次的path)就是还没选完的组合
                - 比如选了 [1]还想继续选,但先记录下来
                - 比如选了 [1,2]还想继续选,但先记录下来
                - 这些都是"半成品",但也是合法的子集
            // 3. 完整的子集(递归到底层时自动包含)
                - 比如 [1,2,3]三个都选了
                - 比如 [1,3]跳过了2
                - 这些都是"成品"
        */
        results.push_back(path); // 这样就不会漏掉 2 中间状态的子集
        if (startIndex == nums.size()) {
            return;
        }
/*  错误写法,我第一次这样写的,导致我无法取出比如,所有的以单个数字为一个组合的这种结果出来
        if (startIndex == nums.size()) {
            results.push_back(path);
            return;
        }
*/
        for (int cur = startIndex; cur < nums.size(); cur++) {
            path.push_back(nums[cur]);
            backtracking(nums, cur + 1);
            path.pop_back();
        }
    }
};
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); 
    cout.tie(0);
    return 0;
}

4-力扣92-子集II

【题目】

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的 子集(幂集)。

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

示例 1:

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

示例 2:

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

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10

【思路】

正确理解for

  • 第一句话 (操作层面):
    for循环遍历当前可选的元素,每个元素都会被加入到当前路径中
  • 第二句话 (结果层面):
    每次加入新元素后形成的新路径 ,都会成为一个新的递归分支的起点
    【AI生成】

这两句话说的是递归过程中同一个操作的两个侧面,不矛盾:

  1. 从操作执行的角度看for循环中的每个i都是在当前组合的基础上添加一个新元素

  2. 从结果生成的角度看 :每次添加新元素后形成的新组合 ,就是接下来要探索的新分支的起点

用具体的递归层级来说明:

假设 nums = [1,2,3,4],我们正在生成所有子集。

第1层递归(初始状态):

  • path = []startIndex = 0

  • for循环:i = 0,1,2,3

操作角度(第一句话):

  • i=0:将1加入当前空组合[]→ 得到[1]

  • i=1:将2加入当前空组合[]→ 得到[2]

  • ...

结果角度(第二句话):

  • 选择1→ 开启以[1]为"头"的新分支

  • 选择2→ 开启以[2]为"头"的新分支

  • ...

进入[1]分支(第2层递归):

  • path = [1]startIndex = 1

  • for循环:i = 1,2,3

操作角度

  • i=1:将2加入当前组合[1]→ 得到[1,2]

  • i=2:将3加入当前组合[1]→ 得到[1,3]

  • ...

结果角度

  • 选择2→ 开启以[1,2]为"头"的新分支

  • 选择3→ 开启以[1,3]为"头"的新分支

  • ...

【代码】

cpp 复制代码
/* 2026-03-18-算法打卡day26
 * 4-力扣90-子集II
 * Author:郑龙浩
 * Date:2026-03-18
 * 算法/技巧:回溯算法 & 递归
 * 这道题和力扣78子集类似,但是多了个内容,就是nums中是存在重复的元素的,所以必须要去重的操作
 * 用时:
 */

#include "bits/stdc++.h"
using namespace std;

class Solution {
public:
    vector <vector <int>> results;
    vector <int> path;
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector <bool> used(nums.size(), false); // 记录使用过的元素
        backtracking(nums, 0);
        return results;
    }
    void backtracking(vector <int>& nums, int startIndex) {
        results.push_back(path);
        if (startIndex == nums.size()) return; // 如果子集的开头到了末尾后面,也要return

        // 关键理解:for循环的作用不是找到第二个可以加入path的元素,而是找到第二个组合的头元素
        // 之前错误的将for的作用理解为了找到第二个可以加入path的元素
        // 所以想要跳过重复的组合的话,直接不将重复的元素作为头就可以了
        for (int cur = startIndex; cur < nums.size(); cur++) {
            // 如果 如果 当前元素不是头元素 && 当前元素之前使用过,就continue
            // 说人话就是,不将第二个相同的元素最为后面组合的头,因为第一次遇到这个元素的时候,就已经计算出来了所有的组合
            if (cur > startIndex && nums[cur] == nums[cur - 1]) {
                continue;
            }
            path.push_back(nums[cur]);
            backtracking(nums, cur + 1);
            path.pop_back();
        }
    }
};
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); 
    cout.tie(0);
    Solution sol;
    vector <int> nums = {1, 2, 2};
    auto ans = sol.subsetsWithDup(nums);
    for (auto i : ans) {
        for (auto j : i) cout << j << ' ';
        cout << '\n';
    }
    return 0;
}

2026-03-18-算法刷题day26 - 蓝桥真题

做回溯做的我头大,换个脑子,明天继续做算法题吧,等会要回宿舍了,剩下的时间做一些蓝桥杯的真题吧,从简单的做起

标签选择:蓝桥杯 省赛

排序:按照难度排序

5-蓝桥云课19701-穿越时空之门

问题描述

随着 20242024 年的钟声回荡,传说中的时空之门再次敞开。这扇门是一条神秘的通道,它连接着二进制和四进制两个不同的数码领域,等待着勇者们的探索。

在二进制的领域里,勇者的力量被转换成了力量数值的二进制表示中各数位之和。

在四进制的领域里,力量的转换规则相似,变成了力量数值的四进制表示中各数位之和。

穿越这扇时空之门的条件是严苛的:当且仅当勇者在二进制领域的力量等同于四进制领域的力量时,他才能够成功地穿越。

国王选定了小蓝作为领路人,带领着力量值从 11 到 20242024 的勇者们踏上了这段探索未知的旅程。作为小蓝的助手,你的任务是帮助小蓝计算出,在这 20242024 位勇者中,有多少人符合穿越时空之门的条件。

答案提交

这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【思路】

没什么技巧,纯模拟过程即可

答案:63

【代码】

cpp 复制代码
/* 2026-03-18-算法打卡day26
 * 5-蓝桥云课19701-穿越时空之门
 * Author:郑龙浩
 * Date:2026-03-18
 * 题型:填空(只有输出没有输入)
 * 用时:4min
 */

#include "bits/stdc++.h"
using namespace std;
typedef long long ll;

ll twoSum(ll num) {
    ll result = 0;
    while (num) {
        result += num % 2;
        num /= 2;
    }
    return result;
}
ll fourSum(ll num) {
    ll result = 0;
    while (num) {
        result += num % 4;
        num /= 4;
    }
    return result;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    ll cnt = 0;
    for (int i = 1; i <= 2024; i++) {
        if (twoSum(i) == fourSum(i)) cnt++;
    }
    cout << cnt;
    return 0;
}

// 63

6-蓝桥云课19695-握手问题

问题描述

小蓝组织了一场算法交流会议,总共有 5050 人参加了本次会议。在会议上,大家进行了握手交流。按照惯例他们每个人都要与除自己以外的其他所有人进行一次握手 (且仅有一次)。但有 77 个人,这 77 人彼此之间没有进行握手 (但这 77 人与除这 77 人以外的所有人进行了握手)。请问这些人之间一共进行了多少次握手?

注意 AA 和 BB 握手的同时也意味着 BB 和 AA 握手了,所以算作是一次握手。

答案提交

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【思路】

这就是个组合的题,纯数学题罢了

这道题的内容可以抽象:

  • 总人数:50 人。
  • 有 7 个人,他们两两之间不组成一组(即彼此不握手)
  • 但这 7 人与其他 43 人 中的每个人都恰好组成一组一次(即互相握手一次)
  • 其余 43 人两两之间正常组成一组一次。

总握手数 = C(43, 2) + C(7, 1) × C(43, 1)

然后模拟计算就可以了

答案 1204

【代码】

cpp 复制代码
/* 2026-03-18-算法打卡day26
 * 6-蓝桥云课19695-握手问题
 * Author:郑龙浩
 * Date:2026-03-18
 * 题型:填空(只有输出没有输入)
 * 用时:4min
 */

#include "bits/stdc++.h"
using namespace std;
typedef long long ll;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cout << 43 * 42 / 2 + 7 * 43;
    return 0;
}

// 1204

7-蓝桥云课3533-棋盘

问题描述

小蓝拥有 n×nn×n 大小的棋盘,一开始棋盘上全都是白子。小蓝进行了 mm 次操作,每次操作会将棋盘上某个范围内的所有棋子的颜色取反(也就是白色棋子变为黑色,黑色棋子变为白色)。请输出所有操作做完后棋盘上每个棋子的颜色。

输入格式

输入的第一行包含两个整数 nn,mm,用一个空格分隔,表示棋盘大小与操作数。

接下来 mm 行每行包含四个整数 x1x1​,y1y1​,x2x2​,y2y2​,相邻整数之间使用一个空格分隔,表示将在 x1x1​ 至 x2x2​ 行和 y1y1​ 至 y2y2​ 列中的棋子颜色取反。

输出格式

输出 nn 行,每行 nn 个 00 或 11 表示该位置棋子的颜色。如果是白色则输出 00,否则输出 11。

样例输入

复制代码
3 3
1 1 2 2
2 2 3 3
1 1 3 3

样例输出

复制代码
001
010
100

评测用例规模与约定

对于 3030% 的评测用例,n,m≤500n,m≤500 ;

对于所有评测用例,1≤n,m≤20001≤n,m≤2000,1≤x1≤x2≤n1≤x1​≤x2​≤n,1≤y1≤y2≤m1≤y1​≤y2​≤m。

【思路】

题目的歧义

题目中的 「接下来 mm 行每行包含四个整数 x1x1​,y1y1​,x2x2​,y2y2​,相邻整数之间使用一个空格分隔,表示将在 x1x1​ 至 x2x2​ 行和 y1y1​ 至 y2y2​ 列中的棋子颜色取反。」

我感觉描述的有歧义,让我产生了两种理解:

  • 1 将 x1 ~ x2 || y1 ~ y2 的矩阵颜色取反 --> 并集
  • 2 将 x1 ~ x2 && y1 ~ y2 的矩阵颜色取反 --> 交集
    看了示例后,才知道是交集,刚开始没看示例就去写代码了,导致出错了
解题思路

这就是个普通的「二维差分」的题,直接用基础算法

【代码】

cpp 复制代码
/*
 * 7-蓝桥云课3533-棋盘 (修正版)
 * 算法:二维差分 + 二维前缀和
 * Author:郑龙浩
 * Date:2026-03-18
 */

#include "bits/stdc++.h"
using namespace std;
typedef long long ll;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);

    int n, m;
    cin >> n >> m;

    // 创建差分数组,大小为 (n+2)*(n+2),多一圈边界,方便从1开始索引
    vector<vector<int>> diff(n + 2, vector<int>(n + 2, 0));

    // 读取m个操作并更新差分数组
    for (int k = 0; k < m; ++k) {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        
        // 标准的二维差分区间更新操作
        // 注意:此处是直接+1和-1,不进行取模运算
        diff[x1][y1] += 1;
        diff[x1][y2 + 1] -= 1;
        diff[x2 + 1][y1] -= 1;
        diff[x2 + 1][y2 + 1] += 1;
    }

    // 通过计算二维前缀和,得到每个位置 (i, j) 被操作的次数
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1];
        }
    }

    // 输出结果,每个位置的操作次数对2取模即为最终状态
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            // 输出对2取模的结果,并确保是0或1
            cout << diff[i][j] % 2;
        }
        cout << '\n';
    }
    return 0;
}
相关推荐
C蔡博士2 小时前
计算复杂性:P、NP、NP-hard、NP-complete 一篇通关
算法·计算理论·np问题·计算复杂性
add45a2 小时前
C++与自动驾驶系统
开发语言·c++·算法
TsukasaNZ2 小时前
C++中的命令模式
开发语言·c++·算法
superkcl20222 小时前
指针常量有什么用呢?
开发语言·c++·算法
华清远见成都中心2 小时前
嵌入式春招笔试高频算法题(附解题思路)
算法
像污秽一样2 小时前
算法设计与分析-习题9.1
数据结构·算法·dfs·dp·贪婪
無限進步D2 小时前
差分算法 cpp
c++·算法·蓝桥杯·竞赛
星空露珠3 小时前
迷你世界UGC3.0脚本Wiki生物模块管理接口 Monster
开发语言·数据结构·算法·游戏·lua
星空露珠3 小时前
迷你世界UGC3.0脚本Wiki世界模块管理接口 World
开发语言·数据库·算法·游戏·lua