999.可以被一步捕获的棋子数

目标

给定一个 8 x 8 的棋盘,只有一个 白色的车,用字符 'R' 表示。棋盘上还可能存在白色的象 'B' 以及黑色的卒 'p'。空方块用字符 '.' 表示。

车可以按水平或竖直方向(上,下,左,右)移动任意个方格直到它遇到另一个棋子或棋盘的边界。如果它能够在一次移动中移动到棋子的方格,则能够 吃掉 棋子。

注意:车不能穿过其它棋子,比如象和卒。这意味着如果有其它棋子挡住了路径,车就不能够吃掉棋子。

返回白车将能 吃掉 的 卒的数量。

示例 1:

输入:
[
    [".",".",".",".",".",".",".","."],
    [".",".",".","p",".",".",".","."],
    [".",".",".","R",".",".",".","p"],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."],
    [".",".",".","p",".",".",".","."],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."]
]
输出:3
解释:
在本例中,车能够吃掉所有的卒。

示例 2:

输入:
[
    [".",".",".",".",".",".",".","."],
    [".","p","p","p","p","p",".","."],
    [".","p","p","B","p","p",".","."],
    [".","p","B","R","B","p",".","."],
    [".","p","p","B","p","p",".","."],
    [".","p","p","p","p","p",".","."],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."]
]
输出:0
解释:
象阻止了车吃掉任何卒。

示例 3:

输入:
[
    [".",".",".",".",".",".",".","."]
    [".",".",".","p",".",".",".","."],
    [".",".",".","p",".",".",".","."],
    ["p","p",".","R",".","p","B","."],
    [".",".",".",".",".",".",".","."],
    [".",".",".","B",".",".",".","."],
    [".",".",".","p",".",".",".","."],
    [".",".",".",".",".",".",".","."]
]
输出:3
解释: 
车可以吃掉位置 b5,d6 和 f5 的卒。

说明:

  • board.length == 8
  • board[i].length == 8
  • board[i][j] 可以是 'R','.','B' 或 'p'
  • 只有一个格子上存在 board[i][j] == 'R'

思路

8 x 8 的棋盘上有一个白车 R,若干个白象 B 和黑卒 'p',空格由 '.' 表示。问白车能够吃掉黑卒的数量,注意车只能吃掉同行同列四个方向上第一个遇到的黑卒。

首先需要找到白车的位置,按行遍历棋盘,记录当前行上一个棋子,遇到白车时判断上一个棋子是否是黑卒,如果是计入结果,然后判断后面遇到的第一个棋子,如果是黑卒计入结果。当前行遍历完成后结束循环,按照同样方法遍历白车所在列即可。

代码


/**
 * @date 2024-12-06 9:05
 */
public class NumRookCaptures999 {

    public int numRookCaptures(char[][] board) {
        int res = 0;
        int rookRow = -1;
        int rookCol = -1;
        here:
        for (int i = 0; i < 8; i++) {
            // 首先按行遍历,找到白色车的位置(rookRow, rookCol),同时判断它前后的棋子是否是黑卒
            char[] row = board[i];
            char last = '.';
            for (int j = 0; j < 8; j++) {
                if (row[j] == 'R') {
                    // 如果找到白车,判断当前行前面的棋子是否是黑卒
                    if (last == 'p') {
                        res++;
                    }
                    // 记录坐标
                    rookCol = j;
                    rookRow = i;
                } else if (rookCol != -1 && row[j] != '.') {
                    // 判断后面第一个棋子是否是黑卒
                    if (row[j] == 'p') {
                        res++;
                    }
                    break here;
                }
                if (row[j] != '.') {
                    last = row[j];
                }
            }
            if (rookCol != -1) {
                break;
            }
        }

        for (int i = rookRow - 1; i >= 0; i--) {
            if (board[i][rookCol] != '.') {
                // 找到上面第一个棋子
                if (board[i][rookCol] == 'p') {
                    res++;
                }
                break;
            }
        }
        for (int i = rookRow + 1; i < 8; i++) {
            if (board[i][rookCol] != '.') {
                // 找到下面第一个棋子
                if (board[i][rookCol] == 'p') {
                    res++;
                }
                break;
            }
        }
        return res;
    }

}

