3863.将一个字符串排序的最小操作次数

目标

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

在一次操作中,你可以选择 s 的任意 子字符串(但 不能 是整个字符串),并将其按 非降序字母顺序 进行 排序。

返回使 s 按 非降序 排列所需的 最小 操作次数。如果无法做到,则返回 -1。

示例 1:

输入: s = "dog"
输出: 1
解释:
将子字符串 "og" 排序为 "go"。
现在,s = "dgo",已按升序排列。因此,答案是 1。

示例 2:

输入: s = "card"
输出: 2
解释:
将子字符串 "car" 排序为 "acr",得到 s = "acrd"。
将子字符串 "rd" 排序为 "dr",得到 s = "acdr",已按升序排列。因此,答案是 2。

示例 3:

输入: s = "gf"
输出: -1
解释:
在给定提示下,无法对 s 进行排序。因此,答案是 -1。

说明:

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

思路

有一个字符串 s,每次操作可以将其子串(不能是整个字符串)按非递减排序,求使整个字符串按非递减排列所需的最小操作次数,如果无法实现返回 -1。

分类讨论:

  • 如果 s 本身非递减,操作次数为 0。
  • 否则,找出最大字符 max 与 最小字符 min:
    • 如果 s[0] == min || s[n - 1] == max,只需将剩余部分排序即可,操作次数为 1。
    • 如果 s[0] == max && s[n - 1] == min:
    • 如果字符串中有多余的 min 或者 max,可以直接排序前缀 s[0, n - 2] 或后缀 s[1, n - 1] 使得最大或最小归位,然后再排序剩余部分,操作次数为 2。
    • 否则,需要先将 min 与 max 移出来:
      • 如果 n == 2,返回 -1。
      • 否则,将 min 与 前一个的元素交换,排序前缀 s[0, n - 2],然后再排序后缀 s[1, n - 1],操作次数为 3。
    • 其余情况,先排序前缀 s[0, n - 2] 再排序后缀 s[1, n - 1] 即可,操作次数为 2。

代码


/**
 * @date 2026-10-21 9:10
 */
public class MinOperations3863 {

    public int minOperations(String s) {
        int max = s.charAt(0);
        int min = max;
        char[] chars = s.toCharArray();
        int[] cnt = new int[26];
        int n = chars.length;
        boolean ordered = true;
        for (char c : chars) {
            if (c < max) {
                ordered = false;
            }
            cnt[c - 'a']++;
            max = Math.max(max, c);
            min = Math.min(min, c);
        }
        if (ordered) {
            return 0;
        } else if (max == s.charAt(n - 1) || min == s.charAt(0)) {
            return 1;
        } else if (max == s.charAt(0) && min == s.charAt(n - 1)) {
            if (n == 2) {
                return -1;
            } else if (cnt[min - 'a'] > 1 || cnt[max - 'a'] > 1) {
                return 2;
            } else {
                return 3;
            }
        }
        return 2;
    }

}

性能

3723.数位平方和的最大值

目标

给你两个 正 整数 num 和 sum。

如果一个正整数 n 满足以下两个条件,则称其为 好整数 :

  • n 的位数 恰好 为 num。
  • n 的各位数字之和 恰好 为 sum。

一个 好整数 n 的 分数 定义为 n 的各位数字的平方和。

返回一个 字符串 ,表示能获得 最大分数 的 好整数 n。如果有多个可能的整数,返回 最大 的那个。如果不存在这样的整数,返回一个空字符串。

示例 1:

输入: num = 2, sum = 3
输出: "30"
解释:
有 3 个好整数:12、21 和 30。
12 的分数是 12 + 22 = 5。
21 的分数是 22 + 12 = 5。
30 的分数是 32 + 02 = 9。
最大分数是 9,由好整数 30 获得。因此,答案是 "30"。

示例 2:

输入: num = 2, sum = 17
输出: "98"
解释:
有两个好整数:89 和 98。
89 的分数是 82 + 92 = 145。
98 的分数是 92 + 82 = 145。
最大分数是 145。获得此分数的最大好整数是 98。因此,答案是 "98"。

