2972.统计移除递增子数组的数目II

目标

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

如果 nums 的一个子数组满足:移除这个子数组后剩余元素 严格递增 ,那么我们称这个子数组为 移除递增 子数组。比方说,[5, 3, 4, 6, 7] 中的 [3, 4] 是一个移除递增子数组,因为移除该子数组后,[5, 3, 4, 6, 7] 变为 [5, 6, 7] ,是严格递增的。

请你返回 nums 中 移除递增 子数组的总数目。

注意,剩余元素为空的数组也视为是递增的。

子数组 指的是一个数组中一段连续的元素序列。

示例 1:

输入:nums = [1,2,3,4]
输出:10
解释:10 个移除递增子数组分别为:[1], [2], [3], [4], [1,2], [2,3], [3,4], [1,2,3], [2,3,4] 和 [1,2,3,4]。移除任意一个子数组后,剩余元素都是递增的。注意,空数组不是移除递增子数组。

示例 2:

输入:nums = [6,5,7,8]
输出:7
解释:7 个移除递增子数组分别为:[5], [6], [5,7], [6,5], [5,7,8], [6,5,7] 和 [6,5,7,8] 。
nums 中只有这 7 个移除递增子数组。

示例 3:

输入:nums = [8,7,6,6]
输出:3
解释:3 个移除递增子数组分别为:[8,7,6], [7,6,6] 和 [8,7,6,6] 。注意 [8,7] 不是移除递增子数组因为移除 [8,7] 后 nums 变为 [6,6] ,它不是严格递增的。

说明:

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

思路

有一个正整数数组,如果移除某些子数组可以使得剩余元素严格递增,则称为移除递增子数组。求移除递增子数组的个数。

显然移除递增子数组 [i, j] 后,前后的子数组严格递增,且 nums[i - 1] < nums[j + 1]。我们的目标是要找到有多少种 i, j 的组合满足条件,假设 i ≤ j

今天又重新思考了一下,可以先求出数组最长的严格递增前缀的最后一个元素之后的下标 first,以及最长的 严格递增后缀第一个元素下标 end。这里下标的含义直接影响着后续的处理,比如这里没有定义end为后缀第一个元素的前一个元素,那么后面遍历的时候j的初值就要取end - 1,因为判断 j == n - 1 时的处理逻辑是排除了所有后缀直接加1,而如果j初值取end,表示nums[j]是被留下来的,就不能简单的加1了,还需要与前面前缀的最后一个元素比较。

  • 如果 first 不在数组下标范围内,说明数组整体严格递增,这时所有子数组都满足条件,n 个元素数组的子数组个数为 n * (n + 1) / 2
  • 否则,循环遍历 i ∈[0, first],令j = end - 1,用 nums[i - 1] 依次与后缀 [end, n) 范围内的元素比较,如果 nums[i - 1] < nums[j + 1] 则直接增加 n - 1 - (j + 1) + 1 + 1 = n - j 个,其中包括[j + 1, n - 1]、……、[n - 1, n - 1] 以及 [0, i)这里省略了与不同前缀的组合(下同)特别注意:
    • i == 0 时会越界,需要特殊处理。这里剩余的子数组包括:ϕ、[end, n - 1]、[end + 1, n - 1]、……、[n - 1, n - 1],从 end 到 n - 1n - 1 - end + 1 = n - end 个,再加上ϕ 即 n - end + 1不能使用公式计算后缀子数组的个数!因为前面存在非严格递增的子数组需要排除掉,而剩余数组是移除递增子数组得到的,前面移除了,后面就必须全部保留,比如 [1, 2, 3] 的子数组 [1] [1,2] [1,2,3] [2] [2,3] [3],我们只能得到 [1,2,3] [2,3] [3] 再加一个ϕ
    • j = n - 1 时会越界,这时表明已经排除了所有后缀,所以直接加1即可,表示前缀子数组 [0, i)

代码

/**
 * @date 2024-07-11 13:12
 */
public class IncremovableSubarrayCount2972 {

