2829.k-avoiding数组的最小总和

目标

给你两个整数 n 和 k 。

对于一个由 不同 正整数组成的数组,如果其中不存在任何求和等于 k 的不同元素对,则称其为 k-avoiding 数组。

返回长度为 n 的 k-avoiding 数组的可能的最小总和。

示例 1:

输入:n = 5, k = 4
输出:18
解释:设若 k-avoiding 数组为 [1,2,4,5,6] ,其元素总和为 18 。
可以证明不存在总和小于 18 的 k-avoiding 数组。

示例 2:

输入:n = 2, k = 6
输出:3
解释:可以构造数组 [1,2] ,其元素总和为 3 。
可以证明不存在总和小于 3 的 k-avoiding 数组。 

说明:

  • 1 <= n, k <= 50

思路

定义 k-avoiding 数组是由不同的正整数组成,并且任意两个元素的和不等于 k 的数组。求长度为 n 的 k-avoiding 数组的最小和。

构造一个长度为 n 的正整数数组,要使和最小,需要从 num = 1 开始选,跳过 k - num

网友指出可以使用等差数列求和来计算,第一部分是 1 ~ m, m = min(k / 2, n) 和为 m * (m + 1) / 2,第二部分是 k ~ k + n - m - 1,和为 (n - m) * (2 * k + n - m - 1) / 2

代码


/**
 * @date 2025-03-26 0:13
 */
public class MinimumSum2829 {

    public int minimumSum(int n, int k) {
        int res = 0;
        int length = 0;
        int num = 1;
        Set<Integer> avoiding = new HashSet<>();
        while (length < n) {
            if (avoiding.contains(num)) {
                num++;
                continue;
            }
            if (num < k) {
                avoiding.add(k - num);
            }
            length++;
            res += num;
            num++;
        }
        return res;
    }

}

性能

2711.对角线上不同值的数量差

目标

给你一个下标从 0 开始、大小为 m x n 的二维矩阵 grid ,请你求解大小同样为 m x n 的答案矩阵 answer 。

矩阵 answer 中每个单元格 (r, c) 的值可以按下述方式进行计算:

  • topLeft[r][c] 为矩阵 grid 中单元格 (r, c) 左上角对角线上 不同值 的数量。
  • bottomRight[r][c] 为矩阵 grid 中单元格 (r, c) 右下角对角线上 不同值 的数量。

然后 answer[r][c] = |topLeft[r][c] - bottomRight[r][c]|

返回矩阵 answer 。

矩阵对角线 是从最顶行或最左列的某个单元格开始,向右下方向走到矩阵末尾的对角线。

如果单元格 (r1, c1) 和单元格 (r, c) 属于同一条对角线且 r1 < r ,则单元格 (r1, c1) 属于单元格 (r, c) 的左上对角线。类似地,可以定义右下对角线。

示例 1:

输入:grid = [[1,2,3],[3,1,5],[3,2,1]]
输出:[[1,1,0],[1,0,1],[0,1,1]]
解释:第 1 个图表示最初的矩阵 grid 。 
第 2 个图表示对单元格 (0,0) 计算,其中蓝色单元格是位于右下对角线的单元格。
第 3 个图表示对单元格 (1,2) 计算,其中红色单元格是位于左上对角线的单元格。
第 4 个图表示对单元格 (1,1) 计算,其中蓝色单元格是位于右下对角线的单元格,红色单元格是位于左上对角线的单元格。
- 单元格 (0,0) 的右下对角线包含 [1,1] ,而左上对角线包含 [] 。对应答案是 |1 - 0| = 1 。
- 单元格 (1,2) 的右下对角线包含 [] ,而左上对角线包含 [2] 。对应答案是 |0 - 1| = 1 。
- 单元格 (1,1) 的右下对角线包含 [1] ,而左上对角线包含 [1] 。对应答案是 |1 - 1| = 0 。
其他单元格的对应答案也可以按照这样的流程进行计算。

示例 2:

输入:grid = [[1]]
输出:[[0]]
解释:- 单元格 (0,0) 的右下对角线包含 [] ,左上对角线包含 [] 。对应答案是 |0 - 0| = 0 。

说明:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n, grid[i][j] <= 50

思路

计算矩阵元素左上对角线的不同元素个数与右下对角线的不同元素个数的差的绝对值。即将元素所在的左上右下对角线,以当前元素为分界点(不包括当前元素),分成左上与右下两部分,计算每部分不同元素的个数,取差的绝对值。

