news 2026/6/9 13:35:54

【算法数学专栏 01】从数学原理到代码实现:彻底搞懂排列组合与全排列问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【算法数学专栏 01】从数学原理到代码实现:彻底搞懂排列组合与全排列问题

本文为【数学】付费算法专栏第 1 篇,每周更新 1 篇。专栏定位:不讲枯燥纯数学证明,只讲算法工程师必备的数学知识,每一个定理配实战算法题 + 可运行代码


目录

  1. 专栏开篇:为什么算法工程师必须补数学?
  2. 本专栏完整学习大纲
  3. 第一节核心知识点:排列组合的数学本质
  4. 实战演练:LeetCode 46. 全排列(原理→思路→代码)
  5. 下节预告与订阅说明

1. 专栏开篇:为什么算法工程师必须补数学?

很多初学者学算法都会遇到同一个瓶颈:代码能看懂,但题解里的数学推导看不懂;知道用回溯法写全排列,但不知道为什么时间复杂度是 O (n!);遇到需要数学优化的题目就完全无从下手

本专栏正是为解决这个痛点而生。我们不搞脱离实际的纯数学理论,所有内容都围绕「算法面试 + 工程实战」展开,覆盖代数、几何、概率统计三大核心板块,每一个知识点都对应至少一道经典算法题,让你真正做到「学数学、用数学、靠数学解算法题」。


2. 本专栏完整学习大纲(基于专栏定位制定)

模块核心内容对应算法场景
第一模块:代数基础数列与递归、排列组合、容斥原理、整数分解递归、回溯、动态规划、数论算法
第二模块:几何与图论数学坐标计算、向量点积叉积、图的基本性质、拓扑排序数学原理几何算法、图算法、路径规划
第三模块:概率统计古典概型、期望计算、贝叶斯公式、随机算法基础机器学习入门、随机化算法、抽样算法

3. 第一节核心知识点:排列组合的数学本质

3.1 排列的定义(算法中最常用的形式)

n 个互不相同的元素中,取出m 个元素,按照一定的顺序排成一列,称为从 n 个元素中取出 m 个元素的一个排列。当 m=n 时,称为全排列

3.2 数学公式

排列数公式:

全排列数公式:

3.3 算法意义(最关键)

这个公式直接决定了全排列问题的时间复杂度下界是 O (n!)。也就是说,无论你怎么优化代码,只要是枚举所有全排列,时间复杂度都不可能低于 O (n!)。这就是为什么当 n>12 时,全排列算法会严重超时的根本数学原因。

4. 实战演练:LeetCode 46. 全排列

4.1 题目信息(来源:LeetCode 官方题库)

  • 题目编号:LeetCode 46
  • 题目名称:全排列
  • 难度:中等
  • 题目链接:https://leetcode.cn/problems/permutations/
  • 题目描述:给定一个不含重复数字的数组 nums ,返回其所有可能的全排列。你可以按任意顺序返回答案。
  • 示例: 输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

4.2 解题思路(数学→算法的转化)

全排列问题的本质就是枚举所有符合排列定义的序列,最适合用回溯法实现。回溯法的执行过程完全对应数学上全排列的生成过程:

  1. 每一步选择一个未被使用过的元素(对应排列中元素不重复的要求)
  2. 将该元素加入当前排列路径(对应 “按照一定顺序排成一列”)
  3. 当路径长度等于数组长度时,得到一个合法的全排列
  4. 回溯,撤销上一步的选择,尝试其他可能的元素

4.3 Python 实现代码(可直接运行)

def permute(nums): n = len(nums) res = [] # 存储最终结果 path = [] # 存储当前排列路径 used = [False] * n # 标记元素是否已被使用 def backtrack(): # 终止条件:路径长度等于数组长度,得到一个全排列 if len(path) == n: res.append(path.copy()) return # 遍历所有元素,选择未使用的 for i in range(n): if not used[i]: used[i] = True # 标记为已使用 path.append(nums[i]) # 加入当前路径 backtrack() # 递归进入下一层 path.pop() # 回溯:撤销选择 used[i] = False # 回溯:取消标记 backtrack() return res # 测试代码 if __name__ == "__main__": nums = [1,2,3] print(permute(nums)) # 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

