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

server/2024/9/24 13:19:31/

题目描述

整数数组的一个 排列  就是将其所有成员以序列或线性顺序排列。例如,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版本
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) - 2while i >= 0 and nums[i] >= nums[i + 1]:i -= 1if i >= 0:j =len(nums) - 1while j >= 0 and nums[j] <= nums[i]:j -= 1nums[i], nums[j] = nums[j], nums[i]left, right = i + 1, len(nums) - 1while left < right:nums[left], nums[right] = nums[right], nums[left]left += 1right -= 1

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

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

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

复杂度分析

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

方式二:使用库函数

思路

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

代码实现

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

Java版本
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) - 1for 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 itertoolsdef nextPermutation(nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""# 找到需要改变的位置i = len(nums) - 2while i >= 0 and nums[i] >= nums[i + 1]:i -= 1# 如果整个数组是降序排列,则直接反转整个数组if i < 0:nums.reverse()return# 找到比nums[i]大的最小元素,并交换j = len(nums) - 1while nums[j] <= nums[i]:j -= 1nums[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.ppmy.cn/server/20943.html

相关文章

视频怎么批量压缩?5个好用的电脑软件和在线网站

视频怎么批量压缩&#xff1f;有时候我们需要批量压缩视频来节省存储空间&#xff0c;便于管理文件和空间&#xff0c;快速的传输发送给他人。有些快捷的视频压缩工具却只支持单个视频导入&#xff0c;非常影响压缩效率&#xff0c;那么今天就向大家从软件和在线网站2个角度介绍…

推荐算法顶会论文合集

SIGIR SIGIR 2022 | 推荐系统相关论文分类整理&#xff1a;8.74 https://mp.weixin.qq.com/s/vH0qJ-jGHL7s5wSn7Oy_Nw SIGIR2021推荐系统论文集锦 https://mp.weixin.qq.com/s/N7V_9iqLmVI9_W65IQpOtg SIGIR2020推荐系统论文聚焦&#xff1a; https://mp.weixin.qq.com/s…

Spring Security(学习笔记) --AuthenticationProvider案例演示梳理!

重点标识 AuthenticationManager 默认是有parent的。 Security 向Spring容器注册了一个AuthenticationManager &#xff0c;是一个全局的&#xff0c;也就是所谓的parent。 注册一个AuthenticationManager &#xff0c;就是一个全局的 如果想要局部的&#xff0c;可以设置一个…

什么是SSRF攻击?该如何防御SSRF攻击?

随着网络安全形式日益严峻&#xff0c;各式各样的攻击频繁发生。当前&#xff0c;应用程序为了给用户提供更多更方便的功能&#xff0c;从另一个URL获取数据的场景越来越多&#xff0c;因此出现了一种安全漏洞攻击-SSRF。并且&#xff0c;由于云服务和体系结构的复杂性&#xf…

分发糖果——使用贪心算法

135. 分发糖果 已解答 困难 相关标签 相关企业 n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 你需要按照以下要求&#xff0c;给这些孩子分发糖果&#xff1a; 每个孩子至少分配到 1 个糖果。相邻两个孩子评分更高的孩子会获得更多的糖果。 请你给…

2024LarkXR新增功能系列之二 | 普通应用多开推流

在数字化迅速演变的今天&#xff0c;3D数字时代的到来已经改变了许多行业的运作方式。特别是在虚拟现实领域&#xff0c;实时云渲染技术正迅速成为关键驱动力&#xff0c;这种技术使得复杂的3D视觉内容可以迅速、高效地通过云平台渲染并传输到用户的设备上&#xff0c;无论设备…

opencv绘制线段------c++

绘制线段 bool opencvTool::drawLines(std::string image_p, std::vector<cv::Point> points) {cv::Mat ima cv::imread(image_p.c_str()); // 读取图像&#xff0c;替换为你的图片路径 cv::Scalar red cv::Scalar(0, 0, 255); // Red color int thickness 2;// 遍…

R语言高级数据管理

一&#xff0c;数学函数 绝对值函数abs(x) sqrt(x) 开平方根 不小于某个数的最小整数ceiling(x) 不大于某个数的最大整数floor(x) 四舍五入round(x) sin(x) cos(x) log(x) 二&#xff0c;统计函数 求平均值 > x<-c(2,3,4,5,6,7,8,9,10) > mean(x) 求和 &g…