LeetCode 46. 全排列|吃透回溯

一、前言

全排列是回溯算法的第一道母题 ,也是绝大多数新手最真实的状态:看思路秒懂,一写代码全错

二、题目描述

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

示例:nums = [1,2,3]

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

三、错误代码

java 复制代码
class Solution {
    List<List<Integer>> res=new ArrayList<>();
    boolean[] used=new boolean[nums.length]; // 致命错误1
    List<Integer>path=new ArrayList<>();

    public List<List<Integer>> permute(int[] nums) {
        if(nums==null||nums.length==0){
            return res;
        }
        back(nums);
        return res;
    }

    private void back(int[] nums){
        if(path.size()==nums.length){
            res.add(new ArrayList<>(path));
            return ;
        }
        for(int i=0;i<nums.length;i++){
            if(nums[i]==true){ // 致命错误2
                continue;
            }
            if(nums[i]==false){ // 致命错误3
                path.add(nums[i]);
                used[i]=true;
                back(nums);
                path.remove(pathsize()-1); // 致命错误4
                used[i]=false;
            }
        }
    }
}

四、逐条复盘我的所有错误

错误1:类成员位置直接 new used 数组

boolean[] used=new boolean[nums.length];

原因:nums 是方法参数,类加载的时候 nums 还不存在,不能用来初始化成员变量。

正确做法:只声明不初始化,在主方法里根据 nums 长度创建数组。

错误2、3:把 nums[i] 当成布尔标记判断

if(nums[i]==true)if(nums[i]==false)

超级低级逻辑错误

nums[i] 是数字(1、2、3),不是布尔值!

是否被使用,看的是usedi,不是 numsi

正确逻辑 :如果 used[i]==true 说明用过了,直接跳过。

错误4:拼写错误 pathsize()

集合获取长度是 path.size(),不能连写,会编译报错。

错误5:多余嵌套 if

只要判断 used[i] 为 true 就 continue,剩下的一定是未使用,不需要再套一层 if 判断 false。

五、标准代码

java 复制代码
import java.util.ArrayList;
import java.util.List;

class Solution {
    // 结果集、当前路径、标记数组(只声明)
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    boolean[] used;

    public List<List<Integer>> permute(int[] nums) {
        if(nums == null || nums.length == 0){
            return res;
        }
        // 根据输入数组长度初始化标记数组
        used = new boolean[nums.length];
        back(nums);
        return res;
    }

    // 回溯核心函数
    private void back(int[] nums){
        // 终止条件:路径长度等于数组长度,说明凑齐一组排列
        if(path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return;
        }

        // 遍历所有数字,逐个尝试选择
        for(int i = 0; i < nums.length; i++){
            // 已经选过的数字,跳过
            if(used[i]){
                continue;
            }

            // 1. 做选择:加入路径 + 标记占用
            path.add(nums[i]);
            used[i] = true;

            // 2. 递归:继续选下一个位置的数
            back(nums);

            // 3. 回溯(撤销选择):递归跑完回来,恢复状态
            path.remove(path.size() - 1);
            used[i] = false;
        }
    }
}

六、重点攻克:我最看不懂的四行回溯代码

这四行是回溯的灵魂,也是我最开始完全不懂的地方:

java 复制代码
path.add(nums[i]);
used[i]=true; 
back(nums);
path.remove(path.size()-1);
used[i]=false;

通俗大白话解释(终于听懂版)

第一步:做选择

把当前数字放进路径,标记为已使用,下层递归不能再选。

第二步:递归深挖

程序直接扎进下一层,继续填下一个位置,直到路径填满、记录答案。

第三步:递归返回,必须回溯

当这一条分支的所有排列全部找完了,回到当前行

删掉刚刚加的数字、取消占用标记,清空状态,换下一个数字重新选

最简例子走一遍 [1,2]

  1. 选1 → path=1,used0=true,递归深入

  2. 下层只能选2 → path=1,2,存答案

  3. 递归返回,删掉2、取消标记

  4. 循环走完,回到最上层,删掉1、取消标记

  5. 状态清空,开始选2开头的排列

不回溯 = 状态永远不重置 = 只能算出第一个答案

七、回溯核心模板(永久背诵)

所有排列题通用模板:

终止条件:收集答案

for 遍历所有选择:

if 已使用 → 跳过

做选择

递归

撤销选择(回溯)

八、总结

  1. used 是标记下标,和 nums 数组本身无关,绝对不能拿 nums 判断布尔值。

  2. 成员变量不能用方法参数初始化,used 必须在主函数 new。

  3. 回溯核心就是:选进去 → 递归到底 → 退出来重置

  4. 存答案必须 new ArrayList<>(path),保存快照,不能直接存 path。

  5. 撤销顺序必须和选择顺序相反: 选择:add → true 撤销:remove → false

相关推荐
鼎艺创新科技1 小时前
不依赖 UE/Unity:我们如何从零搭建一套国产三维 GIS 渲染引擎
人工智能·算法·unity·游戏引擎·三维电子沙盘
程序员三藏2 小时前
Python+requests实现接口自动化测试
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试
.道阻且长.4 小时前
11.LeetCode算法习题讲解--滑动窗口--将x减到0的最小操作数
算法·leetcode·职场和发展
wenyq74 小时前
LeetCode 2460. Apply Operations to an Array
算法·leetcode
.格子衫.5 小时前
032动态规划之区间DP——算法备赛
算法·动态规划
青 春 记 忆5 小时前
LeetCode 142. 环形链表 II|Python 解法详解
python·leetcode·链表
小欣加油5 小时前
leetcode3069 将元素分配到两个数组中I
数据结构·c++·算法·leetcode·职场和发展
不会代码的小猴6 小时前
7. JSON
开发语言·c++·笔记·qt·算法·json
怪奇云呼军6 小时前
知识库也会注入指令?闪电智能VoiceAgent 如何防住 Prompt Injection
人工智能·python·算法·云计算·音视频