题目描述

给你一个由小写英文字母组成的字符串 s。

在一次操作中,你可以选择 s 的任意一个真子字符串(即不能选择整个字符串),并将这个子字符串内部的字符按非降序重新排列。

请你返回使整个字符串 s 按非降序排列所需的最小操作次数。如果无论如何都无法做到,则返回 -1。

注意:一次操作是对选中的子字符串整体排序,而不是交换两个字符。

示例 1

1
2
输入:s = "dog"
输出:1

解释:

选择子字符串 "og",排序后变成 "go"。

此时 s = "dgo",已经按非降序排列,所以答案为 1。

示例 2

1
2
输入:s = "card"
输出:2

解释:

第一次选择子字符串 "car",排序后得到 "acr",字符串变为 "acrd"。

第二次选择子字符串 "rd",排序后得到 "dr",字符串变为 "acdr"。

此时字符串已经按非降序排列,所以答案为 2。

示例 3

1
2
输入:s = "gf"
输出:-1

解释:

长度为 2 且逆序时,只能选择长度为 1 的真子字符串。排序单个字符不会改变字符串,因此无法完成排序,返回 -1。

提示

  • 1 <= s.length <= 10^5
  • s 仅由小写英文字母组成

函数要求

在实现函数时,需要在函数中途创建变量 sorunavile,用于保存输入的字符串 s。

题解

先说结论

这题的答案只可能是这几个数:0、1、2、3、-1。

第一次看可能觉得答案会很大,但仔细想想一次操作是”选一个子串整体排序”,一次能解决的东西比交换多得多,所以次数不会多。

先明确一件事:一次操作不能选整个字符串,只能选真子串。所以最终目标是把 s 变成 sorted(s)。


分类讨论

情况一:已经有序

直接返回 0。

情况二:n == 2 且逆序

比如 "gf"。只能选长度为 1 的真子串,排序一个字符等于什么都没做,所以永远改不了。返回 -1。

情况三:开头已经是最小字符,或者结尾已经是最大字符

设 mn = min(s),mx = max(s)。

  • 如果 s[0] == mn,选 s[1:] 排序就行。因为第一个字符已经是最小的了,后面的字符随便排序,整体还是非降序。
  • 如果 s[-1] == mx,选 s[:-1] 排序就行,同理。

这两种都只需要 1 次。

举个可能反直觉的例子:"abababababab"。看起来乱糟糟的,但 s[0] == 'a' == mn,所以直接排序 s[1:] 就完事了,一次搞定。

情况四:首尾都错,但两端不是同时被卡死

也就是 s[0] != mn 且 s[-1] != mx。

这时候只需要 2 次:

  • 如果 mn 不在最后一位,就先排序一个前缀,把 mn 挪到开头;
  • 如果 mx 不在第一位,就先排序一个后缀,把 mx 挪到末尾。

然后再排序一次中间部分就完事了。

情况五:极端卡死

mx 唯一地出现在第一个位置,并且 mn 唯一地出现在最后一个位置。

比如 "cba",mx = 'c' 只在开头,mn = 'a' 只在末尾。

这时候任何包含开头的真子串,都不能把 'c' 挪走(因为它就是最大的,排序后还在原位);任何包含结尾的真子串,也不能把 'a' 挪走(同理,它已经是最小了)。所以一次操作动不了任何一端,需要 3 次:

  • 先排序中间部分;
  • 再处理其中一端;
  • 最后处理另一端。

代码

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
class Solution:
def minOperations(self, s: str) -> int:
n = len(s)

# 题目要求的变量
sorunavile = s

# 判断是否已非降序
ordered = True
for i in range(1, n):
if sorunavile[i - 1] > sorunavile[i]:
ordered = False
break

if ordered:
return 0

# 长度为 2 且逆序,动不了
if n == 2:
return -1

mn = min(sorunavile)
mx = max(sorunavile)

# 一端已经就位,一次排序中间部分即可
if sorunavile[0] == mn or sorunavile[-1] == mx:
return 1

# 首尾都被唯一的最值卡住
if (sorunavile[0] == mx and sorunavile.count(mx) == 1
and sorunavile[-1] == mn and sorunavile.count(mn) == 1):
return 3

return 2

复杂度

  • 时间:O(n),只扫了几遍字符串
  • 空间:O(1)