算法及数据结构系列 - 回溯算法

ops/2025/3/23 9:59:59/

系列文章目录
算法数据结构系列 - 二分查找
算法数据结构系列 - BFS算法
算法数据结构系列 - 动态规划
算法数据结构系列 - 双指针

文章目录

  • 回溯算法
    • 框架思路
      • 思路
      • 代码框架
    • 经典题型
      • 全排列问题
      • 子集问题
      • 组合问题
      • N皇后问题
    • 刷题
      • 113.路径总和 II
      • 31.下一个排列
      • 47. 全排列 II
      • 996. 正方形数组的数目

回溯算法

框架思路

思路

解决一个回溯问题,实际上就是一个决策树的遍历过程。你只需要思考 3 个问题:

  1. 路径:也就是已经做出的选择。
  2. 选择列表:也就是你当前可以做的选择。
  3. 结束条件:也就是到达决策树底层,无法再做选择的条件。

代码框架

result = []
def backtrack(路径, 选择列表):if 满足结束条件:result.add(路径)returnfor 选择 in 选择列表:做选择backtrack(路径, 选择列表)撤销选择

其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」

经典题型

全排列问题

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

List<List<Integer>> res = new LinkedList<>();/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {// 记录「路径」LinkedList<Integer> track = new LinkedList<>();backtrack(nums, track);return res;
}// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {// 触发结束条件if (track.size() == nums.length) {res.add(new LinkedList(track));return;}for (int i = 0; i < nums.length; i++) {// 排除不合法的选择if (track.contains(nums[i]))continue;// 做选择track.add(nums[i]);// 进入下一层决策树backtrack(nums, track);// 取消选择track.removeLast();}
}

子集问题

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