    public long incremovableSubarrayCount(int[] nums) {
        int n = nums.length;
        long res = 0;
        int first = -1;
        int end = -1;
        for (int i = 1; i < n; i++) {
            if (nums[i] <= nums[i - 1]) {
                first = i;
                break;
            }
        }
        if (first == -1) {
            return n * (n + 1) / 2;
        } else {
            for (int i = n - 2; i >= 0; i--) {
                if (nums[i + 1] <= nums[i]) {
                    end = i + 1;
                    break;
                }
            }
        }

        for (int i = 0; i <= first; i++) {
            if (i == 0) {
                res += n - end + 1;
                continue;
            }
            for (int j = end - 1; j < n; j++) {
                if (j == n - 1) {
                    res += 1;
                } else if (nums[i - 1] < nums[j + 1]) {
                    res += n - j;
                    break;
                }
            }
        }
        return res;
    }
}

性能

2970.统计移除递增子数组的数目I

目标

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

如果 nums 的一个子数组满足:移除这个子数组后剩余元素 严格递增 ,那么我们称这个子数组为 移除递增 子数组。比方说,[5, 3, 4, 6, 7] 中的 [3, 4] 是一个移除递增子数组,因为移除该子数组后,[5, 3, 4, 6, 7] 变为 [5, 6, 7] ,是严格递增的。

请你返回 nums 中 移除递增 子数组的总数目。

注意 ,剩余元素为空的数组也视为是递增的。

子数组 指的是一个数组中一段连续的元素序列。

示例 1:

输入:nums = [1,2,3,4]
输出:10
解释:10 个移除递增子数组分别为:[1], [2], [3], [4], [1,2], [2,3], [3,4], [1,2,3], [2,3,4] 和 [1,2,3,4]。移除任意一个子数组后,剩余元素都是递增的。注意,空数组不是移除递增子数组。

示例 2:

输入:nums = [6,5,7,8]
输出:7
解释:7 个移除递增子数组分别为:[5], [6], [5,7], [6,5], [5,7,8], [6,5,7] 和 [6,5,7,8] 。
nums 中只有这 7 个移除递增子数组。

示例 3:

输入:nums = [8,7,6,6]
输出:3
解释:3 个移除递增子数组分别为:[8,7,6], [7,6,6] 和 [8,7,6,6] 。注意 [8,7] 不是移除递增子数组因为移除 [8,7] 后 nums 变为 [6,6] ,它不是严格递增的。

说明:

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

思路

有一个正整数数组,如果移除某些子数组可以使得剩余元素严格递增,则称为移除递增子数组。求移除递增子数组的个数。

显然移除递增子数组 [i, j] 后,前后的子数组严格递增,且 nums[i - 1] < nums[j + 1]。我们的目标是要找到有多少种 i, j 的组合满足条件,假设 i ≤ j

这题的难度和我的预期不一致,作为一道简单题,编码过程也太过复杂了,边界处理以及各种特殊情况太容易出错了。

先考虑暴力求解吧,枚举i, j的组合,遍历其余元素判断是否严格递增。

看了评论,这个数据范围改一下就是一道困难题 2972.统计移除递增子数组的数目II

代码

/**
 * @date 2024-07-10 1:27
 */
public class IncremovableSubarrayCount2970 {

    public int incremovableSubarrayCount(int[] nums) {
        int n = nums.length;
        int res = 0;
        for (int i = 0; i < n; i++) {
            here:
            for (int j = i; j < n; j++) {
                int last = -1;
                for (int k = 0; k < n; k++) {
                    if ((k < i || k > j) && nums[k] > last) {
                        last = nums[k];
                    } else if (k < i || k > j) {
                        continue here;
                    }
                }
                res++;
            }
        }
        return res;
    }

}

性能

O(n^3)

3102.最小化曼哈顿距离

目标

给你一个下标从 0 开始的数组 points ,它表示二维平面上一些点的整数坐标,其中 points[i] = [xi, yi] 。

两点之间的距离定义为它们的 曼哈顿距离。两个单元格 (xi, yi) 和 (xj, yj) 之间的曼哈顿距离为 |xi - xj| + |yi - yj|。

请你恰好移除一个点,返回移除后任意两点之间的 最大 距离可能的 最小 值。

示例 1:

输入:points = [[3,10],[5,15],[10,2],[4,4]]
输出:12
解释:移除每个点后的最大距离如下所示:
- 移除第 0 个点后,最大距离在点 (5, 15) 和 (10, 2) 之间,为 |5 - 10| + |15 - 2| = 18 。
- 移除第 1 个点后,最大距离在点 (3, 10) 和 (10, 2) 之间,为 |3 - 10| + |10 - 2| = 15 。
- 移除第 2 个点后,最大距离在点 (5, 15) 和 (4, 4) 之间,为 |5 - 4| + |15 - 4| = 12 。
- 移除第 3 个点后,最大距离在点 (5, 15) 和 (10, 2) 之间的,为 |5 - 10| + |15 - 2| = 18 。
在恰好移除一个点后,任意两点之间的最大距离可能的最小值是 12 。

示例 2:

输入:points = [[1,1],[1,1],[1,1]]
输出:0
解释:移除任一点后,任意两点之间的最大距离都是 0 。

说明:

  • 3 <= points.length <= 10^5
  • points[i].length == 2
  • 1 <= points[i][0], points[i][1] <= 10^8

提示:

  • Notice that the Manhattan distance between two points [xi, yi] and [xj, yj] is max({xi - xj + yi - yj, xi - xj - yi + yj, - xi + xj + yi - yj, - xi + xj - yi + yj}).
  • If you replace points as [xi - yi, xi + yi] then the Manhattan distance is max(max(xi) - min(xi), max(yi) - min(yi)) over all i.
  • After those observations, the problem just becomes a simulation. Create multiset of points [xi - yi, xi + yi], you can iterate on a point you might remove and get the maximum Manhattan distance over all other points.

思路

有一些二维平面上的整数坐标点,移除其中一个点,求任意两点之间曼哈顿距离最大值的最小值。

n个点之间的曼哈顿距离有C(n,2) = n * (n - 1) / 2 个,移除其中一个点,最大值可能发生变化,需要找到其中最小的。

问题的关键是如何计算n个点之间曼哈顿距离的最大值,如果暴力计算,找出最大值的时间复杂度是 O(n^2),还要依次移除一个点找到其中的最小值,综合起来时间复杂度是 O(n^3)。点的个数最多 10^5,暴力求解会超时。

我么需要考虑曼哈顿距离有什么特性,找出最大值需要每一个都遍历吗?移除一个点对最大值有什么影响,需要重新计算吗?

看了题目的提示是使用变量替换将问题转化:两点(xi, yi) 与 (xj, yj) 之间的曼哈顿距离为|xi - xj| + |yi - yj|,去掉绝对值即 max({xi - xj + yi - yj, xi - xj - yi + yj, - xi + xj + yi - yj, - xi + xj - yi + yj}),令 ui = xi - yi, vi = xi + yi,则点 (xi, yi) 与 (xj, yj) 之间的曼哈顿距离转换为 max({vi - vj, ui - uj, uj - ui, vj - vi}) = max({|vi - vj|, |ui- uj|})对于所有的 i,j,曼哈顿距离的最大值为 max({max(vi) - min(vi), max(ui) - min(ui)})。这样找出曼哈顿距离最大值的时间复杂度减少为 O(n)

于是,我们只需要将坐标点转化为 (ui, vi),依次去除某个坐标点,再找出 u、v 的最大值与最小值即可。试了一下,O(n^2)的时间复杂度仍会超时。

我们可以记录一下vi、ui取最大最小值时的坐标,然后排除这几个坐标点后找出其中的最小值,最坏的情况即坐标没有重复,时间复杂度为O(4n)。

我也尝试了记录次大与次小值,试图将复杂度降为 O(n),但需要考虑去除掉相应坐标点后,另一个坐标是否会受到影响。例如,max({max(vi) - min(vi), max(ui) - min(ui)}) 假如我们删去了vi最大的坐标,取次大的vi,但是后面ui的最大与最小是否受到影响?

因此我们不仅需要一次遍历获取到最大与次大,还要再次遍历比较当前值是否是最大或最小,如果是则取次大或次小。需要注意,这里的最大与次大以及最小与次小的值可以相同。更进一步,如果我们同时保存了x、y 取最大、最小的四个坐标,我们只需排除这4个坐标即可,其它坐标对最大距离没有影响。

