图解 LeetCode 算法汇总——回溯
本文首发公众号:小码A梦
回溯算法是一种常见的算法,常见用于解决排列组合、排列问题、搜索问题等算法,在一个搜索空间中寻找所有的可能的解。通过向分支不断尝试获取所有的解,然后找到合适的解,找完一个分支后再往回搜索。回溯算法通常使用递归的方式实现。
回溯本质是一种暴力搜索法,列出所有可能的解,然后找到合适的解。以 a、b、c的排列组合为例,画出全排列组合。

以上排列组合回溯步骤:
- 列出所有可能存在的组合。
- 分解组合,把问题分解多个阶段,每个阶段添加一个分叉。
- 走完一个分叉,或者遇到不符合期望条件的时,就退回到上一个分叉。继续走其它没走的路。直到走完所有的路。
- 回溯一半都是使用递归实现。
根据以上的步骤得出一个简单的回溯算法的模板:
public Solution {
List<List<Integer>> result;
LinkedList<Integer> path;
//记录那些元素被遍历过
boolean[] used;
private List<List<Integer>> permute(int[] nums) {
result = new ArrayList<>();
path = new LinkedList<>();
used = new boolean[nums.length];
permuteHelper(nums);
return result;
}
private void permuteHelper(int[] nums) {
if (递归终止条件) {
result.add(new ArrayList<>(path));
return;
}
//遍历各个元素
for (int i = 0; i < nums.length; i++) {
used[i] = true;
//选择元素
path.add(nums[i]);
permuteHelper(nums);
//移除元素
path.removeLast();
used[i] = false;
}
}
}
以上代码使用递归,递归一般要设置一个终止条件,然后遍历整个元素,通过链表选择元素和移除元素。
LeetCode 题解
上面所说的,回溯主要解决一些排列组合、排列问题、搜索问题等问题,LeetCode 有很多类似的问题,这里选取了几个比较常见的题目。
- 39 组合总和
- 40 组合总和 II
- 46 全排列
- 47 全排列 II
- 51 N皇后
39.组合总和(中等)
题目描述

解法
这是一个比较典型的排列组合问题,本题采用的是求总和,使用总和减去遍历的数据,最后得到结果为零,就是符合的组合。
- 为了减少遍历次数,数组需要先排序。总数减的数据如果小于零,就不会在该分支继续遍历了。
- 可以重复使用元素,每次都遍历一遍全部元素。
- 减去分支结果之后,以新的结果,再创建分支做减法。
- 递归遍历一直到结果为零和负数。
- 为零,符合条件,记录数据,对应的分支遍历终止,继续遍历下一个分支。
- 为负数,返回到上一个分支,继续遍历后面的分支。

最终代码:
class Solution {
List<List<Integer>> list = new ArrayList<>();
int[] candidate;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
candidate = candidates;
recall(0,target,new LinkedList<>());
return list;
}
private void recall(int start, int target, LinkedList<Integer> path) {
if (target == 0) {
list.add(new ArrayList<>(path));
return;
}
for (int i = start; i <candidate.length ; i++) {
int sub = target - candidate[i];
if (sub < 0) {
break;
}
path.add(candidate[i]);
recall(i,sub,path);
path.removeLast();
}
}
}
recall 使用递归方法遍历分支,而使用链表的特性,记录遍历的节点,如果不符合要求就上一个分支回撤,同时链表移除最后一个结点。
40.组合总和II(中等)

解题思路
这题的解题思路和上面的组合总和是差不多的,唯一不同的是元素不能被重复遍历,使用一个变量记录遍历的起始值,遍历过的数据,下次往后一位开始遍历。
代码如下:
class Solution {
List<List<Integer>> list = new ArrayList<>();
int[] candidate;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
candidate = candidates;
recall(0,target,new LinkedList<>());
return list;
}
private void recall(int start, int target, LinkedList<Integer> path) {
if (target == 0) {
list.add(new ArrayList<>(path));
return;
}
for (int i = start; i <candidate.length ; i++) {
//这里解决集合重复问题
if (i > start && candidate[i] == candidate[i-1]) {
continue;
}
int sub = target - candidate[i];
if (sub < 0) {
break;
}
path.add(candidate[i]);
recall(i + 1,sub,path);
path.removeLast();
}
}
}
start 记录遍历的起始值,其他解题方法和上面的组合求和是类似的。题目还有一个要求是不能出现重复的组合,就需要判断 candidate[i] == candidate[i-1] 就忽略该数据,往后继续遍历。
46.全排列
解题思路
- 每个元素都需要遍历一遍。
- 遍历元素的时,遍历完第一数,继续遍历未遍历的数据。
- 遍历结束后,返回上一个分叉。

