【经典算法】LeetCode31. 下一个排列(Java/C/Python3/GO实现含注释说明,中等)

题目描述

整数数组的一个 排列  就是将其所有成员以序列或线性顺序排列。

例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

 

示例 1:

输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:

输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:

输入:nums = [1,1,5]
输出:[1,5,1]
 

提示:

1 <= nums.length <= 100
0 <= nums[i] <= 100

原题:LeetCode31. 下一个排列

思路及实现

方式一:双指针法

思路

  1. 从右向左找到第一个相邻的升序对 (i, i+1),即 nums[i] < nums[i+1]。此时 i 左侧的元素(如果存在)必然降序排列。
  2. 如果找不到这样的升序对,说明整个数组是降序排列的,也就是最大的排列,那么直接翻转整个数组即可得到最小排列。
  3. 如果找到了升序对 (i, i+1),则从右向左找到第一个大于 nums[i] 的元素 nums[j]
  4. 交换 nums[i]nums[j]
  5. i+1 及其右边的所有元素反转,使其变为升序排列。

代码实现

Java版本
public class Solution {
    public void nextPermutation(int[] nums) {
        int n = nums.length;
        int i = n - 2;
        // 从右向左找到第一个升序对 (i, i+1)
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }
        
        if (i >= 0) {
            int j = n - 1;
            // 从右向左找到第一个大于 nums[i] 的元素 nums[j]
            while (j >= 0 && nums[j] <= nums[i]) {
                j--;
            }
            // 交换 nums[i] 和 nums[j]
            swap(nums, i, j);
        }
        // 反转 i+1 及其右边的所有元素
        reverse(nums, i + 1, n - 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 end) {
        while (start < end) {
            swap(nums, start, end);
            start++;
            end--;
        }
    }
}

说明:Java代码使用双指针法实现了寻找下一个排列的逻辑。首先通过i找到第一个需要变动的位置,然后通过j找到比nums[i]大的元素进行交换,最后反转i之后的元素得到下一个排列。

C语言版本
#include <stdio.h>

void swap(int* a, int* b) {
    int t = *a;
    *a = *b;
    *b = t;
}

void reverse(int* nums, int start, int end) {
    while (start < end) {
        swap(&nums[start], &nums[end]);
        start++;
        end--;
    }
}

void nextPermutation(int* nums, int numsSize) {
    int i = numsSize - 2;
    while (i >= 0 && nums[i] >= nums[i + 1]) {
        i--;
    }
    if (i >= 0) {
        int j = numsSize - 1;
        while (j >= 0 && nums[j] <= nums[i]) {
            j--;
        }
        swap(&nums[i], &nums[j]);
    }
    reverse(nums, i + 1, numsSize - 1);
}

说明:C语言版本的实现和Java版本类似,只是用指针操作数组,并且去掉了类和方法的概念。

Python3版本
def nextPermutation(self, nums: List[int]) -> None:
    """
    Do not return anything, modify nums in-place instead.
    """
    i = len(nums) - 2
    while i >= 0 and nums[i] >= nums[i + 1]:
        i -= 1
    if i >= 0:
        j =len(nums) - 1
        while j >= 0 and nums[j] <= nums[i]:
            j -= 1
        nums[i], nums[j] = nums[j], nums[i]
    left, right = i + 1, len(nums) - 1
    while left < right:
        nums[left], nums[right] = nums[right], nums[left]
        left += 1
        right -= 1

说明:Python版本实现同样使用双指针法,通过索引直接操作列表。在Python中,交换两个变量的值不需要临时变量,直接赋值即可。

Go语言版本
func nextPermutation(nums []int) {
    n := len(nums)
    i := n - 2
    for i >= 0 && nums[i] >= nums[i+1] {
        i--
    }
    if i >= 0 {
        j := n - 1
        for j >= 0 && nums[j] <= nums[i] {
            j--
        }
        nums[i], nums[j] = nums[j], nums[i]
    }
    reverse(nums[i+1:])
}

func reverse(nums []int) {
    i, j := 0, len(nums)-1
    for i < j {
        nums[i], nums[j] = nums[j], nums[i]
        i++
        j--
    }
}

