Leetcode hot 100之回溯O(N!):选择/DFS

目录

框架:排列/组合/子集

元素无重不可复选

全排列

子集

组合:[1, n] 中的 k 个数

分割成回文串

元素无重不可复选:排序,多条值相同的只遍历第一条

子集/组合

先进行排序,让相同的元素靠在一起,如果发现 nums[i] == nums[i-1],则跳过 

排列

元素无重可复选

子集/组合:sum=target

排列:去除 used 剪枝

N皇后


如果不能成功,那么返回的时候我们就还要把这个位置还原。这就是回溯算法,也是试探算法。

解决一个回溯问题,实际上就是一个决策树的遍历过程

1、路径:已选择。

2、选择列表:可选择。

3、结束条件:无选择。

框架:排列/组合/子集

result = []
function backtrack(路径, 选择列表):if 满足结束条件:result.add(路径)result.push([...path])或者result.push(path.slice())//path还会改变,所以不能传引用地址returnfor 选择 in 选择列表:做选择pushbacktrack(路径, 选择列表)撤销选择pop数组(有一定的剪枝,不用判断是否use)
const backtrack = (start) => {// 回溯算法标准框架for (let i = start; i < nums.length; i++) {// 做选择track.push(nums[i]);// 回溯遍历下一层节点backtrack(i + 1);// 撤销选择track.pop();}};backtrack(0);
图
function backtrack(nums, used, track, res) {for (let i = 0; i < nums.length; i++) {if (used[i]) {continue;}track.push(nums[i]);used[i] = true;backtrack(nums, used, track, res);track.pop();used[i] = false;}
}全排列中
做选择/撤销选择 可用if(path.includes(item)) continue;代替

元素无重不可复选

全排列

key:

  1. path.length == string.length
  2. path.includes(item)
const _permute = string => {const res = [];const backtrace = path => {if(path.length == string.length){res.push(path);return;}for(const item of string) {if(path.includes(item)) continue;backtrace(path + item);}};backtrace('');return res;
}

子集

输入一个无重复元素的数组 nums,其中每个元素最多使用一次,请你返回 nums 的所有子集

比如输入 nums = [1,2,3],算法应该返回如下子集:

[ [],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3] ]

/*** @param {number[]} nums* @return {number[][]}*/
var subsets = function(nums) {// 用于存储结果const res = [];// 用于记录回溯路径const track = [];/*** 回溯算法的核心函数,用于遍历子集问题的回溯树* @param {number} start - 控制树枝的遍历,避免产生重复子集*/const backtrack = (start) => {// 前序遍历位置,每个节点的值都是一个子集res.push([...track]);// 回溯算法标准框架for (let i = start; i < nums.length; i++) {// 做选择track.push(nums[i]);// 回溯遍历下一层节点backtrack(i + 1);// 撤销选择track.pop();}};backtrack(0);return res;
};

组合:[1, n] 中的 k 个数

返回范围 [1, n] 中所有可能的 k 个数的组合,剪枝

let result = []
let path = []
var combine = function(n, k) {result = []combineHelper(n, k, 1)return result
};
const combineHelper = (n, k, startIndex) => {if (path.length === k) {result.push([...path])return}for (let i = startIndex; i <= n - (k - path.length) + 1; ++i) {path.push(i)combineHelper(n, k, i + 1)path.pop()}
}

分割成回文串

  • 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个.....。
  • 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段.....。

/*** @param {string} s* @return {string[][]}*/
const isPalindrome = (s, l, r) => {for (let i = l, j = r; i < j; i++, j--) {if(s[i] !== s[j]) return false;}return true;
}var partition = function(s) {const res = [], path = [], len = s.length;backtracking(0);return res;function backtracking(startIndex) {if(startIndex >= len) {res.push(Array.from(path));return;}for(let i = startIndex; i < len; i++) {if(!isPalindrome(s, startIndex, i)) continue;path.push(s.slice(startIndex, i + 1));backtracking(i + 1);path.pop();}}
};

元素无重不可复选:排序,多条值相同的只遍历第一条

子集/组合

nums = [1,2,2],你应该输出:

[ [],[1],[2],[1,2],[2,2],[1,2,2] ]

如果一个节点有多条值相同的树枝相邻,则只遍历第一条,剩下的都剪掉,不要去遍历:

先进行排序,让相同的元素靠在一起,如果发现 nums[i] == nums[i-1],则跳过 