看了官网题解,转换后的距离称为切比雪夫距离。事实上,max({|vi - vj|, |ui - uj|}) 计算的是 (vi, ui) (vj, uj) 两点曼哈顿距离投影到 x 轴(|vi - vj|) 和 y 轴(|ui - uj|)的线段长度的最大值,即切比雪夫距离。注意本题中的u、v对应题解中v、u,并且使用的是x-y,而非y-x,不过对于求解没有什么影响,无非是关于坐标轴镜像了一下。

代码

/**
 * @date 2024-07-09 10:20
 */
public class MinimumDistance3102 {

    public int minimumDistance(int[][] points) {
        for (int[] point : points) {
            int u = point[0] - point[1];
            int v = point[0] + point[1];
            point[0] = u;
            point[1] = v;
        }
        int n = points.length;
        int res = Integer.MAX_VALUE;
        int maxX = Integer.MIN_VALUE;
        int minX = Integer.MAX_VALUE;
        int maxY = Integer.MIN_VALUE;
        int minY = Integer.MAX_VALUE;
        int maxXIndex = -1, minXIndex = -1, maxYIndex = -1, minYIndex = -1;
        List<Integer> exclude = new ArrayList<>();
        for (int j = 0; j < n; j++) {
            if (points[j][0] > maxX) {
                maxX = points[j][0];
                maxXIndex = j;
            }
            if (points[j][0] < minX) {
                minX = points[j][0];
                minXIndex = j;
            }
            if (points[j][1] > maxY) {
                maxY = points[j][1];
                maxYIndex = j;
            }
            if (points[j][1] < minY) {
                minY = points[j][1];
                minYIndex = j;
            }
        }
        exclude.add(maxXIndex);
        exclude.add(minXIndex);
        exclude.add(maxYIndex);
        exclude.add(minYIndex);
        for (Integer i : exclude) {
            maxX = Integer.MIN_VALUE;
            minX = Integer.MAX_VALUE;
            maxY = Integer.MIN_VALUE;
            minY = Integer.MAX_VALUE;
            for (int j = 0; j < n; j++) {
                if (i != j) {
                    maxX = Math.max(maxX, points[j][0]);
                    minX = Math.min(minX, points[j][0]);
                    maxY = Math.max(maxY, points[j][1]);
                    minY = Math.min(minY, points[j][1]);
                }
            }
            res = Math.min(res, Math.max(maxX - minX, maxY - minY));
        }
        return res;
    }
}

性能

724.寻找数组的中心下标

目标

给你一个整数数组 nums ,请计算数组的 中心下标 。

数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。

如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。

如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。

示例 1:

输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。

示例 2:

输入:nums = [1, 2, 3]
输出:-1
解释:
数组中不存在满足此条件的中心下标。

示例 3:

输入:nums = [2, 1, -1]
输出:0
解释:
中心下标是 0 。
左侧数之和 sum = 0 ,(下标 0 左侧不存在元素),
右侧数之和 sum = nums[1] + nums[2] = 1 + -1 = 0 。

说明:

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

思路

寻找数组的中心下标,中心下标指左侧元素和等于右侧元素和,如果存在多个中心下标,取左边的那个。例如,[1, -1, 1, 0] 取1。

直接的想法是计算后缀和,然后从前计算累加和与后缀和比较,涉及两个下标 ii + 1 注意防止下标越界并且特殊处理最后一个元素。

官网题解比较清晰,计算数组累加和 sum,循环累加左侧和 lSum,如果 lSum * 2 + nums[i] == sum 表明 i 为中心下标。

代码

/**
 * @date 2024-07-08 0:01
 */
public class PivotIndex724 {

    /**
     * 参考官网题解思路
     */
    public int pivotIndex_v1(int[] nums) {
        int n = nums.length;
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        int lSum = 0;
        for (int i = 0; i < n; i++) {
            if (lSum * 2 + nums[i] == sum){
                return i;
            }
            lSum += nums[i];
        }
        return -1;
    }

    public int pivotIndex(int[] nums) {
        int n = nums.length;
        int[] suffix = new int[n];
        suffix[n - 1] = nums[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            suffix[i] = suffix[i + 1] + nums[i];
        }
        int sum = 0;
        for (int i = 0; i < n; i++) {
            if ((i < n - 1 && sum == suffix[i + 1]) || (i == n - 1 && sum == 0)) {
                return i;
            }
            sum += nums[i];
        }
        return -1;
    }
}

性能

官网题解更优

1958.检查操作是否合法