性能

3001.捕获黑皇后需要的最少移动次数

目标

现有一个下标从 1 开始的 8 x 8 棋盘,上面有 3 枚棋子。

给你 6 个整数 a 、b 、c 、d 、e 和 f ,其中:

  • (a, b) 表示白色车的位置。
  • (c, d) 表示白色象的位置。
  • (e, f) 表示黑皇后的位置。

假定你只能移动白色棋子,返回捕获黑皇后所需的最少移动次数。

请注意:

  • 车可以向垂直或水平方向移动任意数量的格子,但不能跳过其他棋子。
  • 象可以沿对角线方向移动任意数量的格子,但不能跳过其他棋子。
  • 如果车或象能移向皇后所在的格子,则认为它们可以捕获皇后。
  • 皇后不能移动。

示例 1:

输入:a = 1, b = 1, c = 8, d = 8, e = 2, f = 3
输出:2
解释:将白色车先移动到 (1, 3) ,然后移动到 (2, 3) 来捕获黑皇后,共需移动 2 次。
由于起始时没有任何棋子正在攻击黑皇后,要想捕获黑皇后,移动次数不可能少于 2 次。

示例 2:

输入:a = 5, b = 3, c = 3, d = 4, e = 5, f = 2
输出:1
解释:可以通过以下任一方式移动 1 次捕获黑皇后:
- 将白色车移动到 (5, 2) 。
- 将白色象移动到 (5, 2) 。

说明:

  • 1 <= a, b, c, d, e, f <= 8
  • 两枚棋子不会同时出现在同一个格子上。

思路

有一个 8 x 8 的棋盘,上面有车、象、皇后三个棋子。其中车和象是白色,皇后是黑色。车走横线与竖线,象走斜线,皇后固定不动。问白棋捕获黑皇后所需的最小移动次数,所谓捕获指任意白棋走到黑皇后所在的位置。注意,沿着某一方向移动多个格子算一步。

当我们移动一个白棋的时候另一个白棋是不动的,所以就可能存在一个白棋被另一个白棋挡住的情况。如果不考虑碰撞,白棋捕获黑皇后最多走两步,并且有两种走法,对于车来说可以先走到同一行或者同一列。这时如果另一个白棋出现在其中一条路径上,我们只需选另一条路径即可,最多走两步。因此白棋阻挡只会对初始时可以一步捕获的情况产生影响,这时我们只需要将阻挡的棋子移开,然后另一个棋子可以一步直达,还是两步。

根据上面的分析,如果某个白棋可以直接捕获黑皇后(路径上没有阻挡),那么只需要移动一次,否则需要移动两次。

题解中给出了另一种写法来判断是否产生阻挡的情况,如果 (m − l) * (m − r) > 0,那么整数 m 不在整数 l 和 r 之间。如果在它们之间,符号一定相异,l 与 r 换位置也不影响,这样无需比较二者的最大最小值。

代码


/**
 * @date 2024-12-05 10:06
 */
public class MinMovesToCaptureTheQueen3001 {

        public int minMovesToCaptureTheQueen(int a, int b, int c, int d, int e, int f) {
        // 车与皇后在同一行,象没有在同一行,或者在同一行却不在二者之间
        if (a == e && (a != c || d < Math.min(b, f) || d > Math.max(b, f))) {
            return 1;
        }
        // 车与皇后在同一列,象没有在同一列,或者在同一列却不在二者之间
        if (b == f && (b != d || c < Math.min(a, e) || c > Math.max(a, e))) {
            return 1;
        }
        // 象与皇后在同一斜线,车没有在同一斜线,或者在同一斜线却不在二者之间
        if (c + d == e + f && (a + b != c + d || a < Math.min(c, e) || a > Math.max(c, e))) {
            return 1;
        }
        if (c - d == e - f && (a - b != c - d || a < Math.min(c, e) || a > Math.max(c, e))) {
            return 1;
        }
        return 2;
    }

}

性能

3240.最少翻转次数使二进制矩阵回文II

