枚举问题的两大利器:深度优先搜索(DFS)与下一个排列(Next Permutation)算法详解(Java版本)(漫画解析)

枚举问题的两大利器:深度优先搜索(DFS)与下一个排列(Next Permutation)算法详解(Java版本)(漫画解析)

枚举问题的两大利器:深度优先搜索(DFS)与下一个排列(Next Permutation)算法详解

一、引言:枚举问题的核心挑战

在算法竞赛与工程实践中,暴力枚举常是解决排列/组合问题的兜底方案。然而,当问题规模扩大(如 n > 10)时,直接生成所有排列会导致 O(n!) 时间复杂度,极易超时。此时,DFS回溯Next Permutation成为两大高效解法:

  • DFS:通过递归+剪枝实现灵活枚举,适合需动态过滤的场景

Next Permutation:原地生成字典序排列,空间高效且常数极小

在这里插入图片描述
典型场景:LeetCode : 46(全排列)47(带重复元素的全排列)31(下一个排列)60(第k个排列)

二、深度优先搜索(DFS):回溯法的灵活枚举

1. 核心思想与剪枝机制

DFS 通过递归探索状态空间树,核心逻辑如下:

  1. 选择:从未使用元素中选一个加入当前路径
  2. 递归:进入下一层搜索
  3. 回溯:撤销选择,尝试其他分支

关键优势:剪枝
在递归过程中动态判断是否满足题目约束(如和为特定值、无连续重复等),提前终止无效分支,显著减少搜索空间。

剪枝示例
题目要求排列中不能有连续偶数

2. 代码实现(含完整注释)