目标

给你一个下标从 0 开始的 8 x 8 网格 board ,其中 board[r][c] 表示游戏棋盘上的格子 (r, c) 。棋盘上空格用 '.' 表示,白色格子用 'W' 表示,黑色格子用 'B' 表示。

游戏中每次操作步骤为:选择一个空格子,将它变成你正在执行的颜色(要么白色,要么黑色)。但是,合法 操作必须满足:涂色后这个格子是 好线段的一个端点 (好线段可以是水平的,竖直的或者是对角线)。

好线段 指的是一个包含 三个或者更多格子(包含端点格子)的线段,线段两个端点格子为 同一种颜色 ,且中间剩余格子的颜色都为 另一种颜色 (线段上不能有任何空格子)。你可以在下图找到好线段的例子:

给你两个整数 rMove 和 cMove 以及一个字符 color ,表示你正在执行操作的颜色(白或者黑),如果将格子 (rMove, cMove) 变成颜色 color 后,是一个 合法 操作,那么返回 true ,如果不是合法操作返回 false 。

示例 1:

输入:board = [[".",".",".","B",".",".",".","."],[".",".",".","W",".",".",".","."],[".",".",".","W",".",".",".","."],[".",".",".","W",".",".",".","."],["W","B","B",".","W","W","W","B"],[".",".",".","B",".",".",".","."],[".",".",".","B",".",".",".","."],[".",".",".","W",".",".",".","."]], rMove = 4, cMove = 3, color = "B"
输出:true
解释:'.','W' 和 'B' 分别用颜色蓝色,白色和黑色表示。格子 (rMove, cMove) 用 'X' 标记。
以选中格子为端点的两个好线段在上图中用红色矩形标注出来了。

示例 2:

输入:board = [[".",".",".",".",".",".",".","."],[".","B",".",".","W",".",".","."],[".",".","W",".",".",".",".","."],[".",".",".","W","B",".",".","."],[".",".",".",".",".",".",".","."],[".",".",".",".","B","W",".","."],[".",".",".",".",".",".","W","."],[".",".",".",".",".",".",".","B"]], rMove = 4, cMove = 4, color = "W"
输出:false
解释:虽然选中格子涂色后,棋盘上产生了好线段,但选中格子是作为中间格子,没有产生以选中格子为端点的好线段。

说明:

  • board.length == board[r].length == 8
  • 0 <= rMove, cMove < 8
  • board[rMove][cMove] == '.'
  • color 要么是 'B' 要么是 'W' 。

思路

有一个 8 x 8 网格,网格中 . 表示空格,W 表示白色,B 表示黑色。现在需要给空格涂色,合法的操作指涂色的格子是好线段的端点。好线段长度至少包含三个格子,且两端颜色一致,中间颜色也一致但与两端颜色不同。给定一个操作,判断其是否合法。

按照题意从涂色端点开始依次遍历八个方向上是否存在好线段。

代码

/**
 * @date 2024-07-07 12:21
 */
public class CheckMove1958 {

    public boolean checkMove(char[][] board, int rMove, int cMove, char color) {
        if (board[rMove][cMove] != '.') {
            return false;
        }
        int[][] direction = new int[][]{
                {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1}
        };
        char opposite = (char) (color ^ 'B' ^ 'W');
        for (int[] d : direction) {
            int cnt = 0;
            int r = d[0];
            int c = d[1];
            while (rMove + r >= 0 && rMove + r < 8
                    && cMove + c >= 0 && cMove + c < 8
                    && board[rMove + r][cMove + c] == opposite) {
                r += d[0];
                c += d[1];
                cnt++;
            }
            // 题目要求至少三个格子,所以要求cnt>0
            if (cnt > 0 && rMove + r >= 0 && rMove + r < 8
                    && cMove + c >= 0 && cMove + c < 8
                    && board[rMove + r][cMove + c] == color) {
                return true;
            }
        }
        return false;
    }
}

性能

3101.交替子数组计数

目标

给你一个 二进制数组 nums 。

如果一个 子数组 中 不存在 两个 相邻 元素的值 相同 的情况,我们称这样的子数组为 交替子数组 。

返回数组 nums 中交替子数组的数量。

示例 1:

输入: nums = [0,1,1,1]
输出: 5
解释:
以下子数组是交替子数组:[0] 、[1] 、[1] 、[1] 以及 [0,1] 。

示例 2:

输入: nums = [1,0,1,0]
输出: 10
解释:
数组的每个子数组都是交替子数组。可以统计在内的子数组共有 10 个。

说明:

  • 1 <= nums.length <= 10^5
  • nums[i] 不是 0 就是 1 。

思路

返回二进制数组中交替子数组的数量。

记录相邻元素相同的下标,计算子数组的数量。拥有n个元素的数组,其子数组数量为 n * (n + 1) / 2

官网题解是另一种思路:

  • 如果相邻的元素 nums[i-1] ≠ nums[i],那么可以将 nums[i] 加到所有以 i-1 为右端点的子数组末尾,再加上nums[i]自身,即 以 i 为右端点的交替子数组数量 = 以 i-1 为右端点的交替子数组数量 + 1
  • 如果相邻元素相等,则记录以i为右端点的交替子数组数量为1。

其实本质都是一样的,一个是使用公式求解,一个是通过循环累加。

代码

/**
 * @date 2024-07-06 20:52
 */
public class CountAlternatingSubarrays3101 {
    /**
     * 官网题解
     */
    public long countAlternatingSubarrays_v1(int[] nums) {
        long res = 0, cur = 0;
        int pre = -1;
        for (int a : nums) {
            cur = (pre != a) ? cur + 1 : 1;
            pre = a;
            res += cur;
        }
        return res;
    }

    public long countAlternatingSubarrays(int[] nums) {
        int n = nums.length;
        long res = 0;
        int s = 0;
        for (int i = 1; i < n; i++) {
            if (nums[i] == nums[i - 1]) {
                // 这里个数的计算是从 s ~ i-1的元素个数 i - 1 - s + 1
                int k = i - s;
                res += k * (k + 1) / 2;
                s = i;
            }
        }
        // 注意处理结尾的情况
        if (s != n - 1) {
            // 这里计算的是 s ~ n-1 的元素个数 n - 1 - s + 1
            int k = n - s;
            // 这里要防止溢出,使用1L
            res += k * (k + 1L) / 2;
        } else {
            res++;
        }
        return res;
    }
}

性能

3033.修改矩阵

目标

给你一个下标从 0 开始、大小为 m x n 的整数矩阵 matrix ,新建一个下标从 0 开始、名为 answer 的矩阵。使 answer 与 matrix 相等,接着将其中每个值为 -1 的元素替换为所在列的 最大 元素。

返回矩阵 answer 。

示例 1:

输入:matrix = [[1,2,-1],[4,-1,6],[7,8,9]]
输出:[[1,2,9],[4,8,6],[7,8,9]]
解释:上图显示了发生替换的元素(蓝色区域)。
- 将单元格 [1][1] 中的值替换为列 1 中的最大值 8 。
- 将单元格 [0][2] 中的值替换为列 2 中的最大值 9 。

示例 2:

输入:matrix = [[3,-1],[5,2]]
输出:[[3,2],[5,2]]
解释:上图显示了发生替换的元素(蓝色区域)。

说明:

  • m == matrix.length
  • n == matrix[i].length
  • 2 <= m, n <= 50
  • -1 <= matrix[i][j] <= 100
  • 测试用例中生成的输入满足每列至少包含一个非负整数。

思路

使用矩阵各列的最大值替换该列中值为-1的元素。

先求出各列的最大值,然后替换。

代码

/**
 * @date 2024-07-05 0:26
 */
public class ModifiedMatrix3033 {

    /**
     * 优化到一个循环中,使用局部遍历保存当前列的最大值,
     * 没必要使用数组保存所有列的最大值然后再处理
     */
    public int[][] modifiedMatrix_v1(int[][] matrix) {
        int m = matrix.length;
        int n = matrix[0].length;
        for (int i = 0; i < n; i++) {
            int max = 0;
            for (int j = 0; j < m; j++) {
                max = Math.max(max, matrix[j][i]);
            }
            for (int j = 0; j < m; j++) {
                if (matrix[j][i] == -1) {
                    matrix[j][i] = max;
                }
            }
        }
        return matrix;
    }