目标

给你一个 m x n 的二进制矩阵 grid 。

如果矩阵中一行或者一列从前往后与从后往前读是一样的,那么我们称这一行或者这一列是 回文 的。

你可以将 grid 中任意格子的值 翻转 ,也就是将格子里的值从 0 变成 1 ,或者从 1 变成 0 。

请你返回 最少 翻转次数,使得矩阵中 所有 行和列都是 回文的 ,且矩阵中 1 的数目可以被 4 整除 。

示例 1:

输入:grid = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

示例 2:

输入:grid = [[0,1],[0,1],[0,0]]
输出:2

示例 3:

输入:grid = [[1],[1]]
输出:2

说明:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m * n <= 2 * 10^5
  • 0 <= grid[i][j] <= 1

思路

有一个二进制矩阵 grid,每次操作可以翻转任意格子,使 0 变为 1,或者使 1 变为 0。求使得矩阵 所有行 所有列 变成回文,且矩阵中 1 的数目可以被 4 整除 的最少操作次数。

考虑矩阵操作后的最终状态,类似一个 或者 或者 旋转 90 度的 字,每一个 内的元素完全相同,中间线上如果有元素的话也是对称的。

昨天的题 最少翻转次数使二进制矩阵回文I 要求的是 所有行 或者 所有列,当我们保证行是回文的时候,如果遇到不同的元素,翻转哪一个都可以。对于本题,需要同时保证列也是回文的,并且矩阵中 1 的个数能够被 4 整除。在要求操作次数最小的情况下,选择翻转哪一个是有影响的。需要考虑翻转元素后的代价,如果我们在保证 是回文的时候翻转了某个元素导致了该元素所在 的对称位置上的元素不同,那么翻转次数需要多加1,而如果翻转镜像位置的元素刚好可以使所在列对称位置上的元素相同,应该优先选择。实际上矩阵中 1 的数目可以被 4 整除,只需考虑奇数行与奇数列时的中间行与中间列,因为在其它区域出现 1,如果行列是回文的,一定可以被 4 整除。

我们再重新梳理一下,翻转的时候优先选择代价最小的,只需要考虑 四个对称位置上的值哪个占少数就翻转哪个。分别统计中间行与列中 1 的个数,以及变成回文的操作次数。由于中间行、列的翻转是任意的,我们可以计算中间行列中 1 的个数之和 midOneCnt,注意这里需要去除中心元素(即行或列为奇数时的中间元素)后面单独处理。根据 mod = midOneCnt % 4 的值,我们可以确定操作翻转哪个元素:

  • 如果值为 1,将 10
  • 如果值为 2,都行
  • 如果值为 3,将 01

需要的操作次数为 Math.min(mod, 4 - mod)。如果保证回文的操作的次数不够使 1 的个数满足条件,就需要额外的操作。注意,这么做的前提条件是 有足够的格子,如果中线的格子总数不够 4 个,比如 3 个格子,里面都是 1 需要补一个 1 是无法操作的,这时操作次数只能取 mod 了。

最后考虑中心元素,它必须为0,否则 1 的总个数不可能被 4 整除。

代码


/**
 * @date 2024-11-15 11:28
 */
public class MinFlips3240 {