示例 3:

输入: num = 1, sum = 10
输出: ""
解释:
不存在恰好有 1 位数字且各位数字之和为 10 的整数。因此,答案是 ""。

说明:

  • 1 <= num <= 2 * 10^5
  • 1 <= sum <= 2 * 10^6

思路

定义数位长度为 num,数位之和为 sum 的整数为好整数,好整数的分数定义为各位数字的平方和,返回分数最大的好整数,如果分数相同则返回整数值最大的,如果不存在返回空串。

贪心策略就是取当前位所能取的最大值,余下的补 0。

代码


/**
 * @date 2026-10-14 9:21
 */
public class MaxSumOfSquares3723 {

    public String maxSumOfSquares(int num, int sum) {
        StringBuilder sb = new StringBuilder();
        while (num > 0) {
            int d = Math.min(sum, 9);
            sum -= d;
            num--;
            sb.append(d);
        }
        return sum == 0 ? sb.toString() : "";
    }

}

性能

3727.最大交替平方和

目标

给你一个整数数组 nums。你可以以任意顺序 重新排列元素 。

数组 arr 的 交替得分 定义为:

  • score = arr[0]^2 - arr[1]^2 + arr[2]^2 - arr[3]^2 + ...

在对 nums 重新排列后,返回其 最大可能的交替得分。

示例 1:

输入: nums = [1,2,3]
输出: 12
解释:
nums 的一种可行重排为 [2,1,3],该排列在所有可能重排中给出了最大交替得分。
交替得分计算如下:
score = 22 - 12 + 32 = 4 - 1 + 9 = 12

示例 2:

输入: nums = [1,-1,2,-2,3,-3]
输出: 16
解释:
nums 的一种可行重排为 [-3,-1,-2,1,3,2],该排列在所有可能重排中给出了最大交替得分。
交替得分计算如下:
score = (-3)^2 - (-1)^2 + (-2)^2 - (1)^2 + (3)^2 - (2)^2 = 9 - 1 + 4 - 1 + 9 - 4 = 16

说明:

  • 1 <= nums.length <= 10^5
  • -4 10^4 <= nums[i] <= 4 10^4

思路

有一个下标从 0 开始的整数数组 nums,数组的交替得分定义为 (nums[0]^2 - nums[1]^2) + (nums[2]^2 - nums[3]^2) + …… ,允许对数组重新排序,返回最大可能的交替得分。

依题意,得分为偶数下标元素的平方减去奇数下标元素的平方,要使得分最大,只需奇数下标的平方值最小。将数组替换为元素的平方,然后排序数组,将前 n/2 个元素放在奇数下标即可。

代码


/**
 * @date 2026-10-13 10:20
 */
public class MaxAlternatingSum3727 {

    public long maxAlternatingSum(int[] nums) {
        long res = 0L;
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            nums[i] = nums[i] * nums[i];
        }
        Arrays.sort(nums);
        for (int i = 0; i < n; i++) {
            if (i < n / 2) {
                res -= nums[i];
            } else {
                res += nums[i];
            }
        }
        return res;
    }
}

性能

1541.平衡括号字符串的最少插入次数

目标

给你一个括号字符串 s ,它只包含字符 '(' 和 ')' 。一个括号字符串被称为平衡的当它满足:

  • 任何左括号 '(' 必须对应两个连续的右括号 '))' 。
  • 左括号 '(' 必须在对应的连续两个右括号 '))' 之前。

比方说 "())", "())(())))" 和 "(())())))" 都是平衡的, ")()", "()))" 和 "(()))" 都是不平衡的。

你可以在任意位置插入字符 '(' 和 ')' 使字符串平衡。

请你返回让 s 平衡的最少插入次数。

示例 1:

输入:s = "(()))"
输出:1
解释:第二个左括号有与之匹配的两个右括号,但是第一个左括号只有一个右括号。我们需要在字符串结尾额外增加一个 ')' 使字符串变成平衡字符串 "(())))" 。

示例 2:

输入:s = "())"
输出:0
解释:字符串已经平衡了。

