2026-07-02:将一个字符串排序的最小操作次数。用go语言,问题:给定只包含小写字母的字符串 s。你可以反复进行操作:每次选取 s 的一个连续子串,但子串

0 阅读8分钟

2026-07-02:将一个字符串排序的最小操作次数。用go语言,问题:给定只包含小写字母的字符串 s。你可以反复进行操作:每次选取 s 的一个连续子串,但子串不能覆盖整个字符串;然后把这个子串内部的字符按“从小到大不降序”重新排列。目标是通过尽可能少的这种操作,使整个字符串最终变成非降序(即从左到右字母不减)。

要求:计算实现上述目标所需的最小操作次数;如果无论怎么操作都无法做到,则输出 -1。

1 <= s.length <= 100000。

s 仅由小写英文字母组成。

输入: s = "dog"。

输出: 1。

解释:

将子字符串 "og" 排序为 "go"。

现在,s = "dgo",已按升序排列。因此,答案是 1。

题目来自力扣3863。

一、函数逻辑分步拆解(逐段流程详解)

整体逻辑是分6层条件逐层判断,从最优解(0次)到最差解(3次)依次匹配,匹配到即直接返回答案,无需后续判断。

步骤1:判断原字符串是否已经全局升序

  1. 将字符串转为字节切片,调用有序校验函数检查完整数组是否非降;
  2. 若本身有序,不需要任何操作,直接返回操作次数0。
  3. 示例dog原始序列 d→o→g,o > g,不满足升序,进入下一步判断。

步骤2:特殊边界判断——字符串长度等于2

规则:长度为2时,可选子串只能是单个字符(排序单个字符无变化),不存在有效操作能调换两个字符,永远无法排序,直接返回-1。 示例dog长度为3,跳过该分支,继续判断。

步骤3:找出全局最小字符 mn、全局最大字符 mx

遍历整个字符数组,提取全部字符里ASCII最小、最大的字母:

  • s="dog" 字符ASCII:d=100,o=111,g=103
  • mn = d(100),mx = o(111)

步骤4:判断能否仅用1次操作完成排序(返回1)

判定条件满足其一即可:

  1. 字符串第一个字符就是全局最小值 mn; 含义:首字符已经是全串最小,只需要对除去首字符的后半段连续子串做一次升序排序,后半段有序后整体必然全局升序;
  2. 字符串最后一个字符就是全局最大值 mx; 含义:末尾字符已经是全串最大,只需要对除去末尾字符的前半段连续子串做一次升序排序,前半段有序后整体必然全局升序。

代入示例 "dog" 验证

首字符 t[0] = d,正好等于全局最小值 mn,满足第一条判定条件,直接返回操作次数1,和题目输出结果一致。 操作对应解释:选取后两位连续子串og排序为go,得到dgo全局升序,仅1次操作。

补充该分支通用场景举例

例1:s="dxb",mn=d在首位,排序[x,b]一次即可; 例2:s="bdz",mx=z在末尾,排序[b,d]一次即可。

步骤5:不满足1次操作,判断能否仅用2次操作完成排序(返回2)

前置前提:首字符不是全局最小、末尾字符不是全局最大。 遍历中间区间t[1 : n-1](去掉第一个、最后一个字符的中间所有字符): 只要中间任意一个字符等于全局最小值mn 或 全局最大值mx,就返回2次操作。

原理说明

两种场景对应两次操作方案:

  1. 中间存在全局最小值mn: 第一次操作:排序[0, n-2](除最后一位),把中间的最小值挪到字符串最左侧; 第二次操作:排序[1, n-1](除第一位),将后半段全部理顺,整体有序;
  2. 中间存在全局最大值mx: 第一次操作:排序[1, n-1](除第一位),把中间最大值挪到字符串最右侧; 第二次操作:排序[0, n-2](除最后一位),理顺前半段,整体有序。

步骤6:仅剩最坏场景,固定需要3次操作(返回3)

前置全部条件都不满足时,唯一剩余特征:

  1. 第一个字符是全局最大值mx;
  2. 最后一个字符是全局最小值mn;
  3. 中间所有字符既不含全局最小mn,也不含全局最大mx。

三次操作完整逻辑

  1. 第一次操作:排序子串[0, n-2](去掉末尾),将首位的全局最大值移动到倒数第二位;
  2. 第二次操作:排序子串[1, n-1](去掉首位),把全局最大值移到最后一位,同时把末尾的全局最小值挪到第二位;
  3. 第三次操作:再次排序子串[0, n-2],将第二位的全局最小值移动到第一位,整个字符串完全升序。

二、无解场景总结

仅一种情况输出-1:字符串长度严格等于2,无论字符如何排列,都无法通过规则内操作实现升序。

三、时间复杂度分析

1. 各环节耗时拆解

  1. slices.IsSorted:完整遍历一次字符串,O(n);
  2. slices.Min、slices.Max:各遍历一次字符串,合计O(n);
  3. 中间区间循环t[1:n-1]:最坏遍历n-2个字符,O(n); 所有步骤为顺序串行执行,无嵌套循环。

总时间复杂度