暴力解法是枚举每个格子的左上右下元素,每一对角线都要遍历它所包含的元素个数次。

可以直接按对角线遍历,先记录前缀中的不同元素个数,然后再倒着遍历,计算差值的绝对值。

网友提到由于元素值不大,可以将其保存到 long 型数字中。

代码


/**
 * @date 2025-03-25 0:12
 */
public class DifferenceOfDistinctValues2711 {

    public int[][] differenceOfDistinctValues(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] res = new int[m][n];
        for (int i = 0; i < m + n - 1; i++) {
            int row = i / n < 1 ? 0 : i - n + 1 % m;
            int col = row > 0 ? 0 : n - 1 - i;
            Set<Integer> set = new HashSet<>();
            while (row < m && col < n){
                res[row][col] = set.size();
                set.add(grid[row][col]);
                row++;
                col++;
            }
            row--;
            col--;
            set.clear();
            while (row >= 0 && col >= 0){
                res[row][col] = Math.abs(res[row][col] - set.size());
                set.add(grid[row][col]);
                row--;
                col--;
            }
        }
        return res;
    }

}

性能

2255.统计是给定字符串前缀的字符串数目

目标

给你一个字符串数组 words 和一个字符串 s ,其中 words[i] 和 s 只包含 小写英文字母 。

请你返回 words 中是字符串 s 前缀 的 字符串数目 。

一个字符串的 前缀 是出现在字符串开头的子字符串。子字符串 是一个字符串中的连续一段字符序列。

示例 1:

输入:words = ["a","b","c","ab","bc","abc"], s = "abc"
输出:3
解释:
words 中是 s = "abc" 前缀的字符串为:
"a" ,"ab" 和 "abc" 。
所以 words 中是字符串 s 前缀的字符串数目为 3 。

示例 2:

输入:words = ["a","a"], s = "aa"
输出:2
解释:
两个字符串都是 s 的前缀。
注意,相同的字符串可能在 words 中出现多次,它们应该被计数多次。

说明:

  • 1 <= words.length <= 1000
  • 1 <= words[i].length, s.length <= 10
  • words[i] 和 s 只 包含小写英文字母。

思路

计算字符串数组 words 中有多少个字符串是 s 的前缀。

代码


/**
 * @date 2025-03-24 8:44
 */
public class CountPrefixes2255 {

    public int countPrefixes(String[] words, String s) {
        int res = 0;
        for (String word : words) {
            if (word.length() > s.length()){
                continue;
            }
            int cnt = 1;
            for (int i = 0; i < word.length(); i++) {
                if (word.charAt(i) != s.charAt(i)) {
                    cnt = 0;
                    break;
                }
            }
            res += cnt;
        }
        return res;
    }
}

性能

2116.判断一个括号字符串是否有效

目标

一个括号字符串是只由 '(' 和 ')' 组成的 非空 字符串。如果一个字符串满足下面 任意 一个条件,那么它就是有效的:

  • 字符串为 ().
  • 它可以表示为 AB(A 与 B 连接),其中A 和 B 都是有效括号字符串。
  • 它可以表示为 (A) ,其中 A 是一个有效括号字符串。

给你一个括号字符串 s 和一个字符串 locked ,两者长度都为 n 。locked 是一个二进制字符串,只包含 '0' 和 '1' 。对于 locked 中 每一个 下标 i :

  • 如果 locked[i] 是 '1' ,你 不能 改变 s[i] 。
  • 如果 locked[i] 是 '0' ,你 可以 将 s[i] 变为 '(' 或者 ')' 。

如果你可以将 s 变为有效括号字符串,请你返回 true ,否则返回 false 。

示例 1:

输入:s = "))()))", locked = "010100"
输出:true
解释:locked[1] == '1' 和 locked[3] == '1' ,所以我们无法改变 s[1] 或者 s[3] 。
我们可以将 s[0] 和 s[4] 变为 '(' ,不改变 s[2] 和 s[5] ,使 s 变为有效字符串。

示例 2:

输入:s = "()()", locked = "0000"
输出:true
解释:我们不需要做任何改变,因为 s 已经是有效字符串了。

示例 3:

输入:s = ")", locked = "0"
输出:false
解释:locked 允许改变 s[0] 。
但无论将 s[0] 变为 '(' 或者 ')' 都无法使 s 变为有效字符串。