代码整理如下:
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
if (nums.length == 0) {
return result;
}
used = new boolean[nums.length];
permuteHelper(nums);
return result;
}
private void permuteHelper(int[] nums) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) {
continue;
}
used[i] = true;
path.add(nums[i]);
permuteHelper(nums);
path.removeLast();
used[i] = false;
}
}
}
使用 used 记录哪些数据遍历过,遍历过的数据不会遍历,其他也是使用递归搜索。
51.N皇后
题目描述

解题思路
N 皇后问题是一个经典的回溯算法问题,是面试比较常见的问题。在一个 n * n 的棋盘上,每个格子放入的元素后,查看是够有同行、同列、左上方以及右上方是否冲突,冲突就回溯,不冲突就继续往下遍历。
- 初始化数组,默认初始值。
- 每一行只能放一个 Q,不冲突后,再遍历下一列的数据(因为同一行不能冲突)。
- 因为每一行只放一个 Q,所以不存在同行冲突。判断冲突就潘丹同一列、左上方以及右上方是否有冲突。
- 遍历到最后一行时,记录符合条件的数据。
class Solution {
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
// 初始化棋盘 "." 表示空,"Q"表示皇后,
char[][] board = new char[n][n];
for (char[] c : board) {
Arrays.fill(c, '.');
}
backtrack(board, 0);
return res;
}
private void backtrack(char[][] board, int row) {
//终止条件
if (row == board.length) {
res.add(charToList(board));
return;
}
//每一行列数(也就是长度)
int n = board[row].length;
for (int col = 0; col < n; col++) {
//排除相互攻击的格子
if (!isValid(board,row,col)) {
continue;
}
//放入Q
board[row][col] = 'Q';
//进入下一行放皇后
backtrack(board,row + 1);
//撤销Q
board[row][col] = '.';
}
}
private boolean isValid(char[][] board, int row, int col) {
int n = board.length;
//检查列是否有皇后冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q') {
return false;
}
}
//检查右上方是否有皇后冲突
for (int i = row - 1,j = col + 1; i >= 0 && j < n; i--,j++) {
if (board[i][j] == 'Q') {
return false;
}
}
//检查左上方是否有皇后冲突
for (int i = row - 1,j = col - 1; i >= 0 && j >= 0; i--,j--) {
if (board[i][j] == 'Q') {
return false;
}
}
return true;
}
public List<String> charToList(char[][] board) {
List<String> list = new ArrayList<>();
for (int i = 0; i < board.length; i++) {
list.add(String.copyValueOf(board[i]));
}
return list;
}
}
总结
回溯算法尝试在问题的解空间中搜索可能的解,并在搜索过程中进行选择、撤销选择和终止搜索,直到找到解或确定无解为止。
- 通常通过递归函数来实现回溯算法。
- 在每一步,需要做出选择(选择一个分支)然后递归地探索该选择的结果。
- 在递归返回后,需要撤销之前的选择,以便继续探索其他分支。
- 使用条件语句或循环来控制选择的范围和条件。
图解 LeetCode 算法汇总——回溯的更多相关文章
- LeetCode算法训练-回溯 491.递增子序列 46.全排列 47.全排列 II
欢迎关注个人公众号:爱喝可可牛奶 LeetCode算法训练-回溯 491.递增子序列 46.全排列 47.全排列 II LeetCode 491. 递增子序列 分析 找出并返回所有数组中不同的递增子序 ...
- LeetCode算法训练-回溯总结
欢迎关注个人公众号:爱喝可可牛奶 LeetCode算法训练-回溯总结 适用问题 组合问题:N个数里面按一定规则找出k个数的集合 排列问题:N个数按一定规则全排列,有几种排列方式 切割问题:一个字符串按 ...
- LeetCode算法题目解答汇总(转自四火的唠叨)
LeetCode算法题目解答汇总 本文转自<四火的唠叨> 只要不是特别忙或者特别不方便,最近一直保持着每天做几道算法题的规律,到后来随着难度的增加,每天做的题目越来越少.我的初衷就是练习, ...
- 近日LeetCode算法(记录)
近日LeetCode算法 前言:最近刷了好多leetcode算法题,大家知道,程序=数据结构+算法,由此可见,算法真的是很重要的呢.闲话少谈,切入正题,来看看小编觉得有点意思的5题算法题吧... 1. ...
- ACM金牌选手讲解LeetCode算法《栈和队列的高级应用》
大家好,我是编程熊,双非逆袭选手,字节跳动.旷视科技前员工,ACM金牌,保研985,<ACM金牌选手讲解LeetCode算法系列>作者. 上一篇文章讲解了<线性表>中的数组.链 ...
- LeetCode算法训练 93.复原IP地址 78.子集 90.子集II
欢迎关注个人公众号:爱喝可可牛奶 LeetCode算法训练 93.复原IP地址 78.子集 90.子集II LeetCode 93. 复原 IP 地址 分析 字符串全部由数字组成,ipv4每一段数字不 ...
- 排序算法汇总(C/C++实现)
前言: 本人自接触算法近2年以来,在不断学习中越多地发觉各种算法中的美妙.之所以在这方面过多的投入,主要还是基于自身对高级程序设计的热爱,对数学的沉迷.回想一下,先后也曾参加过ACM大大小小的 ...
- leetcode算法: Find Bottom Left Tree Value
leetcode算法: Find Bottom Left Tree ValueGiven a binary tree, find the leftmost value in the last row ...
- LeetCode算法题-Subdomain Visit Count(Java实现)
这是悦乐书的第320次更新,第341篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第189题(顺位题号是811).像"discuss.leetcode.com& ...
- LeetCode算法题-Number of Lines To Write String(Java实现)
这是悦乐书的第319次更新,第340篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第188题(顺位题号是806).我们要将给定字符串S的字母从左到右写成行.每行最大宽度为 ...
随机推荐
- k8s calico网络
- 驱动开发:内核解析PE结构导出表
在笔者的上一篇文章<驱动开发:内核特征码扫描PE代码段>中LyShark带大家通过封装好的LySharkToolsUtilKernelBase函数实现了动态获取内核模块基址,并通过ntim ...
- NLM 公布了一个新的重新设计的 PubMed 数据库
经常使用 PubMed 的童鞋可能已经发现,美国国家医学图书馆(NLM)在今年 10 月份左右发布了一个新的重新设计的版本以取代 PubMed 数据库的现有版本,新版本现在已经上线,可以通过下面的链接 ...
- 一次有关 DNS 解析导致 APP 慢的问题探究
目录 一.业务背景 二. 问题 三.问题排查 3.1.问题一: 基于DNS 延迟的解析 3.2.问题二:HTTPDNS侧 HTTPDNS基础理论 相关问题 四.优化方向 4.1.域名解析配置 4.2. ...
- ESP8266-01S烧录固件
ESP8266-01S 整理了一下ESP01S的烧录固件和烧录APP 链接:https://pan.baidu.com/s/1DApOQcWhqvk378ZklJSypA 提取码:1028 文件夹包含 ...
- 现代C++学习指南-类型系统
在前一篇,我们提供了一个方向性的指南,但是学什么,怎么学却没有详细展开.本篇将在前文的基础上,着重介绍下怎样学习C++的类型系统. 写在前面 在进入类型系统之前,我们应该先达成一项共识--尽可能使用C ...
- Java 文件写入不覆盖的写法
FileOutputStream o = null; File file = null; FileWriter fw = null; byte[] buff = new byte[]{}; try { ...
- 变分自编码器(VAE)公式推导
论文原文:Auto-Encoding Variational Bayes [OpenReview (ICLR 2014) | arXiv] 本文记录了我在学习 VAE 过程中的一些公式推导和思考.如果 ...
- ubuntu22.04下编译ffmpeg-6.0,并且激活x264编码功能。记录一下踩坑(ERROR: x264 not found using pkg-config)
一.编译x264(在编译前确保安装了pkg-config,默认在/usr/share下) 1.下载x264源代码:(我下载到了~/Downloads下,各位随意就好) git clone https: ...
- 基于.Net Core实现的飞书文档一键导出服务(支持多系统)
feishu-doc-export 一个支持Windows.Mac.Linux系统的飞书文档一键导出服务,仅需一行命令即可将飞书知识库的全部文档同步到本地电脑.导出速度嘎嘎快,实测700多个文档导出只 ...