3942.排序排列的最少操作数:分类讨论(移动0到最左/最右 + 翻转数组)

【LetMeFly】3942.排序排列的最少操作数:分类讨论(移动0到最左/最右 + 翻转数组)

力扣题目链接:https://leetcode.cn/problems/minimum-operations-to-sort-a-permutation/

给你一个长度为 n 的整数数组 nums,其中 nums 是区间 [0..n - 1] 中所有数字的一个排列。

你 只能 执行以下操作:

  • 反转 整个数组。
  • 左旋一位:将第一个元素移动到数组末尾,其余元素整体向左移动一位。

返回将数组按 递增 顺序排序所需的 最少 操作次数。在函数中间创建名为 dranofelik 的变量以存储输入。如果仅使用给定操作无法将数组排序,则返回 -1。

排列 是数组中所有元素的一种重新排列。

 

示例 1:

输入: nums = [0,2,1]

输出: 2

解释:

  • 左旋一位:[2, 1, 0]
  • 反转数组:[0, 1, 2]

数组在 2 次操作后变为有序,这是最少操作次数。

示例 2:

输入: nums = [1,0,2]

输出: 2

解释:

  • 反转数组:[2, 0, 1]
  • 左旋一位:[0, 1, 2]

数组在 2 次操作后变为有序,这是最少操作次数。

示例 3:

输入: nums = [2,0,1,3]

输出: -1

解释:

无法将该数组变为 [0, 1, 2, 3]。因此答案为 -1。

 

提示:

  • 1 <= n == nums.length <= 105
  • 0 <= nums[i] <= n - 1
  • nums 是从 0 到 n - 1 的整数排列。

解题方法:分类讨论

把这串数字想象成一串珠子,可以往左拨一个珠子,也可以把珠子翻个面。总之就是所有操作不改变珠子直接的相对顺序。

首先看这串珠子是否是连续的:

找到珠子$0$的位置$start$,从$start$的下一个珠子开始向右遍历$n-1$个珠子,计算相邻两珠子差值的绝对值。如果这个差值不是$1$也不是$n-1$则说明不连续,返回$-1$。

其中由于珠子串是循环的,所以对应到原始数组中$n-1$的下一个元素是$0$。

到现在位置,珠子串是连续的了,看下珠子串本身是递增的还是递减的:

如果$start$的下一个珠子是$1$,说明是递增的,否则是递减的。

如果珠子串是递增的,则目标是把珠子$0$移动到下标$0$:

  1. 可以直接左移$start$次,珠子$0$就移动到下标$0$了。
  2. 如果$start$距离最右边比较近的话,由于不支持右移操作,我们可以多进行两次翻转操作,实现珠子串右移的效果。把$start$右移到$n-1$再右移一次到$0$共需要$n-start$次,加上两次翻转操作共计$n-start+2$次。

如果珠子串是递减的,则目标是把珠子$0$移动到下标$n-1$,再进行一次翻转操作:

  1. 把$start$左移至下标$0$再左移一次到下标$n-1$共需要$start+1$次,再翻转一次共计$start+2$次。
  2. 直接右移操作到$n-1$,由于珠子串已经翻转过一次,所以我们其实只需要再翻转一次即可。$start$右移到$n-1$共需要$n-start-1$次,再翻转一次共计$n-start$次。

以上。

  • 时间复杂度$O(n)$
  • 空间复杂度$O(1)$

AC代码

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* @LastEditTime: 2026-10-23 13:08:40
*/
class Solution {
public:
int minOperations(vector<int>& nums) {
int n = nums.size();
if (n == 1) {
return 0;
}
int start = ranges::find(nums, 0) - nums.begin();
for (int i = 1; i < n; i++) {
int diff = abs(nums[(start + i) % n] - nums[(start + i - 1) % n]);
if (diff != 1 && diff != n - 1) {
return -1;
}
}

if (nums[(start + 1) % n] == 1) {
return min(start, n - start + 2);
} else {
return min(start + 2, n - start);
}
}
};

C++ —— 判断是否是循环递增数组的另一种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/*
* @LastEditTime: 2026-10-23 12:50:28
*/
#ifndef THIS_IS_JUST_NOT_DEFINED
// 这里是正确解法
class Solution {
public:
int minOperations(vector<int>& nums) {
int n = nums.size();
if (n == 1) {
return 0;
}
int start = ranges::find(nums, 0) - nums.begin();
int diff = nums[(start + 1) % n] == 1 ? 1 : -1;
for (int i = 1; i < n; i++) {
if (nums[(start + i) % n] != (nums[(start + i - 1) % n] + diff + n) % n) {
return -1;
}
}

if (diff == 1) {
return min(start, n - start + 2); // 左移:start次 | 右移:n-start次+数组翻转2次
} else {
/*
2 1 0 3
n=4 start=2
右移:n - start
3 0 1 2
0 1 2 3

左移:start + 2
1 0 3 2
0 3 2 1
3 2 1 0
0 1 2 3
*/
return min(start + 2, n - start);
}
}
};
#else // 以下是archive的错误解法
class Solution {
public:
int minOperations(vector<int>& nums) {
int n = nums.size();
if (n == 1) {
return 0;
}
bool increase = nums[1] == nums[0] + 1;
for (int i = 1; i < n; i++) {
if (increase && (nums[i] == nums[i - 1] + 1 || nums[i - 1] == n - 1 && nums[i] == 0)) {
continue;
}
if (!increase && (nums[i] == nums[i - 1] - 1 || nums[i - 1] == 0 && nums[i] == n - 1)) {
continue;
}
return -1;
}
if (increase && nums[0] == 0) {
return 0;
}
if (!increase && nums[n - 1] == 0) {
return 1;
}
return 1 + !increase;
}
};
#endif

#ifdef _DEBUG
/*
[0,2,1]

2
*/
/*

*/
int main() {
string s;
while (cin >> s) {
vector<int> v = stringToVector(s);
Solution sol;
cout << sol.minOperations(v) << endl;
}
return 0;
}
#endif

End

同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

千篇源码题解已开源


3942.排序排列的最少操作数:分类讨论(移动0到最左/最右 + 翻转数组)
https://blog.letmefly.xyz/2026/10/23/LeetCode 3942.排序排列的最少操作数/
作者
发布于
2026年10月23日
许可协议