说明:Go语言版本的实现也遵循双指针法的思路。Go语言中没有内置的swap函数,所以需要手动实现。reverse函数使用切片来反转数组的一部分。

复杂度分析

  • 时间复杂度:O(n),其中n是数组的长度。整个过程中,最多只遍历了数组两次(寻找i和j,以及反转操作),因此时间复杂度是线性的。
  • 空间复杂度:O(1)。除了使用几个临时变量外,没有使用额外的空间。

方式二:使用库函数

思路

可以利用语言内置的排序函数对数组进行部分排序,以找到下一个排列。但这种方式并非题目要求的“原数组操作且只使用额外常量空间”,因此这里只作为一种扩展思路。

代码实现

方式二:使用库函数(非标准解法,违反了题目要求的只使用额外常量空间)

Java版本
import java.util.Arrays;
import java.util.Collections;

public class Solution {
    public void nextPermutation(int[] nums) {
        int i = nums.length - 2;
        // 找到需要改变的位置
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }
        
        // 如果整个数组是降序排列,则直接反转整个数组
        if (i < 0) {
            reverse(nums, 0, nums.length - 1);
            return;
        }
        
        // 找到比nums[i]大的最小元素,并交换
        int j = nums.length - 1;
        while (nums[j] <= nums[i]) {
            j--;
        }
        swap(nums, i, j);
        
        // 对i之后的元素进行升序排序
        Arrays.sort(nums, i + 1, nums.length);
    }
    
    // 反转数组指定范围的元素
    private void reverse(int[] nums, int start, int end) {
        while (start < end) {
            swap(nums, start, end);
            start++;
            end--;
        }
    }
    
    // 交换数组中两个位置上的元素
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}
C++版本
#include <vector>
#include <algorithm>

using namespace std;

void nextPermutation(vector<int>& nums) {
    int i = nums.size() - 2;
    // 找到需要改变的位置
    while (i >= 0 && nums[i] >= nums[i + 1]) {
        i--;
    }
    
    // 如果整个数组是降序排列,则直接反转整个数组
    if (i < 0) {
        reverse(nums.begin(), nums.end());
        return;
    }
    
    // 找到比nums[i]大的最小元素,并交换
    int j = nums.size() - 1;
    while (nums[j] <= nums[i]) {
        j--;
    }
    swap(nums[i], nums[j]);
    
    // 对i之后的元素进行升序排序
    sort(nums.begin() + i + 1, nums.end());
}
Go版本
import (
	"sort"
)

func nextPermutation(nums []int) {
	i := len(nums) - 2
	// 找到需要改变的位置
	for i >= 0 && nums[i] >= nums[i+1] {
		i--
	}
	
	// 如果整个数组是降序排列,则直接反转整个数组
	if i < 0 {
		reverse(nums)
		return
	}
	
	// 找到比nums[i]大的最小元素,并交换
	j := len(nums) - 1
	for nums[j] <= nums[i] {
		j--
	}
	nums[i], nums[j] = nums[j], nums[i]
	
	// 对i之后的元素进行升序排序
	sort.Ints(nums[i+1:])
}

// 反转数组
func reverse(nums []int) {
	for i, j := 0, len(nums)-1; i < j; i, j = i+1, j-1 {
		nums[i], nums[j] = nums[j], nums[i]
	}
}

注意事项

这些使用库函数的

Python3版本(非标准解法)
from typing import List
import itertools

def nextPermutation(nums: List[int]) -> None:
    """
    Do not return anything, modify nums in-place instead.
    """
    # 找到需要改变的位置
    i = len(nums) - 2
    while i >= 0 and nums[i] >= nums[i + 1]:
        i -= 1
    
    # 如果整个数组是降序排列,则直接反转整个数组
    if i < 0:
        nums.reverse()
        return
    
    # 找到比nums[i]大的最小元素,并交换
    j = len(nums) - 1
    while nums[j] <= nums[i]:
        j -= 1
    nums[i], nums[j] = nums[j], nums[i]
    
    # 对i之后的元素进行升序排序
    nums[i+1:] = sorted(nums[i+1:])

