017搜索之深度优先搜索——算法备赛

article/2025/8/12 20:52:22

深度优先搜索

如果说广度优先搜索是逐层扩散,那深度优先搜索就是一条道走到黑。
深度优先遍历是用递归实现的,预定一条顺序规则(如上下左右顺序) ,一直往第一个方向搜索直到走到尽头或不满足要求后返回上一个叉路口按第二个方向继续搜索,以此类推,直到所有节点都遍历到。

简单回溯

N皇后

问题描述:

设计一种算法,打印 N 皇后在 N × N 棋盘上的各种摆法,其中每个皇后都不同行、不同列,也不在对角线上。

这里的“对角线”指的是所有的对角线,不只是平分整个棋盘的那两条对角线。

原题链接

思路分析

N皇后问题是dfs的经典问题。

对于每一行,皇后都有N(N列)种放法,对于每一行遍历每一种位置,如果它与前面某一行的皇后处在同一列或同一对角线那就不能摆放在该位置;否则可以摆放在该位置,进行下一行的摆放。

vector<int>x;  //存储第i行皇后所在列
vector<vector<string>>tar;
vector<string>tr;
int sum=0,s;
bool check(int k){for(int i=1;i<k;i++){if(x[k]==x[i]) return false;  //判断该行的皇后是否与前面的皇后在同一列if(abs(x[k]-x[i])==k-i) return false;  //判断该行的皇后是否与前面的皇后在同一对角线}return true;
}
void DFS(int t){if(t>s) {  //当m>s时,该方案满足条件。sum++;  //记录方案数。tar.push_back(tr);}else{for(int i=1;i<=s;i++){x[t]=i;tr[t-1]="";tr[t-1].insert(0,s,'.');tr[t-1][i-1]='Q';  //修改该行状态if(check(t)) DFS(t+1);  //符合条件 开始下一行的摆放。 不合条件则摆在下一列。}}
}
vector<vector<string>> solveNQueens(int n) {s=n;x.resize(n+1);tr.resize(n);DFS(1);  //从第一行开始搜索return tar;
}

路径之迷

蓝桥杯2016年国赛题

描述:
在这里插入图片描述
原题链接

代码

#include <bits/stdc++.h>
using namespace std;int dx[4] = { 0,1,0,-1 };
int dy[4] = { 1,0,-1,0 };
int n;
vector<int>rowCnt, colCnt;
bool allFlat = false;
vector<vector<bool>>vis;
vector<int>tar;bool checkEnd(int row, int col) {if (row != n - 1 || col != n - 1) return false;for (int i = 0; i < n; i++) {if (rowCnt[i]) return false;  //有一个不为0,返回false}for (int i = 0; i < n; i++) {if (colCnt[i]) return false;  //有一个不为0,返回false}allFlat = true;return true;
}bool check(int row, int col) {if (row < 0 || row >= n || col < 0 || col >= n|| vis[row][col]) return false;  //越界或走到已走过的节点if (rowCnt[row] <= 0 || colCnt[col] <= 0) return false;  //箭用完了return true;
}void dfs(int row, int col) {if (checkEnd(row, col) || allFlat) return;  //结束for (int i = 0; i < 4; i++) {int x = row + dx[i];int y = col + dy[i];if (check(x,y)) {vis[x][y] = true;rowCnt[x]--, colCnt[y]--;tar.push_back(x * n + y);  //第x行,第y列的节点编号为 x * n + ydfs(x, y);if (allFlat) return;  //allFlat为true,不用继续执行了vis[x][y] = false;  //回溯,还原现场rowCnt[x]++, colCnt[y]++;tar.pop_back();}}
}
int main()
{cin >> n;rowCnt = vector<int>(n);colCnt = vector<int>(n);vis = vector<vector<bool>>(n, vector<bool>(n));for (int i = 0; i < n; i++)cin >> colCnt[i];for (int i = 0; i < n; i++)cin >> rowCnt[i];vis[0][0] = 1;colCnt[0]--;rowCnt[0]--;tar.push_back(0);dfs(0, 0);for (auto i : tar) {cout << i << " ";}return 0;
}

简单正则问题

蓝桥杯2017年省赛题

问题描述

考虑一种简单的正则表达式:只由 x,(,),| 组成的正则表达式。求这个正则表达式能接受的最长字符串的长度?

((xx|xxx)x|(x|xx))xx 能接受的最长字符串是xxxxxx,长度是6.

原题链接

思路分析

  1. ()规定了运算的优先级最高,|代表长度取max,x代表一个字符
  2. 根据嵌套规则,设计dfs
  3. 自底向上画出问题的二叉树,以((xx|xxx)x|(x|xx))xx 为例

在这里插入图片描述

代码

#include<bits/stdc++.h>using namespace std;string s; 
int k = 0;int dfs()
{int res = 0; //统计当前层可以容纳多少xwhile(k < s.size()){if(s[k] == '('){k++;  //跳过左括号res += dfs();  //遇到括号,把括号中的值求出,再与res做+运算 k++; //跳过右括号}else if(s[k] == '|'){k++; //跳过 或 运算res = max(res, dfs()); //遇到或运算,把|右边的求出,再与res做max运算}else if(s[k] == ')') break; else{k++; res++;  //遇到单个x,res直接加1}}return res;
}int main()
{cin >> s;cout << dfs() << endl;return 0;
}

记忆化搜索

掷骰子等于目标数的方法数

问题描述

这里有 n 个一样的骰子,每个骰子都不一样,每个骰子上都有 k 个面,分别标号为 1k

给定三个整数 nktarget,请返回投掷骰子的所有可能得到的结果中(总共有 k^n 种方式),使得骰子面朝上的数字总和等于 target的结果数。

由于答案可能很大,你需要对 109 + 7 取模

原题链接

思路分析

枚举第n个筛子的点数为x,那么这种情况的结果数就是前 n-1个筛子点数和为target-x的结果数。

那可以很自然地想到定义dp[i][j]表示前i个筛子点数和为j的结果数,dp[i][j]=sum(dp[i-1][j-x]) , x为枚举的第i个筛子的点数

动态规划的过程可以使用直观一点的记忆化搜索

代码

int numRollsToTarget(int n, int k, int target) {int mod=1e9+7;vector<vector<int>>dp(n+1,vector<int>(target+1));auto dfs=[&](auto dfs,int st,int sum)->int{if(dp[st][sum]) return dp[st][sum];if(st==n){if(sum<=k){dp[n][sum]=1;return 1;}}int l=max(1,sum-(n-st)*k);int r=min(k,sum-(n-st));for(int i=l;i<=r;i++){dp[st][sum]=(dp[st][sum]+dfs(dfs,st+1,sum-i))%mod;}return dp[st][sum];};return dfs(dfs,1,target);
}

统计满足逆序对数量条件的排列数量

给你一个整数 n 和一个二维数组 requirements ,其中 requirements[i] = [endi, cnti] 表示这个要求中的末尾下标和 逆序对 的数目。

整数数组 nums 中一个下标对 (i, j) 如果满足以下条件,那么它们被称为一个 逆序对

  • i < jnums[i] > nums[j]

请你返回 [0, 1, 2, ..., n - 1] 的 排列 perm 的数目,满足对 所有requirements[i] 都满足 perm[0..endi] 中恰好有 cnti 个逆序对。

由于答案可能会很大,将它对 109 + 7 取余 后返回。

思路分析

首先将问题分解为子问题。考虑示例 {n = 3, requirements = [[2,2],[0,0]] },整个排列 [0,2] 恰好要有 2 个逆序对。分情况进行讨论:

  • 末尾元素为 0。由于 0 是最小元素,前面的两个元素,每个元素都会与 0 构成一对逆序对。此时,[0,1] 还需要贡献 0 对逆序对。
  • 末尾元素为 1。前面的两个元素中,2 会与 1 构成一对逆序对。此时,[0,1] 还需要贡献 1 对逆序对。
  • 末尾元素为 2。前面的两个元素中,任何元素都不能和 2 构成逆序对。此时,[0,1] 还需要贡献 2 对逆序对。

定义函数 dfs(end,cnt),用来计算排列逆序对为 cnt 且满足 requirements 的排列 perm[0…end] 的个数。代入示例 ,我们可以得到 dfs(2,2)=dfs(1,0)+dfs(1,1)+dfs(1,2) 的递推公式。

更一般地,计算dfs(end,cnt),我们可以得到以下递推公式:

  • 如果 [0,end−1]requirements 有要求逆序对数量,且对应的逆序对要求数量为 r,那么我们在计算 dfs(end,cnt)时,因为[0,end-1]的逆序对个数为r,前面的元素需要与最后一个元素贡献 cnt−r 个逆序对,因为排列的每个元素都不一样,最后一个元素的取值要么不存在,要么唯一确定。这个逆序对的个数满足 0≤cnt−r≤end。因此,当满足 r≤cnt≤end+r 时,dfs(end,cnt)=dfs(end−1,r);否则,dfs(end,cnt)=0
  • 如果 end−1 不在 requirements 有要求。那么我们遍历末尾元素所有的可能性。遍历的范围为 0∼min(end,cnt),得到递推公式 dfs(end,cnt)=∑ i=0,min(end,cnt) dfs(end−1,cnt−i)

注意需要对 dfs 应用记忆化搜索,定义数组memomeno[i][j]存储dfs(i,j)的计算结果,这样每个状态最多被计算一次,降低时间复杂度。

最后返回 dfs(n−1,reqMap[n−1]) 即可,其中 reqMap 是将 requirements 转化成的键值对,键为 endi,值为 cnti

代码

int numberOfPermutations(int n, vector<vector<int>>& requirements) {const int MOD = 1e9+7;vector<int> req(n, -1);req[0] = 0;for (auto& p : requirements) {req[p[0]] = p[1];}if (req[0]) {return 0;}int m = ranges::max(req);vector<vector<int>> memo(n, vector<int>(m + 1, -1)); // -1 表示没有计算过auto dfs = [&](auto&& dfs, int i, int j) -> int {if (i == 0) {return 1;  //i为0,直接返回1}int& res = memo[i][j]; // 注意这里是引用if (res != -1) { // 之前计算过return res;}res = 0;if (int r = req[i - 1]; r >= 0) {  //r大于0,说明在req中有逆序对数量要求if (j >= r && j <= r + i) {  //对于满足要求的j,res值唯一确定,继承前者dfs(i-1,r)。res = dfs(dfs, i - 1, r);}} else {for (int k = 0; k <= min(i, j); k++) {  //前面元素和当前最后一位组成k对逆序对,最多不会超过i。res = (res + dfs(dfs, i - 1, j - k)) % MOD;  //对于每个枚举的k,前面元素需组成j - k对逆序对}}return res;};return dfs(dfs, n - 1, req[n - 1]);}

剪枝技巧

组合总数

问题描述

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

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

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

原题链接

代码

vector<vector<int>>tar;vector<int>tr;int s;void dfs(vector<int>& c,int begin,int t){if(t==0) {tar.push_back(tr);return;}/*在搜索中去重,每一次搜索的时候设置 下一轮搜索的起点 begin从每一层的第 2 个结点开始,都不能再搜索产生同一层结点已经使用过的 candidate 里的元素。*/for(int i=begin;i<s&&t-c[i]>=0;i++){  //t-c[i]>=0,减枝tr.push_back(c[i]);dfs(c,i,t-c[i]);  tr.pop_back();  //回朔}}vector<vector<int>> combinationSum(vector<int>& candidates, int target) {s=candidates.size();if(s==0) return tar;sort(candidates.begin(),candidates.end());  //排序是剪枝的前提dfs(candidates,0,target);return tar;}

买瓜

蓝桥杯2023年省赛题

问题描述

小蓝在瓜摊上买瓜。瓜摊上共有n个瓜,每个瓜的重量为Ai,小蓝可以把任何瓜劈成完全等重的两份,不过每个瓜最多劈一刀。小蓝希望买的瓜的重量之和为m。输出小蓝至少要劈多少个瓜才能买到总重量恰好为m的瓜,如果无论如何都无法得到总重量恰好为m的瓜,则输出-1。

原题链接

思路分析

每个西瓜可以有1.选整个,2.不选,3.选一半 三种选择,枚举所有总重量等于m的组合方案,选出劈瓜数最少得那个。如果纯暴力的话,总复杂度将达到O(3^n).

可通过剪枝来优化一下时间复杂度:

  1. 选取的瓜的总重量已经大于等于m,可以不用继续【递】下去,直接【归】。
  2. 维护一个最小劈瓜数ans,当前劈瓜数已经大于等于ans时不用再【递】下去,直接【归】。
  3. 定义一个后缀数组sum,记录sum[i]记录[i,n-1]区间内所有西瓜的总重量,当前面选取西瓜重量+后面所有西瓜总重量都不足以等于m时不用再【递】下去,直接【归】。

代码

#include <bits/stdc++.h>
using namespace std;int ans=50;//ans维护最小劈瓜数  先设为一个大值int a[50];//存瓜 原数组int sum[50];//表示的是从第 i 个瓜到第 n 个瓜的总质量int n,m;void dfs(int S,int i,int cnt)//总和,下标,劈瓜计数器
{if(cnt>=ans)return;//剪枝if(S==m) ans=min(ans,cnt);//如果相等,说明劈瓜劈够了,返回已经劈了几次瓜if(i>=n||S>=m||S+sum[i]<m) return ;//递归结束条件dfs(S+a[i],i+1,cnt);//买一个瓜dfs(S+a[i]/2,i+1,cnt+1);//买半个瓜,计数器+1dfs(S,i+1,cnt);//不买当前瓜,跳到下一个瓜
}
int main()
{ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>m;m<<=1;//总质量也要*2才能保证结果不受影响for(int i=0;i<n;i++) cin>>a[i],a[i]<<=1;//为了防止劈瓜出现小数,将其左移一位*2倍//遍历所有的瓜for(int i=n-1;i>=0;i--){sum[i]=sum[i+1]+a[i];//记录后缀数组}dfs(0,0,0);if(ans==50)cout<<-1;  //最终 ans 仍然为初始值 50,则表示无法通过劈瓜的方式满足要求else cout<<ans;return 0;
}

http://www.hkcw.cn/article/jSSsgSylta.shtml

相关文章

电子电路:时钟脉冲与上升沿的详细解析

一、时钟脉冲的量子物理本质 1. 电磁波能量量子化 时钟脉冲本质是电磁能量的周期性传递,其最小能量单元为: E = h f E = hf E=hf 其中 h = 6.626 10 − 34 J ⋅ s h=6.62610^{-34} \ Js h=6.62610−34 J⋅s(普朗克常数), f f f 为时钟频率。当3GHz CPU运行时,单个时…

HTTPS

HTTPS 是什么 它其实就是网站的保镖版 HTTP。平常你用普通HTTP上网&#xff0c;你浏览器和网站服务器之间传的东西&#xff0c;不管是密码、聊天内容还是信用卡号&#xff0c;都是“裸奔”的&#xff0c;谁都能半路偷看或者篡改。 HTTPS 就不同了&#xff0c;它在你们开始传东…

LTSPICE仿真电路:(三十)压流变换器

1.压流转换器&#xff08;NPN型三极管&#xff09; 压流转换器&#xff1a;将电压转换为电流信号。 直接看仿真 这个电路是负反馈电路&#xff0c;分析使用续断虚短&#xff0c;输入信号是3V&#xff0c;所以在Rset电阻处的电压始终是3V &#xff0c;Uce为6V&#xff08;发射…

电动机定子铁芯冲槽模设计与多物理场仿真优化

摘要 本文系统阐述电动机定子铁芯冲槽模的设计规范与仿真验证方法。通过分析冲裁机理&#xff0c;提出模具材料选型、间隙计算、结构优化的关键技术方案&#xff0c;并借助ANSYS Workbench平台进行应力-疲劳联合仿真&#xff0c;为高精度冲槽模设计提供理论依据和工程实践参考…

window 显示驱动开发-复制深度模具值

Microsoft Direct3D 运行时调用用户模式显示驱动程序的 Blt 函数&#xff0c;将深度模具值从视频内存复制到系统内存&#xff0c;反之亦然。 驱动程序和硬件必须从或转换到驱动程序支持的所有不透明深度模具格式 (&#xff0c;即 D3DDDIFORMAT 枚举类型定义的所有格式&#xff…

pc端小卡片功能-原生JavaScript金融信息与节日日历

代码如下 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>金融信息与节日日历</title><…

Redis最佳实践——购物车优化详解

Redis在电商购物车高并发读写场景下的优化实践 一、购物车业务场景分析 典型操作特征 读/写比例 ≈ 8:2高峰QPS可达10万单用户最大商品数500操作类型&#xff1a;增删改查、全选/反选、数量修改 技术挑战 高并发下的数据一致性海量数据存储与快速访问实时价格计算与库存校验分…

[网页五子棋][对战模块]处理连接成功,通知玩家就绪,逻辑问题(线程安全,先手判定错误)

文章目录 处理连接成功通知玩家就绪逻辑图问题 1&#xff1a;线程安全问题 2&#xff1a;先手判定错误两边都是提示&#xff1a;轮到对方落子![image.png](https://i-blog.csdnimg.cn/img_convert/c570cd26eadbe87ed467bc4edaa7945e.png) 处理连接成功 实现 GameAPI 的 afterC…

Python训练打卡Day39

图像数据与显存 知识点回顾 图像数据的格式&#xff1a;灰度和彩色数据模型的定义显存占用的4种地方 模型参数梯度参数优化器参数数据批量所占显存神经元输出中间状态 batchisize和训练的关系 一、图像数据的格式 1.1灰度图像 作为图像数据&#xff0c;相较于结构化数据&#x…

pycharm打印时不换行,方便对比观察

原来&#xff1a; 优化&#xff1a; import torch torch.set_printoptions(linewidth200) 优化结果&#xff1a;

Practice 2025.6.1—— 二叉树进阶面试题(2)

文章目录 二叉树进阶面试题(2)Leetcode_144 二叉树的前序遍历(使用非递归)Leetcode_94 二叉树的中序遍历(使用非递归)Leetcode_145 二叉树的后序遍历(使用非递归) 二叉树进阶面试题(2) 本篇文章将继续进行二叉树的进阶面试题的讲解&#xff0c;其中&#xff0c;本部分将重点针…

DOCKER使用记录

1、拉取镜像 直接使用docker pull <image>&#xff0c;大概率会出现下面的报错信息&#xff1a; (base) jetsonyahboom:~$ docker pull ubuntu:18.04 Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while …

Vert.x学习笔记-EventLoop与Context的关系

Vert.x学习笔记 1. EventLoop 的核心作用2. Context 的核心作用3. EventLoop 与 Context 的关系1. 事件循环&#xff08;EventLoop&#xff09;的核心职责2. 上下文&#xff08;Context&#xff09;的核心职责3. 事件循环与上下文的关系&#xff08;1&#xff09;一对一绑定&am…

LTSPICE仿真电路:(三十一)HOWLAND电流源

1.HOWLAND电流源 推导过程&#xff1a;这个运放是正负反馈都存在&#xff0c;但负反馈是大于正反馈的&#xff0c;因正反馈多出一个Rload&#xff0c;所以可以使用虚短续断&#xff0c;运放的U等于U-&#xff0c;负反馈处得出Uout与U-的关系&#xff0c;再利用正相端节点电流算…

LLaMA-Factory - 批量推理(inference)的脚本

scripts/vllm_infer.py 是 LLaMA-Factory 团队用于批量推理&#xff08;inference&#xff09;的脚本&#xff0c;基于 vLLM 引擎&#xff0c;支持高效的并行推理。它可以对一个数据集批量生成模型输出&#xff0c;并保存为 JSONL 文件&#xff0c;适合大规模评测和自动化测试。…

引擎下线缺陷检测系统ENAgent

引擎下线缺陷检测系统ENAgent采用信号处理技术以及人工智能技术对引擎生产线下线的各种引擎在生产线上进行缺陷实时检测&#xff0c;通过振动信号、声纹信号等信号融合集成&#xff0c;在线实时判断其是否存在缺陷以及进行故障诊断。ENAgent系统采用全Python语言&#xff0c;以…

【量化交易学习】布林线(BOLL)指标

目录 1. 布林线&#xff08;BOLL&#xff09;指标定义与构成1.1 定义1.2 布林线的构成 2. BOLL&#xff08;布林线&#xff09;的应用场景3. BOLL指标的研判标准3.1 BOLL指标中的上、中、下轨线的意义3.2 BOLL指标中的上、中、下轨线之间的关系3.3 K线和布林线上、中、下轨之间…

ArcGIS Pro 创建渔网格网过大,只有几个格网的解决方案

之前用ArcGIS Pro创建渔网的时候&#xff0c;发现创建出来格网过大&#xff0c;只有几个格网。 后来查阅资料&#xff0c;发现是坐标不对&#xff0c;导致设置格网大小时单位为度&#xff0c;而不是米&#xff0c;因此需要进行坐标系转换&#xff0c;网上有很多资料讲了ArcGIS …

java27

1.IO流 FileOutPutStream字节输出流基本用法&#xff1a; 一次性写入一个字符串的内容&#xff1a; 注意&#xff1a;\r或者\n表示把普通的r或者n的字符转义成回车的意思&#xff0c;所以不需要\\ FileInputStream字节输入流基本用法 -1在ASCII码里面对应的符号&#xff1a; 不…

Windows设置之RDP文件用户密码

1、远程桌面另存为rdp文件 2、编辑rdp文件&#xff0c;添加用户名密码信息 username:s:<用户名> password 51:b:<加密后的密码> 3、<加密后的密码>通过PowerShell命令或者 ("<密码>" | ConvertTo-SecureString -AsPlainText -Force) | Conve…