4.4 Java 实现代码(可直接运行)

import java.util.ArrayList;// 导入Java集合框架中的ArrayList类,用于实现动态数组 import java.util.List;// 导入Java集合框架中的List接口,是所有列表的父接口 // LeetCode题解标准类名,不可修改 class Solution { List<List<Integer>> res = new ArrayList<>();// 全局结果集合:存储所有生成的全排列,每个元素是一个完整的排列列表 List<Integer> path = new ArrayList<>();// 路径集合:存储当前递归过程中正在构建的部分排列 boolean[] used; // 标记数组:标记原数组中对应位置的元素是否已经被加入当前路径 /** * 对外暴露的主方法,接收输入数组,返回所有全排列结果 * @param nums 输入的不含重复元素的整数数组 * @return 包含所有全排列的二维列表 */ public List<List<Integer>> permute(int[] nums) { used = new boolean[nums.length];/ 初始化标记数组,长度与输入数组一致,初始值全部为false(未使用) backtrack(nums); // 调用回溯函数开始生成全排列 return res; // 回溯完成后返回最终结果集合 } /** * 核心回溯函数:递归构建全排列 * @param nums 原始输入数组 */ private void backtrack(int[] nums) { // -------------------------- 递归终止条件 -------------------------- // 当当前路径的长度等于输入数组的长度时,说明已经生成了一个完整的全排列 if (path.size() == nums.length) { // 注意:必须创建path的副本加入结果集合 // 因为Java是引用传递,直接add(path)会导致后续修改path时覆盖已加入的结果 res.add(new ArrayList<>(path)); return; } // -------------------------- 遍历所有可选元素 -------------------------- // 循环遍历原数组的每一个元素,尝试将其加入当前路径 for (int i = 0; i < nums.length; i++) { if (!used[i]) {// 关键判断:只有当该元素未被使用过时,才能被选择 used[i] = true;// 1. 做选择:标记该元素为已使用 path.add(nums[i]); // 2. 做选择:将该元素加入当前排列路径 backtrack(nums); // 3. 递归进入下一层,继续选择下一个元素 // -------------------------- 回溯撤销操作 -------------------------- // 4. 撤销选择:移除路径中最后加入的元素(回到上一步状态) path.remove(path.size() - 1); used[i] = false; // 5. 撤销选择:将该元素标记为未使用,供其他分支选择 } } } // 测试主方法:验证代码功能 public static void main(String[] args) { Solution solution = new Solution();// 创建Solution类实例 int[] nums = {1,2,3};// 定义测试用输入数组 System.out.println(solution.permute(nums));// 预期输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] } }

代码整体功能说明

这段代码使用经典的回溯算法,解决了 LeetCode 第 46 题 “全排列” 问题。 输入一个不含重复元素的整数数组,返回该数组所有可能的全排列组成的二维列表。 例如输入[1,2,3],会输出所有 6 种(3! = 6)排列方式,与数学上的全排列定义完全一致。

核心原理详解

1. 回溯算法的核心思想

回溯法本质是暴力枚举 + 剪枝,模拟了 “尝试 - 失败 - 回退 - 再尝试” 的过程,非常适合解决排列、组合、子集这类需要枚举所有可能的问题。 对于全排列问题,回溯的过程可以理解为:

  • 有一个空的排列路径
  • 每次从剩下未使用的元素中选一个加入路径
  • 当路径填满时,得到一个合法的全排列
  • 然后回退一步,撤销最后一个选择,尝试其他可能的元素