示例 4:

输入:s = "(((())(((())", locked = "111111010111"
输出:true
解释:locked 允许我们改变 s[6] 和 s[8]。
我们将 s[6] 和 s[8] 改为 ')' 使 s 变为有效字符串。

说明:

  • n == s.length == locked.length
  • 1 <= n <= 10^5
  • s[i] 要么是 '(' 要么是 ')' 。
  • locked[i] 要么是 '0' 要么是 '1' 。

思路

有一个由 () 组成的非空字符串,如果 locked[i]0,可以任意修改 i 位置上的字符,判断能否使括号字符串变得有效。

从左到右累加未匹配的左括号数量,遇到 ( 加一,遇到 ) 减一,对于有效字符串最终会得到 0,并且有效字符串的任意前缀中未匹配的 ( 的数量总是大于等于 0

利用这一性质维护一个未匹配左括号数量的可能取值集合,如果最终集合中包含 0,则说明可以变为有效。具体实现时只需维护集合的最大值与最小值,因为集合中的数字都是同时加一减一,所以它们是连续的奇数或偶数。

具体来说就是遇到不可变字符,( min max 加一,) min max 减一。如果 max < 0,返回 false,如果 min < 0,则取 1(如果 min < 0,说明原来是偶数,由于 max 没有减为负数,说明还有比 0 大的偶数,那么最小值为 2 - 1)。如果遇到可变字符,max 加一,min 减一,如果 min < 0,则取 1

代码


/**
 * @date 2025-03-23 21:12
 */
public class CanBeValid2116 {

    public boolean canBeValid(String s, String locked) {
        int n = s.length();
        if (n % 2 == 1) {
            return false;
        }
        int max = 0, min = 0;
        char[] chars = s.toCharArray();
        for (int i = 0; i < n; i++) {
            if (locked.charAt(i) == '0') {
                max++;
                if (--min < 0) {
                    min = 1;
                }
                continue;
            }
            if ('(' == chars[i]) {
                max++;
                min++;
            } else {
                if (--max < 0) {
                    return false;
                }
                if (--min < 0) {
                    min = 1;
                }
            }
        }
        return min == 0;
    }
}

性能

2643.一最多的行

目标

给你一个大小为 m x n 的二进制矩阵 mat ,请你找出包含最多 1 的行的下标(从 0 开始)以及这一行中 1 的数目。

如果有多行包含最多的 1 ,只需要选择 行下标最小 的那一行。

返回一个由行下标和该行中 1 的数量组成的数组。

示例 1:

输入:mat = [[0,1],[1,0]]
输出:[0,1]
解释:两行中 1 的数量相同。所以返回下标最小的行,下标为 0 。该行 1 的数量为 1 。所以,答案为 [0,1] 。 

示例 2:

输入:mat = [[0,0,0],[0,1,1]]
输出:[1,2]
解释:下标为 1 的行中 1 的数量最多。该行 1 的数量为 2 。所以,答案为 [1,2] 。

示例 3:

输入:mat = [[0,0],[1,1],[0,0]]
输出:[1,2]
解释:下标为 1 的行中 1 的数量最多。该行 1 的数量为 2 。所以,答案为 [1,2] 。

说明:

  • m == mat.length
  • n == mat[i].length
  • 1 <= m, n <= 100
  • mat[i][j] 为 0 或 1

思路

返回二进制矩阵中 1 最多的行下标以及 1 的个数。

代码


/**
 * @date 2025-03-22 19:55
 */
public class RowAndMaximumOnes2643 {

    public int[] rowAndMaximumOnes(int[][] mat) {
        int m = mat.length;
        int n = mat[0].length;
        int[] res = new int[2];
        for (int i = 0; i < m; i++) {
            int cnt = 0;
            for (int j = 0; j < n; j++) {
                cnt += mat[i][j];
            }
            if (cnt > res[1]) {
                res[0] = i;
                res[1] = cnt;
            }
        }
        return res;
    }
}

性能

2680.最大或值

目标

给你一个下标从 0 开始长度为 n 的整数数组 nums 和一个整数 k 。每一次操作中,你可以选择一个数并将它乘 2 。

你最多可以进行 k 次操作,请你返回 nums[0] | nums[1] | ... | nums[n - 1] 的最大值。

a | b 表示两个整数 a 和 b 的 按位或 运算。

示例 1:

输入:nums = [12,9], k = 1
输出:30
解释:如果我们对下标为 1 的元素进行操作,新的数组为 [12,18] 。此时得到最优答案为 12 和 18 的按位或运算的结果,也就是 30 。

示例 2:

输入:nums = [8,1,2], k = 2
输出:35
解释:如果我们对下标 0 处的元素进行操作,得到新数组 [32,1,2] 。此时得到最优答案为 32|1|2 = 35 。

说明:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • 1 <= k <= 15

思路

有一个整数数组 nums,最多可以对它执行 k 次操作,每次操作可以任选一个数将其左移 1 位。求操作后数组所有元素的最大或值。

操作集中到同一个数上可以将最高有效位移到最高,少一次操作最高位就低一位。问题的关键在于,如果所有数字都有相同的最高有效位,移哪个是不确定的。

这时就需要枚举操作数了,每选择一个操作数就需要重新计算其余元素的或值。由于或运算没有逆运算,我们无法撤销已经或进去的值。

直接的想法是计算或前缀、后缀,枚举所有元素,将其左移 k 次,然后与前后缀进行或运算,取最大值。

网友提供了另一种基于位运算的解法,计算全体元素的或值,同时计算所有 bit 位上存在重复 1 的位置,通过异或运算将当前元素对或值的贡献抵消,然后补上重复位置上的 1

代码


/**
 * @date 2025-03-21 0:08
 */
public class MaximumOr2680 {

    public long maximumOr_v1(int[] nums, int k) {
        int n = nums.length;
        int or = 0;
        int multiBits = 0;
        for (int i = 0; i < n; i++) {
            multiBits = multiBits | or & nums[i];
            or = or | nums[i];
        }
        long res = 0L;
        for (int i = 0; i < n; i++) {
            res = Math.max(res, or ^ nums[i] | multiBits | ((long) nums[i] << k));
        }
        return res;
    }

    public long maximumOr(int[] nums, int k) {
        int n = nums.length;
        int[] prefix = new int[n + 1];
        int[] suffix = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            prefix[i] = prefix[i - 1] | nums[i - 1];
            suffix[n - i] = suffix[n - i + 1] | nums[n - i];
        }
        long res = 0L;
        for (int i = 0; i < n; i++) {
            res = Math.max(res, prefix[i] | suffix[i + 1] | ((long) nums[i] << k));
        }
        return res;
    }

}