O(n),n为字符串长度,线性复杂度,可支持题目上限1e5长度输入。

四、额外空间复杂度分析

空间占用来源

仅创建一个字节切片t := []byte(s)存储字符串副本,占用n字节;其余变量(mn、mx、循环临时变量)为常数级空间,不随字符串长度变化。

总额外空间复杂度

O(n) 补充优化说明:若不复制完整切片,仅通过索引读取原字符串字符,可优化为O(1)常数空间;当前代码实现采用切片拷贝,因此空间为线性O(n)。

Go完整代码如下:

package main

import (
	"fmt"
	"slices"
)

func minOperations(s string) int {
	t := []byte(s)
	// s 已经是升序
	if slices.IsSorted(t) {
		return 0
	}

	n := len(t)
	// 长为 2,无法排序
	if n == 2 {
		return -1
	}

	mn := slices.Min(t)
	mx := slices.Max(t)
	// 如果 s[0] 是最小值,排序 [1,n-1] 即可
	// 如果 s[n-1] 是最大值,排序 [0,n-2] 即可
	if t[0] == mn || t[n-1] == mx {
		return 1
	}

	// 如果 [1,n-2] 中有最小值,那么先排序 [0,n-2],把最小值排在最前面,然后排序 [1,n-1] 即可
	// 如果 [1,n-2] 中有最大值,那么先排序 [1,n-1],把最大值排在最后面,然后排序 [0,n-2] 即可
	for _, ch := range t[1 : n-1] {
		if ch == mn || ch == mx {
			return 2
		}
	}

	// 现在只剩下一种情况:s[0] 是最大值,s[n-1] 是最小值,且 [1,n-2] 不含最小值和最大值
	// 先排序 [0,n-2],把最大值排到 n-2
	// 然后排序 [1,n-1],把最大值排在最后面,且最小值排在 1
	// 最后排序 [0,n-2],把最小值排在最前面
	return 3
}

func main() {
	s := "dog"
	result := minOperations(s)
	fmt.Println(result)
}

在这里插入图片描述

Python完整代码如下:

# -*-coding:utf-8-*-

from typing import List

def minOperations(s: str) -> int:
    t = list(s)
    n = len(t)
    
    # s 已经是升序
    if all(t[i] <= t[i+1] for i in range(n-1)):
        return 0
    
    # 长为 2,无法排序
    if n == 2:
        return -1
    
    mn = min(t)
    mx = max(t)
    
    # 如果 s[0] 是最小值,排序 [1,n-1] 即可
    # 如果 s[n-1] 是最大值,排序 [0,n-2] 即可
    if t[0] == mn or t[n-1] == mx:
        return 1
    
    # 如果 [1,n-2] 中有最小值,那么先排序 [0,n-2],把最小值排在最前面,然后排序 [1,n-1] 即可
    # 如果 [1,n-2] 中有最大值,那么先排序 [1,n-1],把最大值排在最后面,然后排序 [0,n-2] 即可
    for ch in t[1:n-1]:
        if ch == mn or ch == mx:
            return 2
    
    # 现在只剩下一种情况:s[0] 是最大值,s[n-1] 是最小值,且 [1,n-2] 不含最小值和最大值
    # 先排序 [0,n-2],把最大值排到 n-2
    # 然后排序 [1,n-1],把最大值排在最后面,且最小值排在 1
    # 最后排序 [0,n-2],把最小值排在最前面
    return 3

def main():
    s = "dog"
    result = minOperations(s)
    print(result)

if __name__ == "__main__":
    main()

在这里插入图片描述

C++完整代码如下:

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>

using namespace std;

int minOperations(string s) {
    string t = s;
    int n = t.length();

    // s 已经是升序
    if (is_sorted(t.begin(), t.end())) {
        return 0;
    }

    // 长为 2,无法排序
    if (n == 2) {
        return -1;
    }

    char mn = *min_element(t.begin(), t.end());
    char mx = *max_element(t.begin(), t.end());

    // 如果 s[0] 是最小值,排序 [1,n-1] 即可
    // 如果 s[n-1] 是最大值,排序 [0,n-2] 即可
    if (t[0] == mn || t[n-1] == mx) {
        return 1;
    }

    // 如果 [1,n-2] 中有最小值,那么先排序 [0,n-2],把最小值排在最前面,然后排序 [1,n-1] 即可
    // 如果 [1,n-2] 中有最大值,那么先排序 [1,n-1],把最大值排在最后面,然后排序 [0,n-2] 即可
    for (int i = 1; i < n - 1; i++) {
        if (t[i] == mn || t[i] == mx) {
            return 2;
        }
    }

    // 现在只剩下一种情况:s[0] 是最大值,s[n-1] 是最小值,且 [1,n-2] 不含最小值和最大值
    // 先排序 [0,n-2],把最大值排到 n-2
    // 然后排序 [1,n-1],把最大值排在最后面,且最小值排在 1
    // 最后排序 [0,n-2],把最小值排在最前面
    return 3;
}

int main() {
    string s = "dog";
    int result = minOperations(s);
    cout << result << endl;
    return 0;
}

在这里插入图片描述