    public int[][] modifiedMatrix(int[][] matrix) {
        int m = matrix.length;
        int n = matrix[0].length;
        int[] max = new int[n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                max[i] = Math.max(max[i], matrix[j][i]);
            }
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (matrix[j][i] == -1) {
                    matrix[j][i] = max[i];
                }
            }
        }
        return matrix;
    }
}

性能

3086.拾起K个1需要的最少行动次数

目标

给你一个下标从 0 开始的二进制数组 nums,其长度为 n ;另给你一个 正整数 k 以及一个 非负整数 maxChanges 。

Alice 在玩一个游戏,游戏的目标是让 Alice 使用 最少 数量的 行动 次数从 nums 中拾起 k 个 1 。游戏开始时,Alice 可以选择数组 [0, n - 1] 范围内的任何索引 aliceIndex 站立。如果 nums[aliceIndex] == 1 ,Alice 会拾起一个 1 ,并且 nums[aliceIndex] 变成0(这 不算 作一次行动)。之后,Alice 可以执行 任意数量 的 行动(包括零次),在每次行动中 Alice 必须 恰好 执行以下动作之一:

  • 选择任意一个下标 j != aliceIndex 且满足 nums[j] == 0 ,然后将 nums[j] 设置为 1 。这个动作最多可以执行 maxChanges 次。
  • 选择任意两个相邻的下标 x 和 y(|x - y| == 1)且满足 nums[x] == 1, nums[y] == 0 ,然后交换它们的值(将 nums[y] = 1 和 nums[x] = 0)。如果 y == aliceIndex,在这次行动后 Alice 拾起一个 1 ,并且 nums[y] 变成 0 。

返回 Alice 拾起 恰好 k 个 1 所需的 最少 行动次数。

示例 1:

输入:nums = [1,1,0,0,0,1,1,0,0,1], k = 3, maxChanges = 1
输出:3
解释:如果游戏开始时 Alice 在 aliceIndex == 1 的位置上,按照以下步骤执行每个动作,他可以利用 3 次行动拾取 3 个 1 :

游戏开始时 Alice 拾取了一个 1 ,nums[1] 变成了 0。此时 nums 变为 [1,0,0,0,0,1,1,0,0,1] 。
选择 j == 2 并执行第一种类型的动作。nums 变为 [1,0,1,0,0,1,1,0,0,1]
选择 x == 2 和 y == 1 ,并执行第二种类型的动作。nums 变为 [1,1,0,0,0,1,1,0,0,1] 。由于 y == aliceIndex,Alice 拾取了一个 1 ,nums 变为  [1,0,0,0,0,1,1,0,0,1] 。
选择 x == 0 和 y == 1 ,并执行第二种类型的动作。nums 变为 [0,1,0,0,0,1,1,0,0,1] 。由于 y == aliceIndex,Alice 拾取了一个 1 ,nums 变为  [0,0,0,0,0,1,1,0,0,1] 。
请注意,Alice 也可能执行其他的 3 次行动序列达成拾取 3 个 1 。

示例 2:

输入:nums = [0,0,0,0], k = 2, maxChanges = 3
输出:4
解释:如果游戏开始时 Alice 在 aliceIndex == 0 的位置上,按照以下步骤执行每个动作,他可以利用 4 次行动拾取 2 个 1 :

选择 j == 1 并执行第一种类型的动作。nums 变为 [0,1,0,0] 。
选择 x == 1 和 y == 0 ,并执行第二种类型的动作。nums 变为 [1,0,0,0] 。由于 y == aliceIndex,Alice 拾起了一个 1 ,nums 变为 [0,0,0,0] 。
再次选择 j == 1 并执行第一种类型的动作。nums 变为 [0,1,0,0] 。
再次选择 x == 1 和 y == 0 ,并执行第二种类型的动作。nums 变为 [1,0,0,0] 。由于y == aliceIndex,Alice 拾起了一个 1 ,nums 变为 [0,0,0,0] 。

说明:

  • 2 <= n <= 10^5
  • 0 <= nums[i] <= 1
  • 1 <= k <= 10^5
  • 0 <= maxChanges <= 10^5
  • maxChanges + sum(nums) >= k

思路

