当前位置: 首页 > news >正文

回溯——全排列

题目要求:
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

核心逻辑
选一个数 -> 标记已用-> 递归-> 撤销选择(回溯)-> 继续选

1. path:当前排列的路径
比如[1,2]正在拼,下一步加3->[1,2,3]
2. used[]:标记数字是否用过
避免同一个数字在一个排列里出现两次
3. 回溯:撤销选择
path.remove(最后一个)
used[i] = false
回去换另一个数字继续尝试

一句话总结
全排列 = 回溯 + 标记 used + 路径 path
这是所有回溯题的模板!

完整代码实现如下:

import java.util.*;

class Solution {

// 存放最终结果
List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> permute(int[] nums) {// 记录当前路径List<Integer> path = new ArrayList<>();// 标记数字是否用过boolean[] used = new boolean[nums.length];backtrack(nums, path, used);return res;
}// 回溯核心方法
private void backtrack(int[] nums, List<Integer> path, boolean[] used) {// 出口:路径长度 == 数组长度 → 找到一个排列if (path.size() == nums.length) {res.add(new ArrayList<>(path)); // 必须 new!return;}// 遍历所有选择for (int i = 0; i < nums.length; i++) {// 用过的跳过if (used[i]) continue;// 选择used[i] = true;path.add(nums[i]);// 递归backtrack(nums, path, used);// 撤销选择(回溯)path.remove(path.size() - 1);used[i] = false;}
}

}

代码解释

1. 为什么要 new ArrayList<>(path)?
因为path是同一个List对象,从头到尾都在变:
加元素、删元素、再加、再删
你 res.add(path) 只是把这个对象的引用存进去了。到最后回溯全部结束时,path又变回空了,
所以你存进去的所有引用,指向的都是最后那个空list

2. new ArrayList<>(path) 到底干了啥?
它是拷贝一份当前的path,生成一个新对象,创建了一个副本,这样下次对path修改时,并不会改变副本的内容

3. 整个递归回溯的过程解释:

假设数组为[1,2,3]
首先for遍历到1,把1标记为true后加入到path数组中,之后递归查找1开头的所有排列,第一层递归for循环遍历到2,把2标记为true后加入到path,之后再进入第二层递归,for循环遍历到3,将3标记为ture后加入到path中,之后进入第三层递归,此时path.size()==nums.length。将新建一个数组对象,并赋值path。new ArrayList<>(path)并加入到结果数组res中。执行return,退出返回到第二层递归,执行回溯,将3移除path,并且标记为false。再返回第一层递归,将2移除,标记为false。在当前层的递归继续执行for循环,for遍历到3,将3加入path并标记为true,再执行递归,for遍历到2后将2加入path并标记,再执行递归,将path加入到res中,返回上层递归,将2删除并标记为false。因为这一层的递归for遍历到2,因此for需要接着遍历到3,因为3已经标记为true了,退出for循环。回到第一层递归,将3移除后标记为false。因为第一层的递归,for循环已经遍历到3了,退出循环,返回到第0层递归方法,执行回溯,将1删除并标记为false。第0层for循环继续遍历到2,将2标记为true并加入到path中,执行递归找到2开头的全排列。以此类推,找到3开头的全排列。最后path回溯删除全部元素后,for循环已经遍历到头,path最终为空数组。因此每次将path添加到res结果数组当中的时候,需要创建一个新对象,将path拷贝到其中,即:new ArrayList<>(path)

http://www.jsqmd.com/news/681887/

相关文章:

  • 从MATLAB到Cadence:一个完整CTSDM数模混合芯片的后端验证避坑实录
  • 告别EV2400?手把手教你用STM32F407模拟BQ34Z100对BQ34Z100进行参数配置与读写
  • 别再手动写移位寄存器了!Vivado里这个RAM-Based Shift Register IP核,5分钟搞定数据延时
  • moto 新机到手别乱设置!3 步官方教程,快速上手更流畅
  • 别再死记硬背了!用Python模拟光纤色散如何让信号‘变形’(附代码)
  • 从调试到模板:手把手教你用typeid和decltype搞定C++复杂类型推导(附VS2022实战)
  • 终极指南:3分钟掌握Easy-Scraper,用HTML思维轻松提取网页数据
  • 2026年必备技能:AI成论文第一作者后,如何降AI率 - 降AI实验室
  • 从‘羊车门问题’到‘新冠检测’:贝叶斯公式的5个生活化案例,彻底搞懂条件概率
  • LinkSwift架构深度解析:八大网盘直链获取与下载优化技术实现
  • Building Tools插件终极教程:Blender建筑建模高效指南
  • 保姆级拆解:YOLOv7从tiny到e6e,7个模型结构图到底差在哪?
  • 当数字记忆开始呼吸:用WeChatMsg让聊天记录重获生命
  • 告别Vivado卡顿:用Docker+Jupyter在Ubuntu 18.04上丝滑搭建FINN开发环境(保姆级避坑指南)
  • Win11家庭版+RTX 3050 Ti显卡:保姆级CUDA 11.3与cuDNN配置避坑指南
  • League Akari:英雄联盟玩家的智能效率工具箱,全面解决游戏痛点
  • MIMO系统误码率分析避坑指南:手把手教你用MATLAB仿真ZF、MMSE和ML检测算法
  • Windows下llama-cpp-python CUDA编译终极指南:从无限循环到流畅部署
  • 深入浅出聊5G DMRS:从Gold序列到ZC序列,如何为你的上行传输选择最佳参考信号?
  • 别再乱用shutdown了!Java线程池优雅关闭的3种正确姿势与避坑指南
  • PKHeX自动合法性插件:轻松创建100%合规宝可梦的终极指南
  • 从一次‘Permission denied’报错讲起:手把手教你用chmod命令修复Linux下的文件权限问题
  • 保姆级教程:用STM32F4和ROS Noetic搭建你的第一个机器人底盘(附串口通信代码)
  • Fan Control完整指南:5分钟掌握Windows风扇智能控制终极方案
  • 如何快速搭建现代化企业级后台管理系统:Ant Design Vue3 Admin终极指南
  • Qt信号与状态管理:从clicked()到toggled()的实战解析与setCheckable/Checked的正确使用
  • 监控越做越多,问题却越来越难找?你可能缺的不是工具,而是 Observability
  • 华为eNSP模拟器实战:三层交换机MSTP配置避坑与负载均衡效果验证
  • 别再死记硬背AES了!用Python手搓一个S盒变换,理解分组密码的数学之美
  • 别再为授权费头疼了!手把手教你免授权采集马扎克、西门子等12种主流数控机床数据(附避坑清单)