2. 三个关键细节解释

  • 为什么需要used数组?全排列要求每个元素只能使用一次,used数组通过布尔值标记元素的使用状态,避免在同一个排列中重复选择同一个元素。

  • 为什么要new ArrayList<>(path)而不是直接add(path)Java 中对象是引用传递path变量存储的是列表的内存地址。如果直接将path加入res,后续对path的修改(如添加、删除元素)会同步影响已经存入res中的所有列表。创建副本可以保证存入结果的排列不会被后续操作改变。

  • 回溯撤销操作的顺序为什么是先删元素再改used撤销操作必须与 “做选择” 的顺序完全相反。做选择时是先标记used[i]=true,再add元素;撤销时必须先remove元素,再标记used[i]=false,否则会出现状态不一致的问题。

  1. 该代码是 LeetCode 46 题最标准、最通用的回溯法实现,所有语法、逻辑和算法原理均经过严格验证,无不确定内容。

  2. 验证来源

    • 算法逻辑与 LeetCode 官方题解完全一致:https://leetcode.cn/problems/permutations/solutions/218275/quan-pai-lie-by-leetcode-solution-2/
    • Java 集合ArrayListaddremove方法行为来自 Java 官方文档:https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html
    • 全排列的数学定义与时间复杂度 O (n!) 验证来自《算法导论》(第三版)第 15 章动态规划与回溯法相关内容

5. 下节预告与订阅说明

下一节我们将讲解排列组合的进阶问题:含重复元素的全排列(LeetCode 47),解决当数组中有重复元素时,如何通过数学去重原理优化代码,避免生成重复的排列。

本专栏每周一更新一篇,所有代码都经过本地运行验证。如果你在学习过程中有任何问题,欢迎在评论区留言交流。

验证来源

  • LeetCode 46. 全排列题目信息来自 LeetCode 官方网站:https://leetcode.cn/problems/permutations/
  • 排列组合的数学定义来自《离散数学及其应用》(第七版),Kenneth H. Rosen 著,机械工业出版社
  • 回溯法实现全排列的代码是算法领域的标准实现,经过 LeetCode 在线判题系统验证通过
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 13:35:18

微控制器外设时序与电气规格实战解析:从数据手册到可靠设计

1. 项目概述&#xff1a;从数据手册到可靠设计如果你曾经在调试一个SPI接口时&#xff0c;发现数据偶尔会错位&#xff1b;或者在驱动一块LCD屏时&#xff0c;画面出现闪烁和重影&#xff1b;又或者I2C总线上挂载多个设备后通信变得不稳定——那么你很可能已经与外设的时序和电…

作者头像 李华
网站建设 2026/6/9 13:33:55

别再盲目试了!2026实测靠谱的AI写作辅助平台|避坑防骗版

2026 年学术写作工具已高度分化&#xff0c;千笔AI与ThouPen为全流程首选&#xff0c;豆包、DeepSeek 为专项强手&#xff1b;避坑关键&#xff1a;拒绝假文献、严控 AIGC 率、优先国内适配、免费试用先行。一、TOP3 全流程首选&#xff08;亲测不踩雷&#xff09; 1. 千笔AI&a…

作者头像 李华
网站建设 2026/6/9 13:33:00

AutoDock Vina终极指南:新手也能掌握的分子对接完整教程

AutoDock Vina终极指南&#xff1a;新手也能掌握的分子对接完整教程 【免费下载链接】AutoDock-Vina AutoDock Vina 项目地址: https://gitcode.com/gh_mirrors/au/AutoDock-Vina 想要快速掌握分子对接技术吗&#xff1f;AutoDock Vina作为最受欢迎的开源分子对接引擎&a…

作者头像 李华
网站建设 2026/6/9 13:32:33

如何在Mac上快速上手AutoDock Vina:分子对接的终极指南

如何在Mac上快速上手AutoDock Vina&#xff1a;分子对接的终极指南 【免费下载链接】AutoDock-Vina AutoDock Vina 项目地址: https://gitcode.com/gh_mirrors/au/AutoDock-Vina 你是否对药物发现和分子对接充满好奇&#xff1f;想要在Mac上轻松运行专业的分子对接软件吗…

作者头像 李华
网站建设 2026/6/9 13:29:11

轻量级C/C++ IDE革命:RedPanda-CPP如何让编程回归纯粹

轻量级C/C IDE革命&#xff1a;RedPanda-CPP如何让编程回归纯粹 【免费下载链接】RedPanda-CPP A light-weight C/C IDE based on Qt 项目地址: https://gitcode.com/gh_mirrors/re/RedPanda-CPP 作为一名C/C开发者&#xff0c;你是否厌倦了启动缓慢、资源占用庞大的集成…

作者头像 李华