    /**
     * 执行通过
     */
    public int minFlips(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int res = 0;
        int rowMid = m / 2;
        int colMid = n / 2;
        for (int i = 0; i < rowMid; i++) {
            for (int j = 0; j < colMid; j++) {
                int jj = n - j - 1;
                int ii = m - i - 1;
                int sum = grid[i][j] + grid[i][jj] + grid[ii][j] + grid[ii][jj];
                // 取四个元素中的少数元素个数
                res += Math.min(sum, 4 - sum);
            }
        }
        int midRowOneCnt = 0;
        int midRowOptCnt = 0;
        // 计算奇数行时中间行的操作次数与1的个数
        if (m % 2 == 1) {
            for (int j = 0; j < colMid; j++) {
                int jj = n - j - 1;
                midRowOneCnt += grid[rowMid][j] + grid[rowMid][jj];
                midRowOptCnt += grid[rowMid][j] ^ grid[rowMid][jj];
            }
        }
        int midColOneCnt = 0;
        int midColOptCnt = 0;
        // 计算奇数列时中间列的操作次数与1的个数
        if (n % 2 == 1) {
            for (int i = 0; i < rowMid; i++) {
                int ii = m - i - 1;
                midColOneCnt += grid[i][colMid] + grid[ii][colMid];
                midColOptCnt += grid[i][colMid] ^ grid[ii][colMid];
            }
        }
        int midOpt = midRowOptCnt + midColOptCnt;
        res += midOpt;
        int midOneCnt = midRowOneCnt + midColOneCnt;
        int mod = midOneCnt % 4;
        int remainder;
        // 确保格子数量足够
        if (m + n <= 4) {
            remainder = midOpt - mod;
        } else {
            remainder = midOpt - Math.min(mod, 4 - mod);
        }
        // 如果操作次数不够需要补上
        if (remainder < 0) {
            res += -remainder;
        }
        // 处理中心元素
        if (m % 2 == 1 && n % 2 == 1) {
            res += grid[rowMid][colMid];
        }

        return res;
    }

}

性能

2708.一个小组的最大实力值

目标

给你一个下标从 0 开始的整数数组 nums ,它表示一个班级中所有学生在一次考试中的成绩。老师想选出一部分同学组成一个 非空 小组,且这个小组的 实力值 最大,如果这个小组里的学生下标为 i0, i1, i2, ... , ik ,那么这个小组的实力值定义为 nums[i0] * nums[i1] * nums[i2] * ... * nums[ik​]

请你返回老师创建的小组能得到的最大实力值为多少。

示例 1:

输入:nums = [3,-1,-5,2,5,-9]
输出:1350
解释:一种构成最大实力值小组的方案是选择下标为 [0,2,3,4,5] 的学生。实力值为 3 * (-5) * 2 * 5 * (-9) = 1350 ,这是可以得到的最大实力值。

示例 2:

输入:nums = [-4,-5,-4]
输出:20
解释:选择下标为 [0, 1] 的学生。得到的实力值为 20 。我们没法得到更大的实力值。

说明:

  • 1 <= nums.length <= 13
  • -9 <= nums[i] <= 9

思路

有一个存在负数的数组,求它的非空子序列使得子序列的乘积最大。

首先,要选择所有正数,不能选零(除非全是零),负数必须成对的选(可以使用栈来保存)。可以将数组从小到大排序,从后向前取所有大于零的值,然后再从前向后取,判断栈是否为空,为空入栈,非空出栈,直到零。特别需要注意的是初值的设置需要分情况讨论,如果全是零或者至多一个负数,直接返回零。如果至少有一个正数,则初值设为1。如果全是负数,初值设为第一个元素,之所以不设为1是因为如果只有一个负数,那么就应该取这个值,如果设为1后面再配对的话就不对了,所以需要特殊处理成对匹配的情况。

代码


/**
 * @date 2024-09-03 8:57
 */
public class MaxStrength2708 {

    public long maxStrength(int[] nums) {
        long res = 1L;
        Arrays.sort(nums);
        int n = nums.length;
        int i = n - 1;
        for (; i >= 0; i--) {
            if (nums[i] > 0) {
                res *= nums[i];
            } else if (nums[i] < 0) {
                // 跳过0,使i指向最后一个负数
                break;
            }
        }
        int j = 0;
        boolean flag = true;
        // 至多一个负数,其余全是0,返回0。
        if (i <= 0 && nums[n - 1] == 0) {
            return 0L;
        } else if (i == n - 1) {
            // 如果全是负数
            res = nums[0];
            flag = false;
            j = 1;
        }
        ArrayDeque<Integer> stack = new ArrayDeque<>();
        for (; j <= i; j++) {
            if (nums[j] < 0) {
                if (stack.isEmpty() && flag) {
                    stack.push(nums[j]);
                } else {
                    if (!flag) {
                        res *= nums[j];
                    } else {
                        res *= stack.pop() * nums[j];
                    }
                    flag = true;
                }
            }
        }
        return res;
    }

}

性能