importjava.util.*;publicclassPermutationDFS{publicList<List<Integer>>permute(int[] nums){List<List<Integer>> res =newArrayList<>();List<Integer> path =newArrayList<>();boolean[] used =newboolean[nums.length];dfs(nums, used, path, res);return res;}privatevoiddfs(int[] nums,boolean[] used,List<Integer> path,List<List<Integer>> res){// 终止条件:路径长度等于数组长度(生成一个完整排列)if(path.size()== nums.length){ res.add(newArrayList<>(path));return;}for(int i =0; i < nums.length; i++){// 跳过已选元素(避免重复)if(used[i])continue;// 【剪枝优化】若当前元素与前一个相同且前一个未被使用(处理重复元素)if(i >0&& nums[i]== nums[i-1]&&!used[i-1])continue;// 1. 选择当前元素 path.add(nums[i]); used[i]=true;// 2. 递归下一层dfs(nums, used, path, res);// 3. 回溯:撤销选择 path.remove(path.size()-1); used[i]=false;}}}

3. 复杂度与适用性

指标说明
时间复杂度O(n!)(需生成所有排列)剪枝可大幅优化(如题目有约束条件)
空间复杂度O(n)(递归栈深度 + used 数组 + 路径存储)不含结果集
适用场景✅ 需动态剪枝的复杂枚举✅ 组合/子集问题(非纯排列)❌ 重复元素需额外处理
关键提示:处理重复元素时,必须先排序,并在循环中加入 if (i > 0 && nums[i] == nums[i-1] && !used[i-1]) 判断。
在这里插入图片描述

三、下一个排列(Next Permutation):字典序的高效生成

1. 算法原理(核心思想:贪心+反转)

算法严格按字典序生成下一个排列,步骤如下(以 [1,2,5,4,3] 为例):

  1. 从右向左找第一个降序点
    i = 1nums[1]=2 < nums[2]=5 → 降序点在 i=1,因 [5,4,3] 为降序)
  2. 从右向左找第一个大于 nums[i] 的数
    j = 3nums[3]=4 > 2
  3. 交换 nums[i]nums[j][1,4,5,2,3]
  4. 反转 i 后所有元素[1,4,2,3,5](最小字典序)
为什么有效?步骤1:确保 i 之后的序列是最大字典序(无法再增大)步骤2:找到最小可能增大的数步骤4:反转使 i 后序列最小化(升序)

2. 代码实现(含关键注释)

publicclassNextPermutation{publicvoidnextPermutation(int[] nums){int n = nums.length;int i = n -2;// 从倒数第二位开始// 步骤1:找第一个降序点 (i < i+1)while(i >=0&& nums[i]>= nums[i +1]){ i--;}if(i >=0){int j = n -1;// 步骤2:找第一个大于 nums[i] 的数 (nums[j] > nums[i])while(j >=0&& nums[j]<= nums[i]){ j--;}swap(nums, i, j);// 步骤3:交换}// 步骤4:反转 i+1 到末尾(使序列最小化)reverse(nums, i +1, n -1);}privatevoidswap(int[] nums,int i,int j){int temp = nums[i]; nums[i]= nums[j]; nums[j]= temp;}privatevoidreverse(int[] nums,int start,int end){while(start < end){swap(nums, start++, end--);}}}

3. 使用流程与复杂度

// 生成全排列示例Arrays.sort(nums);// 先排序成最小字典序do{// 处理当前排列 nums}while(nextPermutation(nums));// 直到返回 false(已到最大排列)
指标说明
时间复杂度O(n)(单次操作)全排列总时间 O(n·n!)(比 DFS 的 O(n!) 更优)
空间复杂度O(1)(原地修改,无需额外空间)
重复元素天然去重(字典序自动跳过重复排列)
适用场景✅ 单纯枚举全排列✅ 求第k个字典序排列✅ 要求空间极小的场景
重要特性nextPermutation自动跳过重复排列,无需额外处理(如 [1,1,2] 会生成 [1,2,1] 而非重复 [1,1,2])。
在这里插入图片描述

四、两种方案的深度对比与选择指南

维度DFS回溯Next Permutation
核心思想递归探索+动态剪枝贪心+反转(字典序生成)
空间复杂度O(n)(需 used 数组 + 递归栈)O(1)(原地修改)
时间效率依赖剪枝效果(最坏 O(n!)稳定 O(n)/次(常数极小)
重复元素处理需排序+额外剪枝(if (i>0 && nums[i]==nums[i-1] && !used[i-1])天然支持(自动去重)
灵活性✅ 极高(可随时加入复杂约束)❌ 仅限排列枚举(无法剪枝)
代码可读性逻辑清晰,易理解逻辑精巧,需理解贪心思想
典型场景1. 需求组合/子集(非纯排列)2. 动态约束(如和为K)1. 全排列枚举2. 求第k个排列3. 严格字典序需求

选择建议

问题特征推荐方案原因
需要剪枝(如组合和为特定值)DFSDFS可灵活加入约束条件,避免无效枚举
仅需枚举全排列(无额外约束)Next Permutation空间效率高,常数更小,避免递归开销
有重复元素且需去重Next Permutation自动处理重复,DFS需额外排序+剪枝
需求第k个字典序排列Next Permutation直接迭代 k 次即可(DFS需生成前 k-1 个排列)
数据规模 n ≤ 10DFS 或 NextPermutation两者均可,DFS更易写
数据规模 n > 15Next PermutationDFS O(n!) 会超时(15! ≈ 1.3e12 次操作)
在这里插入图片描述

五、结语:掌握工具,精准匹配问题

  • DFS 是“瑞士军刀”
    适用于复杂约束的枚举问题(如子集和、路径规划),灵活性是其核心价值。当数据量小(n ≤ 10)或需动态剪枝时,DFS 是首选。
  • Next Permutation 是“高效引擎”
    适用于纯字典序枚举场景,空间效率与时间常数优势显著。在 n > 12 时,其 O(n)/次 的特性可避免 DFS 的 O(n!) 瓶颈。
终极建议:遇到排列问题,先判断是否需要剪枝 → 需剪枝选 DFS,否则选 Next Permutation重复元素问题 → 优先选 Next Permutation(避免 DFS 的去重逻辑)生成全排列时,Next Permutation 比 DFS 快 2-3 倍(实测 n=15 时)
在这里插入图片描述

Read more

Sonic表情生成算法基于何种神经网络?Transformer+CNN混合

Sonic表情生成算法的神经网络架构解析:Transformer与CNN的协同之道 在虚拟数字人技术加速落地的今天,如何以极低成本生成高保真、自然流畅的说话视频,成为各大科技公司和内容平台竞相突破的关键命题。传统依赖3D建模与动作捕捉的工作流不仅成本高昂,且制作周期长,难以适应短视频时代“快、准、稳”的内容生产节奏。正是在这一背景下,腾讯联合浙江大学推出的Sonic模型应运而生——它仅需一张静态人像与一段音频,即可端到端生成唇形精准同步、表情细腻自然的数字人视频。 其背后的技术核心,并非单一的深度学习结构,而是Transformer与CNN的混合架构设计。这种组合并非简单拼接,而是基于对语音时序特性和人脸空间结构的深刻理解,实现了跨模态生成中的“各司其职、高效协同”。 用Transformer捕捉声音的时间韵律 语音本质上是一种高度结构化的时间序列信号。不同的音素(如/p/、/m/、/a/)对应特定的嘴型变化,而这些变化又受到上下文语境的影响——比如“sp”中的/p/发音比单独念/p/更轻更快。要准确还原这种动态过程,模型必须具备强大的长程依赖建模能力。 早期方法多采用LS

By Ne0inhk
力扣校招算法通关:双指针技巧全场景拆解 —— 从数组操作到环检测的高效解题范式

力扣校招算法通关:双指针技巧全场景拆解 —— 从数组操作到环检测的高效解题范式

文章目录 * 前言 * 双指针 * 例题讲解 * 移动零 力扣 * 复写零 力扣 * 快乐数 力扣 * 盛最多水的容器 力扣 * 有效三角形的个数 力扣 * 查找总价格为目标值的两个商品 力扣 * 三数之和 力扣 前言 在力扣校招算法题中,双指针技巧是一类高频且实用的解题方法。它并非真正的 “指针”,而是通过两个数组下标(或迭代器)的协同移动,在数组划分、区间求解、环检测等场景中实现高效遍历与逻辑处理,往往能将时间复杂度从暴力法的 O(n平方)优化至O(n),是校招笔试和面试中突破数组类难题的关键武器。 本专栏将围绕力扣校招高频的双指针题型展开,从 “移动零”“复写零” 的数组操作,到 “快乐数” 的环检测、“盛最多水的容器” 的区间优化,再到 “三数之和” 的多指针协同,逐一拆解双指针的核心逻辑、边界处理与去重技巧,

By Ne0inhk
【算法】【优选算法】BFS 解决 FloodFill 算法

【算法】【优选算法】BFS 解决 FloodFill 算法

目录 * 一、FloodFill 算法简介 * 二、733. 图像渲染 * 三、200. 岛屿数量 * 四、695. 岛屿的最⼤⾯积 * 五、130.被围绕的区域 一、FloodFill 算法简介 FloodFill 算法,可以理解为根据洪水蔓延,以点覆面,在这个过程中对涉及的一些问题求解。 二、733. 图像渲染 题目链接:733. 图像渲染 题目描述: 题目解析: * 相当于给我们一个二维数组,和指定一个数组中的值为“母体”,当该位置上下左右四个方向初始值相同,就改为指定值(被传染),依次类推下去,直到全部感染或者传染不了。 解题思路: * 利⽤「深搜」或者「宽搜」,遍历到与该点相连的所有「像素相同的点」,然后将其修改成指定的像素即可. * 也就是我们使用一个队列,

By Ne0inhk
【优选算法必刷100题】第038题(位运算):消失的两个数字

【优选算法必刷100题】第038题(位运算):消失的两个数字

🔥个人主页:Cx330🌸 ❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》 《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔 🌟心向往之行必能至 🎥Cx330🌸的简介: 目录 前言: 38. 消失的两个数字 算法原理(位运算): 思路: 位运算解法代码(C++): 代码一:位图 代码二:异或 博主手记(字体还请见谅哈): 总结: 前言: 聚焦算法题实战,系统讲解三大核心板块:“精准定位最优解”——优选算法,“简化逻辑表达,系统性探索与剪枝优化”——递归与回溯,“以局部最优换全局高效”——贪心算法,讲解思路与代码实现,帮助大家快速提升代码能力 位运算专题 38. 消失的两个数字 题目链接: 面试题 17.19. 消失的两个数字

By Ne0inhk