【每日算法】LeetCode 46. 全排列

对前端开发者而言,学习算法绝非为了"炫技"。它是你从"页面构建者"迈向"复杂系统设计者"的关键阶梯。它将你的编码能力从"实现功能"提升到"设计优雅、高效解决方案"的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

LeetCode 46. 全排列

1. 题目描述

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

示例:

复制代码
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

约束条件:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数互不相同

2. 问题分析

2.1 问题本质

全排列问题是计算机科学中的经典回溯问题,要求生成给定集合中所有元素的所有可能排列 。对于n个不同元素,共有n! 种排列。

2.2 前端场景关联

  1. 路由权限配置:根据用户角色动态生成不同的页面访问路径组合
  2. 数据可视化:多维数据的展示顺序排列
  3. 表单组合:动态表单字段的展示顺序管理
  4. 商品推荐:多商品在多位置的推荐位排列

3. 解题思路

3.1 核心思路对比

方法 时间复杂度 空间复杂度 是否最优解
回溯法(路径记录) O(n×n!) O(n)
交换法(原地交换) O(n×n!) O(n)

最优解推荐:回溯法(路径记录)是最直观且易于理解的方法,适合面试和实际开发。

3.2 算法思路详解

3.2.1 回溯法(路径记录)

使用深度优先搜索(DFS)构建排列树,通过used数组记录已使用的元素,path数组记录当前路径。

3.2.2 交换法(原地交换)

通过在原数组上交换元素位置来生成排列,减少空间使用。

4. 代码实现

4.1 回溯法(路径记录)- 最优解

javascript 复制代码
/**
 * 回溯法解决全排列问题
 * @param {number[]} nums
 * @return {number[][]}
 */
const permute = function(nums) {
    const result = []; // 存储所有排列结果
    const used = new Array(nums.length).fill(false); // 标记元素是否使用过
    const path = []; // 当前路径
    
    /**
     * 回溯函数
     * @param {number[]} path - 当前路径
     * @param {boolean[]} used - 使用标记数组
     */
    const backtrack = (path, used) => {
        // 终止条件:路径长度等于数组长度
        if (path.length === nums.length) {
            result.push([...path]); // 深拷贝当前路径
            return;
        }
        
        // 遍历所有选择
        for (let i = 0; i < nums.length; i++) {
            // 跳过已使用的元素
            if (used[i]) continue;
            
            // 做选择
            used[i] = true;
            path.push(nums[i]);
            
            // 递归进入下一层
            backtrack(path, used);
            
            // 撤销选择(回溯)
            path.pop();
            used[i] = false;
        }
    };
    
    backtrack(path, used);
    return result;
};

// 测试用例
console.log(permute([1, 2, 3]));
// 输出: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

4.2 交换法(原地交换)- 空间优化版

javascript 复制代码
/**
 * 交换法解决全排列问题
 * @param {number[]} nums
 * @return {number[][]}
 */
const permuteSwap = function(nums) {
    const result = [];
    
    /**
     * 交换法回溯
     * @param {number} start - 当前交换起始位置
     */
    const backtrack = (start) => {
        // 当起始位置到达数组末尾,找到一个排列
        if (start === nums.length) {
            result.push([...nums]); // 深拷贝当前数组
            return;
        }
        
        // 从start位置开始,将每个元素交换到start位置
        for (let i = start; i < nums.length; i++) {
            // 交换元素
            [nums[start], nums[i]] = [nums[i], nums[start]];
            
            // 递归处理下一个位置
            backtrack(start + 1);
            
            // 恢复交换(回溯)
            [nums[start], nums[i]] = [nums[i], nums[start]];
        }
    };
    
    backtrack(0);
    return result;
};

// 测试用例
console.log(permuteSwap([1, 2, 3]));

5. 复杂度对比

实现方法 时间复杂度 空间复杂度 优点 缺点
回溯法(路径记录) O(n×n!) O(n) 直观易懂,易于调试 需要used数组额外空间
交换法(原地交换) O(n×n!) O(1)额外空间 空间效率高 破坏原数组顺序,逻辑稍复杂

复杂度说明

  • 时间复杂度:O(n×n!),因为共有n!种排列,每种排列需要O(n)时间构建
  • 空间复杂度:O(n),主要用于递归调用栈和路径存储

6. 总结与前端应用场景

6.1 核心总结

  1. 回溯法是解决排列组合问题的通用模板,掌握此模板可解决一大类问题
  2. 空间与时间的权衡:交换法空间更优,但回溯法更通用
  3. 递归+回溯是深度优先搜索的典型应用

6.2 前端实际应用场景

6.2.1 动态路由权限控制
javascript 复制代码
// 根据用户权限动态生成路由排列组合
function generateRoutePermutations(routes, userPermissions) {
    const availableRoutes = routes.filter(route => 
        userPermissions.includes(route.permission)
    );
    
    // 生成所有可能的页面访问顺序
    return permute(availableRoutes.map(r => r.path));
}
6.2.2 可视化图表配置
javascript 复制代码
// 多个图表组件的展示顺序排列
const chartComponents = ['lineChart', 'barChart', 'pieChart', 'table'];
const allLayouts = permute(chartComponents);
// 用于A/B测试不同布局效果
6.2.3 表单字段动态排序
javascript 复制代码
// 根据用户习惯优化表单字段顺序
function optimizeFormOrder(fields, userBehaviorData) {
    const permutations = permute(fields);
    // 根据用户行为数据选择最优排列
    return findBestPermutation(permutations, userBehaviorData);
}
6.2.4 测试用例生成
javascript 复制代码
// 生成参数的不同排列组合进行测试
function generateTestCases(params) {
    const paramValues = Object.values(params);
    const permutations = permute(paramValues);
    
    return permutations.map(perm => {
        const testCase = {};
        Object.keys(params).forEach((key, index) => {
            testCase[key] = perm[index];
        });
        return testCase;
    });
}
相关推荐
俊男无期1 小时前
超效率工作法
java·前端·数据库
2301_823438021 小时前
【无标题】解析《采用非对称自玩实现强健多机器人群集的深度强化学习方法》
数据库·人工智能·算法
oscar9991 小时前
CSP-J教程——第二阶段第十二、十三课:排序与查找算法
数据结构·算法·排序算法
刘一说2 小时前
Vue Router:官方路由解决方案解析
前端·javascript·vue.js
wgego2 小时前
Polar靶场web 随记
前端
chao1898442 小时前
MATLAB与HFSS联合仿真
算法
DEMO派2 小时前
深拷贝 structuredClone 与 JSON 方法作用及比较
前端
月明长歌2 小时前
【码道初阶】牛客TSINGK110:二叉树遍历(较难)如何根据“扩展先序遍历”构建二叉树?
java·数据结构·算法
jqrbcts2 小时前
关于发那科机器人视觉补偿报警设置
人工智能·算法