示例 3:

输入:s = "))())("
输出:3
解释:添加 '(' 去匹配最开头的 '))' ,然后添加 '))' 去匹配最后一个 '(' 。

示例 4:

输入:s = "(((((("
输出:12
解释:添加 12 个 ')' 得到平衡字符串。

示例 5:

输入:s = ")))))))"
输出:5
解释:在字符串开头添加 4 个 '(' 并在结尾添加 1 个 ')' ,字符串变成平衡字符串 "(((())))))))" 。

说明:

  • 1 <= s.length <= 10^5
  • s 只包含 '(' 和 ')' 。

思路

定义平衡括号字符串是每一个左括号都对应有两个连续右括号的字符串。有一个括号字符串 s,每次操作可以插入一个 ( 或者 ),求使括号字符串变平衡的最小操作次数。

记录未匹配的左括号数量 lc,遇到连续的右括号直接滑倒结束并计数 rc,贪心策略:

  • 如果 lc >= rc,如果 rc 为奇数,需要补一个右括号,操作加一,lc 减去已匹配的数量,rc 置零。
  • 否则,每两个右括号匹配需要补一个左括号,操作加 rc / 2,如果 rc 是奇数,需要补一对括号,操作加 2。

代码


/**
 * @date 2026-10-09 9:09
 */
public class MinInsertions1541 {

    public int minInsertions(String s) {
        char[] chars = s.toCharArray();
        int n = s.length();
        int lc = 0, rc = 0, res = 0;
        int i = 0;
        while (i < n) {
            if (chars[i] == '(') {
                lc += 2;
                i++;
            } else {
                while (i < n && chars[i] == ')') {
                    i++;
                    rc++;
                }
            }
            if (lc >= rc) {
                res += rc % 2;
                lc -= rc + rc % 2;
                rc = 0;
            } else {
                rc -= lc;
                int rem = rc % 2;
                res += rc / 2 + (rem == 0 ? 0 : 2);
                rc = 0;
                lc = 0;
            }
        }
        return res + lc;
    }

}

性能

1520.最多的不重叠子字符串

目标

给你一个只包含小写字母的字符串 s ,你需要找到 s 中最多数目的非空子字符串,满足如下条件:

  1. 这些字符串之间互不重叠,也就是说对于任意两个子字符串 s[i..j] 和 s[x..y] ,要么 j < x 要么 i > y 。
  2. 如果一个子字符串包含字符 char ,那么 s 中所有 char 字符都应该在这个子字符串中。

请你找到满足上述条件的最多子字符串数目。如果有多个解法有相同的子字符串数目,请返回这些子字符串总长度最小的一个解。可以证明最小总长度解是唯一的。

请注意,你可以以 任意 顺序返回最优解的子字符串。

示例 1:

输入:s = "adefaddaccc"
输出:["e","f","ccc"]
解释:下面为所有满足第二个条件的子字符串:
[
  "adefaddaccc"
  "adefadda",
  "ef",
  "e",
  "f",
  "ccc",
]
如果我们选择第一个字符串,那么我们无法再选择其他任何字符串,所以答案为 1 。如果我们选择 "adefadda" ,剩下子字符串中我们只可以选择 "ccc" ,它是唯一不重叠的子字符串,所以答案为 2 。同时我们可以发现,选择 "ef" 不是最优的,因为它可以被拆分成 2 个子字符串。所以最优解是选择 ["e","f","ccc"] ,答案为 3 。不存在别的相同数目子字符串解。

示例 2:

输入:s = "abbaccd"
输出:["d","bb","cc"]
解释:注意到解 ["d","abba","cc"] 答案也为 3 ,但它不是最优解,因为它的总长度更长。

说明:

  • 1 <= s.length <= 10^5
  • s 只包含小写英文字母。

思路

代码

性能

2472.不重叠回文子字符串的最大数目

目标

给你一个字符串 s 和一个 正 整数 k 。

从字符串 s 中选出一组满足下述条件且 不重叠 的子字符串:

  • 每个子字符串的长度 至少 为 k 。
  • 每个子字符串是一个 回文串 。