性能

2612.最少翻转操作数

目标

给你一个整数 n 和一个在范围 [0, n - 1] 以内的整数 p ,它们表示一个长度为 n 且下标从 0 开始的数组 arr ,数组中除了下标为 p 处是 1 以外,其他所有数都是 0 。

同时给你一个整数数组 banned ,它包含数组中的一些位置。banned 中第 i 个位置表示 arr[banned[i]] = 0 ,题目保证 banned[i] != p 。

你可以对 arr 进行 若干次 操作。一次操作中,你选择大小为 k 的一个 子数组 ,并将它 翻转 。在任何一次翻转操作后,你都需要确保 arr 中唯一的 1 不会到达任何 banned 中的位置。换句话说,arr[banned[i]] 始终 保持 0 。

请你返回一个数组 ans ,对于 [0, n - 1] 之间的任意下标 i ,ans[i] 是将 1 放到位置 i 处的 最少 翻转操作次数,如果无法放到位置 i 处,此数为 -1 。

  • 子数组 指的是一个数组里一段连续 非空 的元素序列。
  • 对于所有的 i ,ans[i] 相互之间独立计算。
  • 将一个数组中的元素 翻转 指的是将数组中的值变成 相反顺序 。

示例 1:

输入:n = 4, p = 0, banned = [1,2], k = 4
输出:[0,-1,-1,1]
解释:k = 4,所以只有一种可行的翻转操作,就是将整个数组翻转。一开始 1 在位置 0 处,所以将它翻转到位置 0 处需要的操作数为 0 。
我们不能将 1 翻转到 banned 中的位置,所以位置 1 和 2 处的答案都是 -1 。
通过一次翻转操作,可以将 1 放到位置 3 处,所以位置 3 的答案是 1 。

示例 2:

输入:n = 5, p = 0, banned = [2,4], k = 3
输出:[0,-1,-1,-1,-1]
解释:这个例子中 1 一开始在位置 0 处,所以此下标的答案为 0 。
翻转的子数组长度为 k = 3 ,1 此时在位置 0 处,所以我们可以翻转子数组 [0, 2],但翻转后的下标 2 在 banned 中,所以不能执行此操作。
由于 1 没法离开位置 0 ,所以其他位置的答案都是 -1 。