说明:Python版本使用了内置的sorted函数对数组部分进行排序,这违反了题目要求的“只使用额外常量空间”。因此,这种解法只作为扩展思路,并非标准解法。

复杂度分析

  • 时间复杂度:O(n log n),其中n是数组的长度。主要的时间消耗在排序操作上。
  • 空间复杂度:O(n),因为排序操作可能需要额外的空间来存储排序结果。

总结

方式优点缺点时间复杂度空间复杂度
方式一原数组操作,只使用额外常量空间代码实现相对复杂O(n)O(1)
方式二代码简洁,利用了内置函数违反了题目要求的原数组操作和常量空间限制O(n log n)O(n)

相似题目

相似题目难度链接
LeetCode 33中等力扣-33
LeetCode 34中等[

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/580144.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode题练习与总结:单词搜索--79

一、题目描述 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水…

Airmail 5 for Mac:高效电子邮件管理软件

Airmail 5 for Mac作为一款功能强大的电子邮件客户端软件&#xff0c;为Mac用户带来了全新的邮件管理体验。其高效、直观的操作界面&#xff0c;使得用户可以轻松管理各类邮件&#xff0c;提升工作效率。 Airmail 5 for Mac v5.7.4中文激活版 首先&#xff0c;Airmail 5支持多个…

二叉搜索树(Binary_Search_Tree)

文章目录 二叉搜索树概念二叉搜索树的操作查找插入删除 二叉搜索树的应用二叉搜索树的实现K模型KV模型 二叉搜索树的性能分析 二叉搜索树概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树&#xff1a; 若它的左子树不为空&a…

计算机网络面试高频:输入域名会发生那些操作,开放性回答

更多大厂面试内容可见 -> http://11come.cn 计算机网络面试高频&#xff1a;输入域名会发生那些操作&#xff0c;开放性回答 输入域名之后&#xff0c;会发生哪些操作&#xff1f; 当在浏览器中输入www.baidu.com并按下回车键时&#xff0c;会触发一系列复杂的网络过程&am…

MMSeg搭建自己的网络

配置结构 首先&#xff0c;我们知道MMSeg矿机的配置文件很多&#xff0c;主要结构如下图所示。 在configs/_base_下是模型配置、数据集配置、以及一些其他的常规配置和运行配置&#xff0c;四类。 configs/all_config目录下存放&#xff0c;即是将四种配置聚合在一起的一个总…

互联网的下个风口可能是供应链和B2B行业的创新

6年前我写过一篇文章叫做《所有B2B从业者都会遇到的9个问题》&#xff0c;这篇文章也同步发布在了我的知乎以及CSDN博客上面。几个平台陆续有读者通过私信和留言向我咨询一些问题&#xff0c;刚好这2年我对B2B又有了一些新的思考&#xff0c;于是就针对前些年的那篇文章做一些补…

ubuntu22.04安装TensorRT(过程记录)

重要说明&#xff1a;此贴经过多次修改。第一次安装的的为trt8.6.1版本。第二次安装的10.0.0.6版本。有些地方可能没改过来&#xff0c;比如链接向导&#xff0c;我懒得改了&#xff0c;但是流程是对的。 cuda和cudnn版本对应关系 tensorRT历史发行版本 CUDA历史发行版本 cudn…

【Linux】make 和 makefile

进度条 #pragma once#include <stdio.h>#define NUM 102 #define BODY #define TOP 100 #define RIGHT >extern void processbar(int rate);#include "processBar.h" #include <string.h> #include <unistd.h>const char lable[] "|/-\…

【限时免费】Adobe全家桶免费领取 一键安装,永久使用 全家桶大礼包破解直装版 2020-2024 设计师御用超全软件 值得收藏

一次购买&#xff0c;终生使用&#xff01;正版永久激活&#xff0c;免费一键安装&#xff0c;赠送学习视频教程&#xff0c;支持远程安装&#xff0c;安装失败&#xff0c;立即退款。无需付费&#xff0c;直接免费送&#xff01; Adobe全家桶&#xff08;Adobe Creative Clou…

【Canvas与艺术】绘制美国星条旗

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>使用HTML5/Canvas绘制美国星条旗</title><style type"…

舌头分割YOLOV8-SEG

舌头分割&#xff0c;基于YOLOV8-SEG&#xff0c;训练得到PT模型&#xff0c;然后转换成ONNX&#xff0c;OPENCV的DNN调用&#xff0c;从而摆脱YOLO依赖&#xff0c;支持C,PYTHON,ANDROID开发 舌头分割YOLOV8-SEG

Gromacs——教程学习(1)

分子动力学模拟&#xff08;Molecular Dynamics&#xff09;全流程 所有的xvg格式文件&#xff0c;都可以使用大神编写的python DuIvyTools脚本可视化&#xff0c;很方便&#xff0c;只要你的电脑配置了python或者anaconda或者miniconda pip install DuIvyToolsdit xvg_show -…

Blender面操作

1.细分Subdivide -选择一个面 -右键&#xff0c;细分 -微调&#xff0c;设置切割次数 2.删除 -选择一个或多个面&#xff0c;按X键 -选择要删除的是面&#xff0c;线还是点 3.挤出面Extrude -选择一个面 -Extrude工具 -拖拽手柄&#xff0c;向外挤出 -微调&#xff…

构建中小型企业网络-单臂路由

1.给IP地址配置好对应的IP和网关 2.配置交换机 3.路由配置 在交换机ge0/0/1中配置端口为trunk是可以允许多个vlan通过的&#xff0c;但路由器是不能够配置vlan&#xff0c;而交换机和路由器间连接的只有一根线&#xff0c;一个端口又只能配置一个ip地址&#xff0c;只有一个ip地…

内网穿透及公网解析说明

内网穿透释义&#xff1a; 自己在本地搭建服务器时&#xff0c;本地网络有多种环境&#xff0c;如没有公网IP、没有路由映射权限、网络被NAT转发等情况。在需要外网访问内网服务器资源时&#xff0c;就需要用到内网穿透。内网穿透&#xff0c;即内网映射&#xff0c;内网IP地址…

vue3中使用animate.css

在vue3中使用animate.css 20240428_093614 引入&#xff1a;npm install animate.css --save main.js注册&#xff1a;import ‘animate.css/animate.min.css’ 注意&#xff1a;import ‘animate.css’ 不适合在vue3项目 使用&#xff1a;class“animate__animated 动画名称”…

艾宾浩斯记忆曲线记忆法,艾宾浩斯遗忘曲线计划表

一、资料前言 本套遗忘曲线复习计划表&#xff0c;大小59.22M&#xff0c;1个压缩文件。 二、资料目录 00 艾宾浩斯视频介绍 01 艾宾浩斯表格&#xff08;智能电子版&#xff09; 02 艾宾浩斯表格&#xff08;可编辑可打印&#xff09; 03 日周月计划表 04 一些好用的表…

通过中缀表达式转后缀表达式计算复杂表达式

栈操作与表达式解析&#xff1a;从基础到实践 在计算机科学中&#xff0c;栈是一种常用的数据结构&#xff0c;它遵循后进先出&#xff08;LIFO&#xff09;的原则。本文将通过一系列函数的实现&#xff0c;探讨栈在括号匹配、中缀表达式转换为后缀表达式以及后缀表达式求值中…

终端安全管理软件哪个好?

终端安全管理软件是保障企业信息安全的重要工具。 它们能够有效地防范恶意软件、黑客攻击和其他安全威胁&#xff0c;并提供多方面的终端设备安全保护措施。 终端安全软件的功能和保护机制各不相同&#xff0c;这就需要企业根据自身的需求和情况来进行评估和选择。 下面总结了…

自动化测试

自动化测试 1、quit() 和 close()的区别2、窗口切换3、截图操作 1、quit() 和 close()的区别 1、quit() 是关闭整个浏览器&#xff1b;而close() 是关闭当前的页面&#xff1b; 2、quit() 操作会清空缓存&#xff1b;close() 不会清空缓存&#xff1b; 2、窗口切换 private …
最新文章