返回最优方案中能选择的子字符串的 最大 数目。

子字符串 是字符串中一个连续的字符序列。

示例 1 :

输入:s = "abaccdbbd", k = 3
输出:2
解释:可以选择 s = "abaccdbbd" 中斜体加粗的子字符串。"aba" 和 "dbbd" 都是回文,且长度至少为 k = 3 。
可以证明,无法选出两个以上的有效子字符串。

示例 2 :

输入:s = "adbcda", k = 2
输出:0
解释:字符串中不存在长度至少为 2 的回文子字符串。

说明:

  • 1 <= k <= s.length <= 2000
  • s 仅由小写英文字母组成

思路

返回字符串 s 中长度 至少 为 k 的不重叠回文子串的最大个数。

定义 dp[i] 表示 s[i, n - 1] 中长度为 k 的不重叠回文子串的最大数目,

  • 如果不选 s[i],dp[i] = dp[i + 1]
  • 如果选择 s[i],枚举终点 j,使得 j - i + 1 >= k,如果 s[i, j] 是回文,dp[i] = max(dp[i], 1 + dp[j + 1]))

需要快速判断子串是否是回文,可以预处理。

代码


/**
 * @date 2026-09-15 9:24
 */
public class MaxPalindromes2472 {

    public int maxPalindromes(String s, int k) {
        int n = s.length();
        boolean[][] isPalindrome = new boolean[n][n];
        for (int i = 0; i < n; i++) {
            isPalindrome[i][i] = true;
            int l = i - 1, r = i + 1;
            while (l >= 0 && r < n && s.charAt(l) == s.charAt(r)){
                isPalindrome[l--][r++] = true;
            }
            l = i - 1;
            r = i;
            while (l >= 0 && r < n && s.charAt(l) == s.charAt(r)){
                isPalindrome[l--][r++] = true;
            }
        }
        int[] dp = new int[n + 1];
        for (int i = n - k; i >= 0; i--) {
            dp[i] = dp[i + 1];
            for (int j = n - 1; j >= i + k - 1; j--) {
                if (isPalindrome[i][j]) {
                    dp[i] = Math.max(dp[i], 1 + dp[j + 1]);
                }
            }
        }
        return dp[0];
    }

}

性能

3016.输入单词需要的最少按键次数II

目标

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

电话键盘上的按键与 不同 小写英文字母集合相映射,可以通过按压按键来组成单词。例如,按键 2 对应 ["a","b","c"],我们需要按一次键来输入 "a",按两次键来输入 "b",按三次键来输入 "c"。

现在允许你将编号为 2 到 9 的按键重新映射到 不同 字母集合。每个按键可以映射到 任意数量 的字母,但每个字母 必须 恰好 映射到 一个 按键上。你需要找到输入字符串 word 所需的 最少 按键次数。

返回重新映射按键后输入 word 所需的 最少 按键次数。

下面给出了一种电话键盘上字母到按键的映射作为示例。注意 1,*,# 和 0 不 对应任何字母。

示例 1:

输入:word = "abcde"
输出:5
解释:图片中给出的重新映射方案的输入成本最小。
"a" -> 在按键 2 上按一次
"b" -> 在按键 3 上按一次
"c" -> 在按键 4 上按一次
"d" -> 在按键 5 上按一次
"e" -> 在按键 6 上按一次
总成本为 1 + 1 + 1 + 1 + 1 = 5 。
可以证明不存在其他成本更低的映射方案。

示例 2:

输入:word = "xyzxyzxyzxyz"
输出:12
解释:图片中给出的重新映射方案的输入成本最小。
"x" -> 在按键 2 上按一次
"y" -> 在按键 3 上按一次
"z" -> 在按键 4 上按一次
总成本为 1 * 4 + 1 * 4 + 1 * 4 = 12 。
可以证明不存在其他成本更低的映射方案。
注意按键 9 没有映射到任何字母:不必让每个按键都存在与之映射的字母,但是每个字母都必须映射到按键上。

示例 3:

输入:word = "aabbccddeeffgghhiiiiii"
输出:24
解释:图片中给出的重新映射方案的输入成本最小。
"a" -> 在按键 2 上按一次
"b" -> 在按键 3 上按一次
"c" -> 在按键 4 上按一次
"d" -> 在按键 5 上按一次
"e" -> 在按键 6 上按一次
"f" -> 在按键 7 上按一次
"g" -> 在按键 8 上按一次
"h" -> 在按键 9 上按两次
"i" -> 在按键 9 上按一次
总成本为 1 * 2 + 1 * 2 + 1 * 2 + 1 * 2 + 1 * 2 + 1 * 2 + 1 * 2 + 2 * 2 + 6 * 1 = 24 。
可以证明不存在其他成本更低的映射方案。

说明:

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

思路

可以将 26 个字母映射到 2 ~ 9 按键上,比如将 abc 映射到 2,那么输入 b 需要按两下 2,输入 c 需要按三下 2。给定一个单词 word,返回输入该字符所需的最少按键次数。

与 3014.输入单词需要的最少按键次数I 相比,本题并没有说 word 是由不同字母组成。

将字母出现频次从大到小排序,大的优先分配在按键的第一个位置(按一下),然后是第二个位置(按两下),以此类推。

代码


/**
 * @date 2026-07-31 9:02
 */
public class MinimumPushes3016 {

    public int minimumPushes(String word) {
        int[] cnt = new int[26];
        for (char c : word.toCharArray()) {
            cnt[c - 'a']++;
        }
        PriorityQueue<int[]> q = new PriorityQueue<>((a, b) -> b[1] - a[1]);
        for (int i = 0; i < 26; i++) {
            q.offer(new int[]{i, cnt[i]});
        }
        int k = 0;
        int res = 0;
        for (int i = 0; i < 26; i++) {
            int[] e = q.poll();
            if (e[1] == 0) {
                break;
            }
            res += (k++ / 8 + 1) * e[1];
        }
        return res;
    }
}

性能

3014.输入单词需要的最少按键次数I

目标

给你一个字符串 word,由 不同 小写英文字母组成。

电话键盘上的按键与 不同 小写英文字母集合相映射,可以通过按压按键来组成单词。例如,按键 2 对应 ["a","b","c"],我们需要按一次键来输入 "a",按两次键来输入 "b",按三次键来输入 "c"。

现在允许你将编号为 2 到 9 的按键重新映射到 不同 字母集合。每个按键可以映射到 任意数量 的字母,但每个字母 必须 恰好 映射到 一个 按键上。你需要找到输入字符串 word 所需的 最少 按键次数。

返回重新映射按键后输入 word 所需的 最少 按键次数。

下面给出了一种电话键盘上字母到按键的映射作为示例。注意 1,*,# 和 0 不 对应任何字母。

示例 1:

输入:word = "abcde"
输出:5
解释:图片中给出的重新映射方案的输入成本最小。
"a" -> 在按键 2 上按一次
"b" -> 在按键 3 上按一次
"c" -> 在按键 4 上按一次
"d" -> 在按键 5 上按一次
"e" -> 在按键 6 上按一次
总成本为 1 + 1 + 1 + 1 + 1 = 5 。
可以证明不存在其他成本更低的映射方案。

示例 2:

输入:word = "xycdefghij"
输出:12
解释:图片中给出的重新映射方案的输入成本最小。
"x" -> 在按键 2 上按一次
"y" -> 在按键 2 上按两次
"c" -> 在按键 3 上按一次
"d" -> 在按键 3 上按两次
"e" -> 在按键 4 上按一次
"f" -> 在按键 5 上按一次
"g" -> 在按键 6 上按一次
"h" -> 在按键 7 上按一次
"i" -> 在按键 8 上按一次
"j" -> 在按键 9 上按一次
总成本为 1 + 2 + 1 + 2 + 1 + 1 + 1 + 1 + 1 + 1 = 12 。
可以证明不存在其他成本更低的映射方案。