示例 3:

输入:n = 4, p = 2, banned = [0,1,3], k = 1
输出:[-1,-1,0,-1]
解释:这个例子中,我们只能对长度为 1 的子数组执行翻转操作,所以 1 无法离开初始位置。

说明:

  • 1 <= n <= 10^5
  • 0 <= p <= n - 1
  • 0 <= banned.length <= n - 1
  • 0 <= banned[i] <= n - 1
  • 1 <= k <= n
  • banned[i] != p
  • banned 中的值 互不相同 。

思路

// todo

代码

性能

2610.转换二维数组

目标

给你一个整数数组 nums 。请你创建一个满足以下条件的二维数组:

  • 二维数组应该 只 包含数组 nums 中的元素。
  • 二维数组中的每一行都包含 不同 的整数。
  • 二维数组的行数应尽可能 少 。

返回结果数组。如果存在多种答案,则返回其中任何一种。

请注意,二维数组的每一行上可以存在不同数量的元素。

示例 1:

输入:nums = [1,3,4,1,2,3,1]
输出:[[1,3,4,2],[1,3],[1]]
解释:根据题目要求可以创建包含以下几行元素的二维数组:
- 1,3,4,2
- 1,3
- 1
nums 中的所有元素都有用到,并且每一行都由不同的整数组成,所以这是一个符合题目要求的答案。
可以证明无法创建少于三行且符合题目要求的二维数组。

示例 2:

输入:nums = [1,2,3,4]
输出:[[4,3,2,1]]
解释:nums 中的所有元素都不同,所以我们可以将其全部保存在二维数组中的第一行。

说明:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= nums.length

思路

将数组转化为二维数组,要求每一行元素没有重复,并且行数尽可能地少。

使用哈希表对相同元素计数,将所有 key 放入一行并将所有 key 的计数减一,如果计数减为 0 则删除 key,直到哈希表为空,时间复杂度为 O(n)

也可以将哈希表改为数组相当于计数排序。

代码


/**
 * @date 2025-03-19 0:21
 */
public class FindMatrix2610 {

    public List<List<Integer>> findMatrix(int[] nums) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.merge(num, 1, Integer::sum);
        }
        List<List<Integer>> res = new ArrayList<>();
        while (!map.isEmpty()) {
            res.add(new ArrayList<>());
            List<Integer> list = res.get(res.size() - 1);
            Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<Integer, Integer> entry = iterator.next();
                list.add(entry.getKey());
                entry.setValue(entry.getValue() - 1);
                if (entry.getValue() == 0) {
                    iterator.remove();
                }
            }
        }
        return res;
    }

}

性能

2614.对角线上的质数

目标

给你一个下标从 0 开始的二维整数数组 nums 。

返回位于 nums 至少一条 对角线 上的最大 质数 。如果任一对角线上均不存在质数,返回 0 。

注意:

  • 如果某个整数大于 1 ,且不存在除 1 和自身之外的正整数因子,则认为该整数是一个质数。
  • 如果存在整数 i ,使得 nums[i][i] = val 或者 nums[i][nums.length - i - 1]= val ,则认为整数 val 位于 nums 的一条对角线上。

在上图中,一条对角线是 [1,5,9] ,而另一条对角线是 [3,5,7] 。

示例 1:

输入:nums = [[1,2,3],[5,6,7],[9,10,11]]
输出:11
解释:数字 1、3、6、9 和 11 是所有 "位于至少一条对角线上" 的数字。由于 11 是最大的质数,故返回 11 。

示例 2:

输入:nums = [[1,2,3],[5,17,7],[9,11,10]]
输出:17
解释:数字 1、3、9、10 和 17 是所有满足"位于至少一条对角线上"的数字。由于 17 是最大的质数,故返回 17 。

说明:

  • 1 <= nums.length <= 300
  • nums.length == numsi.length
  • 1 <= nums[i][j] <= 4 * 10^6

思路

n x n 矩阵对角线上的最大质数,对角线指 (i, i)(i, n - 1 - i) 上的元素。

由于本题只需判断对角线上的元素值是否是质数,总个数不超过 2n600 个。可以直接枚举元素,判断元素值是否存在 1 和它本身以外的因子。

