LeetCode 31. 下一个排列

文章目录

  • [LeetCode 31. 下一个排列 - 最简算法实现](#LeetCode 31. 下一个排列 - 最简算法实现)
    • [🎯 核心思路](#🎯 核心思路)
      • [📝 算法步骤(4步法)](#📝 算法步骤(4步法))
    • [💻 最简短代码](#💻 最简短代码)
    • [🎭 最易懂代码(注释版,推荐!!)](#🎭 最易懂代码(注释版,推荐!!))
    • [🎬 可视化演示过程](#🎬 可视化演示过程)
      • [示例1:[1,2,3] → [1,3,2]](#示例1:[1,2,3] → [1,3,2])
      • [示例2:[2,3,1] → [3,1,2]](#示例2:[2,3,1] → [3,1,2])
      • [示例3:[3,2,1] → [1,2,3]](#示例3:[3,2,1] → [1,2,3])
      • [复杂示例:[1,2,7,4,3,1] → [1,3,1,2,4,7]](#复杂示例:[1,2,7,4,3,1] → [1,3,1,2,4,7])
    • [🎨 动画演示](#🎨 动画演示)
    • [📊 算法流程图](#📊 算法流程图)
    • [🎨 数组变化可视化](#🎨 数组变化可视化)
    • [🧠 为什么这样做?](#🧠 为什么这样做?)
    • [🔧 完整测试代码](#🔧 完整测试代码)
    • [⚡ 性能分析](#⚡ 性能分析)
    • [🎯 关键记忆点](#🎯 关键记忆点)
    • [📈 排列变化轨迹图](#📈 排列变化轨迹图)
    • [📋 数字操作追踪表(结合推荐的解法,仔细看这里,一定会懂的!!!)](#📋 数字操作追踪表(结合推荐的解法,仔细看这里,一定会懂的!!!))
      • [示例:[1,2,7,4,3,1] → [1,3,1,2,4,7]](#示例:[1,2,7,4,3,1] → [1,3,1,2,4,7])
      • 反转操作详解
    • [🎯 记忆技巧](#🎯 记忆技巧)

LeetCode 31. 下一个排列 - 最简算法实现

🎯 核心思路

找下一个排列就是找刚好比当前排列大一点点的排列。

📝 算法步骤(4步法)

  1. 从右往左 找第一个小于右邻居的数(称为"突破点")
  2. 如果没找到,说明是最大排列,全部反转
  3. 如果找到了,从右往左找第一个大于突破点 的数,交换它们
  4. 把突破点右边的部分反转

💻 最简短代码

java 复制代码
class Solution {
    public void nextPermutation(int[] nums) {
        int i = nums.length - 2;
        
        // 步骤1:找突破点
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }
        
        // 步骤2&3:如果找到突破点,找替换数并交换
        if (i >= 0) {
            int j = nums.length - 1;
            while (nums[j] <= nums[i]) {
                j--;
            }
            swap(nums, i, j);
        }
        
        // 步骤4:反转突破点右边部分
        reverse(nums, i + 1);
    }
    
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
    
    private void reverse(int[] nums, int start) {
        int left = start, right = nums.length - 1;
        while (left < right) {
            swap(nums, left++, right--);
        }
    }
}

总共仅20行核心代码!

🎭 最易懂代码(注释版,推荐!!)

强烈建议把该代码和下面可视化演示部分结合看

java 复制代码
class Solution {
    public void nextPermutation(int[] nums) {
        int n = nums.length;
        int breakPoint = -1;
        for (int i = n - 2; i >= 0; i--) {//从n-2开始,不能从n-1开始,n-1的话,i+1=n-1+1=n,就越界了
            if (nums[i]<nums[i+1]) {
                breakPoint=i;
                break;
            }
        }
        if (breakPoint!=-1) {
            for(int i=n-1;i>breakPoint;--i){
                if (nums[i]>nums[breakPoint]) {
                    swap(nums,i,breakPoint);
                    break;
                }
            }
        }
        reverse(nums,breakPoint+1);
    }
    void reverse(int[] nums,int start){
        int left=start,right=nums.length-1;
        while (left < right) {
            swap(nums,left++,right--);
        }
    }
    void swap(int[] arr,int i,int j){
        int t = arr[i];
        arr[i]=arr[j];
        arr[j]=t;
    }
}

🎬 可视化演示过程

示例1:[1,2,3] → [1,3,2]

复制代码
原数组: [1, 2, 3]
        ↑  ↑  ↑
        0  1  2

🔍 步骤1:从右往左找突破点
检查位置1: nums[1]=2 < nums[2]=3 ✓ 
找到突破点 i=1

🔄 步骤3:从右往左找替换数
检查位置2: nums[2]=3 > nums[1]=2 ✓
找到替换数 j=2

💱 交换 nums[1] 和 nums[2]
[1, 2, 3] → [1, 3, 2]

🔄 步骤4:反转突破点右边部分(位置2开始)
右边只有一个数,无需反转

✅ 最终结果: [1, 3, 2]

示例2:[2,3,1] → [3,1,2]

复制代码
原数组: [2, 3, 1]
        ↑  ↑  ↑
        0  1  2

🔍 步骤1:从右往左找突破点
检查位置1: nums[1]=3 > nums[2]=1 ❌
检查位置0: nums[0]=2 < nums[1]=3 ✓
找到突破点 i=0

🔄 步骤3:从右往左找替换数
检查位置2: nums[2]=1 < nums[0]=2 ❌
检查位置1: nums[1]=3 > nums[0]=2 ✓
找到替换数 j=1

💱 交换 nums[0] 和 nums[1]
[2, 3, 1] → [3, 2, 1]

🔄 步骤4:反转突破点右边部分(位置1开始)
反转 [2, 1] → [1, 2]
[3, 2, 1] → [3, 1, 2]

✅ 最终结果: [3, 1, 2]

示例3:[3,2,1] → [1,2,3]

复制代码
原数组: [3, 2, 1]
        ↑  ↑  ↑
        0  1  2

🔍 步骤1:从右往左找突破点
检查位置1: nums[1]=2 > nums[2]=1 ❌
检查位置0: nums[0]=3 > nums[1]=2 ❌
没有找到突破点 i=-1

🔄 步骤4:反转整个数组(从位置0开始)
[3, 2, 1] → [1, 2, 3]

✅ 最终结果: [1, 2, 3]

复杂示例:[1,2,7,4,3,1] → [1,3,1,2,4,7]

复制代码
原数组: [1, 2, 7, 4, 3, 1]
        ↑  ↑  ↑  ↑  ↑  ↑
        0  1  2  3  4  5

🔍 步骤1:从右往左找突破点
检查位置4: nums[4]=3 > nums[5]=1 ❌
检查位置3: nums[3]=4 > nums[4]=3 ❌  
检查位置2: nums[2]=7 > nums[3]=4 ❌
检查位置1: nums[1]=2 < nums[2]=7 ✓
找到突破点 i=1

当前状态: [1, 2, 7, 4, 3, 1]
                ↑ 突破点

🔄 步骤3:从右往左找替换数(找第一个>2的数)
检查位置5: nums[5]=1 < 2 ❌
检查位置4: nums[4]=3 > 2 ✓
找到替换数 j=4

💱 交换 nums[1]=2 和 nums[4]=3
[1, 2, 7, 4, 3, 1] → [1, 3, 7, 4, 2, 1]

当前状态: [1, 3, 7, 4, 2, 1]
                ↑ 突破点右边需要反转

🔄 步骤4:反转突破点右边部分(位置2开始)
反转 [7, 4, 2, 1] → [1, 2, 4, 7]
[1, 3, 7, 4, 2, 1] → [1, 3, 1, 2, 4, 7]

✅ 最终结果: [1, 3, 1, 2, 4, 7]

🎨 动画演示

步骤动画图解

复制代码
找突破点过程:
[1, 2, 7, 4, 3, 1]
          ←←←←←
    从右往左检查每相邻对

位置4-5: 3 > 1  ❌ 继续
位置3-4: 4 > 3  ❌ 继续  
位置2-3: 7 > 4  ❌ 继续
位置1-2: 2 < 7  ✅ 找到!突破点=1

找替换数过程:
[1, 2, 7, 4, 3, 1]
    ↑           ←←
  突破点        从右往左找>2的数

位置5: 1 ≤ 2  ❌ 继续
位置4: 3 > 2   ✅ 找到!替换数=4

交换过程:
[1, 2, 7, 4, 3, 1]
    ↑        ↑
    交换这两个数
[1, 3, 7, 4, 2, 1]

反转过程:
[1, 3, | 7, 4, 2, 1]
        反转右边部分
[1, 3, | 1, 2, 4, 7]

最终结果:[1, 3, 1, 2, 4, 7]

📊 算法流程图

🎨 数组变化可视化

关键概念图解

🧠 为什么这样做?

核心洞察

  1. 为什么从右往左找?

    • 我们要的是"刚好大一点点"的排列
    • 改变越右边的数字,影响越小
  2. 为什么找"小于右邻居"的数?

    • 这是第一个可以"增大"的位置
    • 右边都是降序,无法再增大
  3. 为什么要反转右边部分?

    • 交换后,右边仍然是降序(最大排列)
    • 反转变成升序(最小排列),确保是"下一个"

直观理解

想象数字排列像一个"计数器":

复制代码
[1, 2, 3]  计数器:123
[1, 3, 2]  计数器:132 (下一个)
[2, 1, 3]  计数器:213
...

我们的算法就是实现了这个"计数器+1"的操作!

🔧 完整测试代码

java 复制代码
public class NextPermutationTest {
    
    public static void main(String[] args) {
        Solution solution = new Solution();
        
        // 测试用例
        int[][] testCases = {
            {1, 2, 3},      // → [1, 3, 2]
            {3, 2, 1},      // → [1, 2, 3]  
            {1, 1, 5},      // → [1, 5, 1]
            {2, 3, 1},      // → [3, 1, 2]
            {1, 2, 7, 4, 3, 1}  // → [1, 3, 1, 2, 4, 7]
        };
        
        for (int[] nums : testCases) {
            int[] original = nums.clone();
            solution.nextPermutation(nums);
            
            System.out.printf("输入: %s → 输出: %s%n",
                            Arrays.toString(original),
                            Arrays.toString(nums));
        }
    }
}

输出结果:

复制代码
输入: [1, 2, 3] → 输出: [1, 3, 2]
输入: [3, 2, 1] → 输出: [1, 2, 3]
输入: [1, 1, 5] → 输出: [1, 5, 1]
输入: [2, 3, 1] → 输出: [3, 1, 2]
输入: [1, 2, 7, 4, 3, 1] → 输出: [1, 3, 1, 2, 4, 7]

⚡ 性能分析

  • 时间复杂度:O(n) - 最多扫描数组3遍
  • 空间复杂度:O(1) - 原地操作,只用几个变量

🎯 关键记忆点

  1. 四字口诀:找点、找数、交换、反转
  2. 两个查找:都是从右往左
  3. 一次反转:让右边变成最小排列

📈 排列变化轨迹图

以[1,2,3]的所有排列为例

📋 数字操作追踪表(结合推荐的解法,仔细看这里,一定会懂的!!!)

示例:[1,2,7,4,3,1] → [1,3,1,2,4,7]

步骤 操作类型 数组状态详情 关键信息
初始 原始状态 ----
1 找突破点 ----
2 找替换数 ----
3 交换操作 ----
4 反转右边 ----

反转操作详解

反转步骤 数组状态 指针位置 操作说明
反转前 ---- ---
第1步 ---- ---
第2步 ---- ---

🎯 记忆技巧

助记口诀

复制代码
找点找数交换反转,
从右往左是关键,
突破点处做文章,
最小排列是终点。

三个关键位置

  1. 突破点:第一个可以"增大"的位置
  2. 替换数:刚好能让突破点增大的最小数
  3. 反转区:突破点右边需要变成最小排列

这个算法虽然只有20行代码,但蕴含了深刻的数学思想,是贪心算法的完美体现!

相关推荐
1白天的黑夜110 小时前
哈希表-面试题01.02.判定是否互为字符重排-力扣(LeetCode)
c++·leetcode·哈希表
倔强的小石头_11 小时前
基于飞算JavaAI的在线图书借阅平台设计与实现
java·java开发·飞算javaai炫技赛
麦兜*11 小时前
MongoDB 事务管理:多文档操作如何保证 ACID?
java·数据库·后端·mongodb·spring cloud·springboot
CoovallyAIHub11 小时前
PL-YOLOv8:基于YOLOv8的无人机实时电力线检测与植被风险预警框架,实现精准巡检与预警
深度学习·算法·计算机视觉
明月与玄武11 小时前
动态配置最佳实践:Spring Boot 十种落地方式与回滚审计指南(含实操与避坑)
java·springboot动态配置·10种方法与实践
森之鸟11 小时前
开发中使用——鸿蒙本地存储之收藏功能
java·华为·harmonyos
凤年徐11 小时前
【数据结构】八大排序之快速排序:分而治之的艺术
c语言·开发语言·数据结构·c++·算法·排序算法
AAA修煤气灶刘哥11 小时前
微服务网关:别再让接口 “各自为战”!Gateway 实战 + 鉴权全攻略
java·后端·spring cloud
sssvangen11 小时前
拔河(蓝桥杯)(前缀和)
算法·前缀和·职场和发展·蓝桥杯