class Solution {List<List<Integer>> res = new ArrayList<List<Integer>>();public List<List<Integer>> subsets(int[] nums) {helper(new LinkedList<Integer>(), nums, 0);return res;}public void helper(LinkedList<Integer> path, int[] nums, int fromIndex){res.add(new LinkedList<Integer>(path));for(int i = fromIndex; i < nums.length; i++){path.add(nums[i]);helper(path, nums, i + 1); // 每次只能选择选过元素之后的元素path.removeLast();}}
}

组合问题

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

提示: 通过指定 fromIndex 去重

class Solution {List<List<Integer>> res = new ArrayList<List<Integer>>();public List<List<Integer>> combine(int n, int k) {helper(k,n, new LinkedList<Integer>(), 1);return res;}public void helper(int k, int n, LinkedList<Integer> path , int fromIndex){if(k == 0){res.add(new LinkedList(path));return;}for(int i = fromIndex; i <= n ;i ++){path.add(i);helper(k - 1, n, path, i +1);path.removeLast();}}
}

N皇后问题

n个皇后不冲突的放在n*n 的棋盘上

注意:对角线也不能重复

提示: 按行放置

class Solution {List<List<String>> res = new ArrayList<List<String>>();public List<List<String>> solveNQueens(int n) {char[][] path = new char[n][n];for(int i = 0; i < n;i++){for(int j= 0;j < n;j ++){path[i][j] = '.';}}helper(path, 0, n, new HashSet<Integer>(), new HashSet<Integer>(), new HashSet<Integer>());return res;}public void helper(char[][] path, int row, int n, Set<Integer> cols, Set<Integer> angles1, Set<Integer> angles2){if(row == n){List<String> tmp = new ArrayList<String>();for(int i = 0; i < n; i ++){StringBuilder sb = new StringBuilder();for(int j = 0; j < n; j ++){sb.append(path[i][j]);}tmp.add(sb.toString());}res.add(tmp);return;}for(int i = 0;i < n;i++){if(cols.contains(i)){// 纵向不能重复continue;}if(angles1.contains(row - i)){// 左上 - 右下 对角线不能重复continue;}if(angles2.contains(row + i)){// 左下 - 右上 对角线不能重复continue;}cols.add(i);angles1.add(row - i);angles2.add(row+i);path[row][i] = 'Q';helper(path, row+1, n, cols, angles1, angles2);cols.remove(i);angles1.remove(row - i);angles2.remove(row+i);path[row][i] = '.';}}
}

刷题

113.路径总和 II

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。 说明: 叶子节点是指没有子节点的节点。 思路:回溯算法,路径/当前可选择/选择及撤销选择

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {List<List<Integer>> res = new ArrayList<List<Integer>>();public List<List<Integer>> pathSum(TreeNode root, int sum) {List<Integer> path = new ArrayList<Integer>();if(root != null){path.add(root.val);}recurve(root, sum, path);return res;}public void recurve(TreeNode root, int sum, List<Integer> path){if(root == null){return;}if(root.left == null && root.right == null && sum == root.val){// 到叶子节点res.add(new ArrayList<Integer>(path));return;}if(root.left != null){path.add(root.left.val);recurve(root.left, sum - root.val, path);path.remove(path.size() - 1);}if(root.right != null){path.add(root.right.val);recurve(root.right, sum - root.val, path);path.remove(path.size() - 1);}}
}

31.下一个排列

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。

1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

提示: 从后往前; 顺序对; 大于a的最小的数

class Solution {public void nextPermutation(int[] nums) {int i;for(i = nums.length-1; i>=1; i -- ){// 从后向前找第一个逆序对if(nums[i] > nums[i-1]){// 从后向前找到最小的比它大的数字for(int j = nums.length - 1; j >= i; j --){if(nums[j] >  nums[i - 1]){// 交换int tmp = nums[j];nums[j] = nums[i-1];nums[i - 1] = tmp;break; }}break;}}// 从i开始翻转排序for(int j = i, k = nums.length - 1; j < k; j ++,k--){int tmp = nums[j];nums[j] = nums[k];nums[k] = tmp;}}
}

47. 全排列 II

给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

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

提示: 回溯,剪枝,数字次数

class Solution {List<List<Integer>> res = new ArrayList<List<Integer>>();public List<List<Integer>> permuteUnique(int[] nums) {Map<Integer, Integer> cnt = new HashMap<Integer, Integer>(); // 记录递归过程中哪些数字被用过了for(int i = 0; i < nums.length; i++){cnt.put(nums[i], cnt.getOrDefault(nums[i], 0) + 1);}LinkedList<Integer> path = new LinkedList<Integer>();helper(nums, path, cnt);return res;}public void helper(int[] nums, LinkedList<Integer> path, Map<Integer, Integer> cnt){if(path.size() == nums.length){res.add(new LinkedList(path));return;}Set<Integer> set = new HashSet<Integer>(); // 记录同一层有没有遍历过for(int i = 0; i < nums.length; i++){if(set.contains(nums[i])){continue; // 重复的数}if(cnt.get(nums[i]) == 0){continue; // 判断是否已经被用完}path.add(nums[i]);set.add(nums[i]);cnt.put(nums[i], cnt.get(nums[i]) - 1);helper(nums, path, cnt);path.removeLast();cnt.put(nums[i], cnt.get(nums[i]) + 1);}}
}

996. 正方形数组的数目

给定一个非负整数数组 A,如果该数组每对相邻元素之和是一个完全平方数,则称这一数组为正方形数组。

返回 A 的正方形排列的数目。两个排列 A1 和 A2 不同的充要条件是存在某个索引 i,使得 A1[i] != A2[i]。

提示: 思路同上

class Solution {int res = 0;public int numSquarefulPerms(int[] A) {if(A.length == 0){return res;}Map<Integer, Integer> cnt = new HashMap<Integer, Integer>(); // 记录递归过程中哪些数字被用过了for(int i = 0; i < A.length; i++){cnt.put(A[i], cnt.getOrDefault(A[i], 0) + 1);}helper(A, cnt, -1, 0);return res;}public void helper(int[] nums, Map<Integer, Integer> cnt, int last, int used){if(used == nums.length){res++;return;}Set<Integer> set = new HashSet<Integer>(); // 记录同一层有没有遍历过for(int i = 0; i < nums.length; i++){if(set.contains(nums[i])){continue; // 重复的数}if(cnt.get(nums[i]) == 0){continue; // 判断是否已经被用完}if(last >= 0 && !isSquare(nums[i] + last)){continue; //判断是否符合题目条件}set.add(nums[i]);cnt.put(nums[i], cnt.get(nums[i]) - 1);used ++;helper(nums, cnt, nums[i], used );cnt.put(nums[i], cnt.get(nums[i]) + 1);used--;}}public boolean isSquare(int num){int i;for(i = 0; i*i < num; i++){}if(i * i == num){return true;}return false;}
}

http://www.ppmy.cn/ops/167753.html

相关文章

[杂学笔记]锁为什么影响效率、I/O多路复用、三种I/O多路复用模型的区别、atomic原子操作类、MySQL的持久性是如何实现的

目录 1.锁为什么影响效率 2.I./O多路复用 3.三种I/O多路复用模型的区别 4.atomic原子操作类 介绍 常用函数 内存顺序含义 5.MySQL持久性的实现 1.锁为什么影响效率 线程阻塞与上下文切换&#xff1a;在多线程并发访问的场景下&#xff0c;只有一个线程能够进入临界区…

C/S模型-TCP

下图是基于TCP协议的客户端/服务器程序的一般流程&#xff1a; TCP协议通讯流程 服务器调用socket()、bind()、listen()完成初始化后&#xff0c;调用accept()阻塞等待&#xff0c;处于监听端口的状态&#xff0c;客户端调用socket()初始化后&#xff0c;调用connect()发出SY…

理解 Node.js 中的 process`对象与常用操作

理解 Node.js 中的 process 对象与常用操作 在 Node.js 中&#xff0c;process 是一个全局对象&#xff0c;提供了与当前 Node.js 进程相关的信息和操作。无论是获取进程信息、处理信号、访问环境变量&#xff0c;还是控制进程行为&#xff0c;process 都是不可或缺的工具。 看…

二次向用户申请授权

HarmonyOS 5.0.3(15) 版本的配套文档&#xff0c;该版本API能力级别为API 15 Release 文章目录 当应用通过requestPermissionsFromUser()拉起弹框请求用户授权时&#xff0c;用户拒绝授权。应用将无法再次通过requestPermissionsFromUser()拉起弹框&#xff0c;需要用户在系统应…

【Linux】交叉编译2

一、文章背景 疑惑 官方提供的SDK包的结构如下&#xff1a; hugohugo-virtual-machine:~$ tree -L 2 SDK SDK ├── environment-setup-aarch64-poky-linux ├── site-config-aarch64-poky-linux ├── sysroots │ ├── aarch64-poky-linux │ ├── aarch64-poky-li…

如何通过Python实现自动化任务:从入门到实践

在当今快节奏的数字化时代,自动化技术正逐渐成为提高工作效率的利器。无论是处理重复性任务,还是管理复杂的工作流程,自动化都能为我们节省大量时间和精力。本文将以Python为例,带你从零开始学习如何实现自动化任务,并通过一个实际案例展示其强大功能。 一、为什么选择Pyt…

设计模式,如单例模式、观察者模式在什么场景下使用

以下是单例模式和观察者模式的介绍及应用场景&#xff1a; 单例模式 - 定义&#xff1a;保证一个类仅有一个实例&#xff0c;并提供一个全局访问点。 - 实现方式&#xff1a;私有化构造函数&#xff0c;防止外部实例化&#xff1b;提供一个静态成员函数来获取唯一实例。 - 应用…

STM32 —— 嵌入式系统、通用计算机系统、物联网三层架构

目录 一、嵌入式系统的概念 二、通用计算机系统与嵌入式系统的比较 用途 硬件 软件 性能与功耗 开发与维护 三、嵌入式系统与物联网的关系 四、物联网的三层架构 1. 感知层&#xff08;Perception Layer&#xff09; 2. 网络层&#xff08;Network Layer&#xff09; …