代码


/**
 * @date 2025-03-18 9:06
 */
public class DiagonalPrime2614 {

    public int diagonalPrime(int[][] nums) {
        int n = nums.length;
        int m = nums[0].length;
        int res = 0;
        for (int i = 0; i < n; i++) {
            if (nums[i][i] > res && isPrime(nums[i][i])) {
                res = nums[i][i];
            }
            if (nums[i][m - 1 - i] > res && isPrime(nums[i][m - 1 - i])) {
                res = nums[i][m - 1 - i];
            }
        }
        return res;
    }

    public boolean isPrime(int num) {
        if (num <= 1) {
            return false;
        }
        if (num <= 3) {
            return true;
        }
        if (num % 2 == 0 || num % 3 == 0) {
            return false;
        }
        for (int i = 5; i * i <= num; i += 6) {
            if (num % i == 0 || num % (i + 2) == 0) {
                return false;
            }
        }
        return true;
    }

}

性能

1963.使字符串平衡的最小交换次数

目标

给你一个字符串 s ,下标从 0 开始 ,且长度为偶数 n 。字符串 恰好 由 n / 2 个开括号 '[' 和 n / 2 个闭括号 ']' 组成。

只有能满足下述所有条件的字符串才能称为 平衡字符串 :

  • 字符串是一个空字符串,或者
  • 字符串可以记作 AB ,其中 A 和 B 都是 平衡字符串 ,或者
  • 字符串可以写成 [C] ,其中 C 是一个 平衡字符串 。

你可以交换 任意 两个下标所对应的括号 任意 次数。

返回使 s 变成 平衡字符串 所需要的 最小 交换次数。

示例 1:

输入:s = "][]["
输出:1
解释:交换下标 0 和下标 3 对应的括号,可以使字符串变成平衡字符串。
最终字符串变成 "[[]]" 。

示例 2:

输入:s = "]]][[["
输出:2
解释:执行下述操作可以使字符串变成平衡字符串:
- 交换下标 0 和下标 4 对应的括号,s = "[]][][" 。
- 交换下标 1 和下标 5 对应的括号,s = "[[][]]" 。
最终字符串变成 "[[][]]" 。

示例 3:

输入:s = "[]"
输出:0
解释:这个字符串已经是平衡字符串。

说明:

  • n == s.length
  • 2 <= n <= 10^6
  • n 为偶数
  • s[i] 为'[' 或 ']'
  • 开括号 '[' 的数目为 n / 2 ,闭括号 ']' 的数目也是 n / 2

思路

有一个长度为偶数 n 的字符串,有 n / 2[]。定义括号能够匹配的字符串是平衡字符串。每一次操作可以交换任意两个下标对应的括号,求将字符串变平衡的最少操作次数。

[[]][][] 都是平衡字符串,如何确定该与哪个括号交换才能使操作次数最小?对于 ]][[][][,第一个与最后一个交换之后变为平衡字符串 [][][[]]。而如果选择与倒数第二个 [ 交换,会变成 []][,需要再交换一次才能变为平衡字符串。

平衡字符串有这样的性质:其前缀中 [ 的个数总是大于等于 ] 的个数。如果发现 ] 的个数超过 [,那么必须要进行交换。最好将它交换到最后,因为这样可以减少它在前面前缀中对 ] 的贡献,将交换操作尽可能地延后,减小必须进行交换的次数。

也可以先将平衡的括号消掉,剩下的必然是 ]]]......[[[ 的形式。交换一次最多可以抵消 4 个,最少抵消 2 个,即 (stack.size() + 2) / 4

代码


/**
 * @date 2025-03-17 16:32
 */
public class MinSwaps1963 {

    public int minSwaps_v1(String s) {
        int n = s.length();
        int left = 0, right = 0;
        for (int i = 0; i < n; i++) {
            char c = s.charAt(i);
            if (c == '[') {
                left++;
            } else if (c == ']' && left > 0) {
                left--;
            } else {
                right++;
            }
        }
        return (left + right + 2) / 4;
    }

    public int minSwaps(String s) {
        int n = s.length();
        ArrayDeque<Character> stack = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            char c = s.charAt(i);
            if (c == ']' && !stack.isEmpty() && stack.peek() == '[') {
                stack.pop();
            } else {
                stack.push(c);
            }
        }
        return (stack.size() + 2) / 4;
    }

}

性能