有一个二进制(元素不是0就是1)数组nums,选择一个固定的位置aliceIndex,如果该位置元素值为1,则可以拾起并将元素置0。接下来可以采取行动:

  1. 任选一个不等于aliceIndex且值为0的元素置1
  2. 将任意相邻且元素值不等的元素交换,如果其中一个位置是aliceIndex,且交换后的值为1,则可以拾起这个1并将元素置0

问恰好拾起k个1所需最小行动次数。

很明显行动1要选与aliceIndex相邻的,这样才可以用行动2将1拾起。

我们首先面对的问题是aliceIndex怎么选,要拾取1就需要将1都通过行动2移动到aliceIndex周围,如果拾取一个1的行动次数大于2的话就需要考虑使用行动1直接在aliceIndex周围设置1再拾取。

// todo

代码

性能

3099.哈沙德数

目标

如果一个整数能够被其各个数位上的数字之和整除,则称之为 哈沙德数(Harshad number)。给你一个整数 x 。如果 x 是 哈沙德数 ,则返回 x 各个数位上的数字之和,否则,返回 -1 。

示例 1:

输入: x = 18
输出: 9
解释: x 各个数位上的数字之和为 9 。18 能被 9 整除。因此 18 是哈沙德数,答案是 9 。

示例 2:

输入: x = 23
输出: -1
解释: x 各个数位上的数字之和为 5 。23 不能被 5 整除。因此 23 不是哈沙德数,答案是 -1 。

说明:

  • 1 <= x <= 100

思路

判断给定的整数x的各位数字之和能否整除x,如果可以返回各位数字之和,否则返回-1。

代码

/**
 * @date 2024-07-03 0:40
 */
public class SumOfTheDigitsOfHarshadNumber3099 {

    public int sumOfTheDigitsOfHarshadNumber(int x) {
        int sum = 0;
        // 容易出错的点是直接对x进行修改,后面求余的时候就错了
        int digit = x;
        while (digit > 0) {
            sum += digit % 10;
            digit /= 10;
        }
        return x % sum == 0 ? sum : -1;
    }
}

性能

3115.质数的最大距离

目标

给你一个整数数组 nums。

返回两个(不一定不同的)质数在 nums 中 下标 的 最大距离。

示例 1:

输入: nums = [4,2,9,5,3]
输出: 3
解释: nums[1]、nums[3] 和 nums[4] 是质数。因此答案是 |4 - 1| = 3。

示例 2:

输入: nums = [4,8,2,8]
输出: 0
解释: nums[2] 是质数。因为只有一个质数,所以答案是 |2 - 2| = 0。

说明:

  • 1 <= nums.length <= 3 * 10^5
  • 1 <= nums[i] <= 100
  • 输入保证 nums 中至少有一个质数。

思路

找出数组中质数的最远距离(下标之差)。

知识点:

  • 自然数:非负整数
  • 质数:只能被1和它本身整除的大于1的自然数
  • 合数:不是质数的大于1的自然数

如果 n 是一个合数,那么它可以分解为两个自然数 a、b 的乘积,即 n = a * b。设 a ≤ b ,如果 a ≥ √n ,那么 a * b ≥ n。 也就是说 a、b 要么同时等于 √n,要么一个大于一个小于 √n不可能同时大于√n。于是判断一个数是否是质数,只需判断 n 是否能够整除 1 ~ √n

除了2的偶数都不是质数,因此自增的步长可以设为2。

更进一步分析,所有质数除了2和3外,都形如 6k - 16k + 1。考虑 n % 6

  • 余数为0,首先6不是质数,能被6整除的数也不是质数
  • 余数为2、4,表明能够被2整除,不是质数
  • 余数为3,表明能被3整除,不是质数
  • 余数为5,6k - 1
  • 余数为1,6k + 1

从5开始,步长可以设为6。

代码

/**
 * @date 2024-07-02 9:13
 */
public class MaximumPrimeDifference3115 {

    public int maximumPrimeDifference_v2(int[] nums) {
        int i = 0, j = nums.length - 1;
        while (!isPrimeNumber(nums[i])) {
            i++;
        }
        while (!isPrimeNumber(nums[j])) {
            j--;
        }
        return j - i;
    }

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

    public static boolean isPrimeNumber_v1(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) {
            // i = 6k - 1, i + 2 = 6k + 1
            if (num % i == 0 || num % (i + 2) == 0) {
                return false;
            }
        }
        return true;
    }
}

性能