说明:

  • 1 <= word.length <= 26
  • word 仅由小写英文字母组成。
  • word 中的所有字母互不相同。

思路

可以将 26 个字母映射到 2 ~ 9 按键上,比如将 abc 映射到 2,那么输入 b 需要按两下 2,输入 c 需要按三下 2。给定一个由 不同字母 组成的单词 word,返回输入该字符所需的最少按键次数。

字母出现的频次都是 1,先将所有按键的第一个位置填满,然后是第二个、第三个。

录入单词所需的按键次数为 8 * (1 + 2 + …… + k) + (n % 8) * (k + 1) = 8 * (1 + k) * k / 2 + (n % 8) * (k + 1) = 4 * k * (k + 1) + (n % 8) * (k + 1) = (4 * k + n % 8) * (k + 1)。

代码


/**
 * @date 2026-07-30 9:10
 */
public class MinimumPushes3014 {

    public int minimumPushes_v1(String word) {
        int n = word.length();
        int k = n / 8;
        return (4 * k + n % 8) * (k + 1);
    }
}

性能

1464.数组中两元素的最大乘积

目标

给你一个整数数组 nums,请你选择数组的两个不同下标 i 和 j,使 (nums[i]-1)*(nums[j]-1) 取得最大值。

请你计算并返回该式的最大值。

示例 1:

输入:nums = [3,4,5,2]
输出:12 
解释:如果选择下标 i=1 和 j=2(下标从 0 开始),则可以获得最大值,(nums[1]-1)*(nums[2]-1) = (4-1)*(5-1) = 3*4 = 12 。 

示例 2:

输入:nums = [1,5,4,5]
输出:16
解释:选择下标 i=1 和 j=3(下标从 0 开始),则可以获得最大值 (5-1)*(5-1) = 16 。

示例 3:

输入:nums = [3,7]
输出:12

说明:

  • 2 <= nums.length <= 500
  • 1 <= nums[i] <= 10^3

思路

从数组中取两个不同下标的元素相乘并返回最大的乘积。

找到数组最大的两个元素相乘即可。

代码


/**
 * @date 2026-07-27 8:55
 */
public class MaxProduct1464 {

    public int maxProduct(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        return (nums[n - 1] - 1) * (nums[n - 2] - 1);
    }
}

性能

628.三个数的最大乘积

目标

给你一个整型数组 nums ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。

示例 1:

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

示例 2:

输入:nums = [1,2,3,4]
输出:24

示例 3:

输入:nums = [-1,-2,-3]
输出:-6

说明:

  • 3 <= nums.length <= 10^4
  • -1000 <= nums[i] <= 1000

思路

从数组中取三个不同的下标,返回其乘积的最大值。

由于存在负数,分情况讨论,定义 max1、max2、max3 分别为前三大元素,min1、min2 分别是前二小元素:

  • 如果 max1 < 0, 乘积只能为负,要使乘积最大,那么其绝对值应该最小,因此取 max1 * max2 * max3
  • 如果 max2 < 0, 最大乘积可以为正,取最小的两个负数相乘,乘积最大,取 max1 * min1 * min2
  • 如果 max3 < 0,乘积可能为正也可能为负(只有三个元素),不论那种情况,都可取 max1 * min1 * min2
  • 如果 max3 > 0, 最大值可能是 max1 * max2 * max3 或者 max1 * min1 * min2

代码


/**
 * @date 2026-07-27 16:37
 */
public class MaximumProduct628 {

    public int maximumProduct_v1(int[] nums) {
        int max1 = -1001, max2 = -1001, max3 = -1001;
        int min1 = 1001, min2 = 1001;
        for (int num : nums) {
            if (num > max1) {
                max3 = max2;
                max2 = max1;
                max1 = num;
            } else if (num > max2) {
                max3 = max2;
                max2 = num;
            } else if (num > max3) {
                max3 = num;
            }
            if (num < min1) {
                min2 = min1;
                min1 = num;
            } else if (num < min2) {
                min2 = num;
            }
        }
        return Math.max(max1 * max2 * max3, max1 * min1 * min2);
    }

}

性能