排列

// 注意:javascript 代码由 chatGPT🤖 根据我的 java 代码翻译,旨在帮助不同背景的读者理解算法逻辑。
// 本代码还未经过力扣测试,仅供参考,如有疑惑,可以参照我写的 java 代码对比查看。/*** @param {number[]} nums* @return {number[][]}*/var permuteUnique = function(nums) {let res = [];let track = [];let used = new Array(nums.length).fill(false);// 先排序,让相同的元素靠在一起nums.sort((a, b) => a - b);backtrack(nums, used, track, res);return res;
};/*** @param {number[]} nums* @param {boolean[]} used* @param {number[]} track* @param {number[][]} res*/
function backtrack(nums, used, track, res) {if (track.length === nums.length) {res.push(track.slice());return;}for (let i = 0; i < nums.length; i++) {if (used[i]) {continue;}// 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) {continue;}track.push(nums[i]);used[i] = true;backtrack(nums, used, track, res);track.pop();used[i] = false;}
}

元素无重可复选

子集/组合:sum=target

想让每个元素被重复使用,我只要把 i + 1 改成 i 即可

给之前的回溯树添加了一条树枝,在遍历这棵树的过程中,一个元素可以被无限次使用

这棵回溯树会永远生长下去,所以我们的递归函数需要设置合适的 base case 以结束算法,即路径和大于 target 时就没必要再遍历下去了 

排列:去除 used 剪枝

N皇后

在 n * n 的棋盘上要摆 n 个皇后,
要求:任何两个皇后不同行,不同列不在同一条斜线上,
求给一个整数 n ,返回 n 皇后的摆法数。

要求:空间复杂度 O(1) ,时间复杂度O(n!)

  1. 要确定皇后的位置,其实就是确定列的位置,因为行已经固定了
  2. 进一步讲,也就是如何摆放 数组arr [0,1,2,3,...,n-1]
  3. 如果没有【不在同一条斜线上】要求,这题其实只是单纯的全排列问题
  4. 在全排列的基础上,根据N皇后的问题,去除一些结果
  • arr :n个皇后的列位置

  • res :n皇后排列结果

  • ruler: 记录对应的列位置是否已经占用(也是是否有皇后),如果有,那么设为1,没有设为0

  • setPos :哈希集合,标记正斜线(从左上到右下)位置,如果在相同正斜线上,坐标(x,y)满足 y-x 都相同,(y1 - x1)应该等于(y2 - x2)

  • setCon :哈希集合,标记反正斜线(从y右上到左下)位置,如果在相同反斜线上,坐标(x,y)满足 x+y 都相同,(x1 + y1)应该等于(x2 + y2)

  • 是否在同一斜线上,其实就是这两个点的所形成的斜线的斜率是否为±1。点P(a,b) ,点Q(c,d)

    (1)斜率为1 (d-b)/(c-a) = 1,横纵坐标之差相等

    (2)斜率为-1 (d-b)/(c-a) = -1 ,等式两边恒等变形 a+b = c + d ,横纵坐标之和相等

/**** @param n int整型 the n* @return int整型*/
function Nqueen(n) {let res = []; //二维数组,存放每行Q的列坐标let isQ = new Array(n).fill(0); //记录该列是否有Qlet setPos = new Set(); //标记正对角线let setCon = new Set(); // 标记反对角线//给当前row找一个colconst backTrace = (row, path) => {if (path.length === n) {res.push(path);return;}for (let col = 0; col < n; col++) {if (isQ[col] == 0 &&!setPos.has(row - col) &&!setCon.has(row + col)) {path.push(col);isQ[col] = 1;setPos.add(row - col);setCon.add(row + col);backTrace(row + 1, path);path.pop();isQ[col] = 0;setPos.delete(row - col);setCon.delete(row + col);}}};backTrace(0, []);return res.length;
}
module.exports = {Nqueen: Nqueen,
};

动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table 或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySql】4- 实践篇(二)

文章目录 1. SQL 语句为什么变“慢”了1.1 什么情况会引发数据库的 flush 过程呢&#xff1f;1.2 四种情况性能分析1.3 InnoDB 刷脏页的控制策略 2. 数据库表的空间回收2.1 innodb_file_per_table参数2.2 数据删除流程2.3 重建表2.4 Online 和 inplace 3. count(*) 语句怎样实现…

基于PSD-ML算法的语音增强算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 1.加窗处理&#xff1a; 2.分帧处理&#xff1a; 3.功率谱密度估计&#xff1a; 4.滤波处理&#xff1a; 5.逆变换处理&#xff1a; 6.合并处理&#xff1a; 5.算法完整程序工程 1.算法…

2023,社交进入大变革时代

社交赛道的发展似乎没有终局&#xff0c;尤其AIGC这一概念出现之后&#xff0c;社交有可能将迎来全新场景。那么在技术的助推下&#xff0c;社交赛道是否有可能跑出下一个超级APP&#xff1f;如何看待2023年的社交赛道变革&#xff1f;不妨来看看本文的解读。 2023社交还有新故…

JavaScript系列从入门到精通系列第十九篇:JavaScript中的this关键字

文章目录 前言 一&#xff1a;什么是this 二&#xff1a;this的灵活妙用 前言 function fun(a,b){console.log(a b); }fun(1,2); 我们通过形参的形式往参数中添加了参数。浏览器也会默默的给我们传递一个参数过去&#xff0c;这个参数被称为this。传递的节点就是在调用函…

阿里云服务器乌兰察布带宽收费价格表

阿里云服务器华北6&#xff08;乌兰察布&#xff09;地域公网带宽价格表&#xff0c;1M带宽价格是23元/月&#xff0c;按使用流量价格是0.8元每GB&#xff0c;阿里云服务器网来详细说下1M带宽、5M带宽、6M带宽、10M带宽、20M带宽、50M带宽和100M等带宽收费价格表&#xff1a; …

材质、纹理、贴图的区别和关联

1、材质、纹理、贴图的概念 材质&#xff08;Material&#xff09;、纹理&#xff08;Texture&#xff09;、贴图&#xff08;Texture Map&#xff09;是计算机图形学中的三个概念&#xff0c;它们之间存在关系但也有一些区别。 材质&#xff08;Material&#xff09;是描述物…

NPM 常用命令(九)

目录 1、npm link 1.1 使用语法 1.2 描述 2、npm login 2.1 描述 3、npm logout 3.1 描述 4、npm ls 4.1 使用语法 4.2 描述 5、npm org 5.1 使用语法 5.2 示例&#xff1a; 6、npm outdated 6.1 使用语法 6.2 描述 6.3 示例 7、npm owner 7.1 使用语法 7.2…

论文笔记 A theory of learning from different domains

domain adaptation 领域理论方向的重要论文. 这篇笔记主要是推导文章中的定理, 还有分析定理的直观解释. 笔记中的章节号与论文中的保持一致. 1. Introduction domain adaptation 的设定介绍: 有两个域, source domain 与 target domain. source domain: 一组从 source dist.…

Kafka在企业级应用中的实践

前言 前面说了很多Kafka的性能优点&#xff0c;有些童鞋要说了&#xff0c;这Kafka在企业开发或者企业级应用中要怎么用呢&#xff1f;今天咱们就来简单探究一下。 1、 使用 Kafka 进行消息的异步处理 Kafka 提供了一个可靠的消息传递机制&#xff0c;使得企业能够将不同组件…

汽车驾驶 - 四梁六柱是什么

汽车的四梁六柱指的是车辆的两个前纵梁&#xff0c;两个后纵梁和ABC柱。虽然不像车辆上的发动机变速箱这些部件出镜率那么高&#xff0c;但这几个部位的重要作用可一点都不含糊。一辆车在碰撞时能够受力起到保护左右的就是四梁六柱&#xff0c;对我们汽车的安全性起到至关重要的…

使用docker-compose部署Redis(单机部署)

目录 一、查看Redis镜像版本二、拉取自己需要的镜像版本三、创建挂载目录四、添加配置文件五、编写 docker-compose.yml 文件六、启动容器七、连接测试 一、查看Redis镜像版本 先去Docker Hub查看Redis镜像有那些版本&#xff0c;我部署的时候Redis最新已经到7.x的版本了&…

Go运算操作符全解与实战:编写更高效的代码!

目录 简介file 基础数学运算操作符加法操作符 语法示例类型描述 减法操作符 -语法示例类型描述 乘法操作符 *语法示例类型描述 除法操作符 /语法示例类型描述 取模操作符 %语法示例类型描述 位运算操作符按位与操作符 &语法示例类型描述 按位或操作符 |语法示例类型描述 按…