力扣每日一题0823-782. 变为棋盘

246 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第23天,点击查看活动详情

一个 n x n 的二维网络 board 仅由 0 和 1 组成 。每次移动,你能任意交换两列或是两行的位置。

返回 将这个矩阵变为  “棋盘”  所需的最小移动次数 。如果不存在可行的变换,输出 -1

“棋盘” 是指任意一格的上下左右四个方向的值均与本身不同的矩阵。

示例 1:

chessboard1-grid.jpg

输入: board = [[0,1,1,0],[0,1,1,0],[1,0,0,1],[1,0,0,1]]
输出: 2
解释:一种可行的变换方式如下,从左到右:
第一次移动交换了第一列和第二列。
第二次移动交换了第二行和第三行。

示例 2:

chessboard2-grid.jpg

输入: board = [[0, 1], [1, 0]]
输出: 0
解释: 注意左上角的格值为0时也是合法的棋盘,也是合法的棋盘.

示例 3:

chessboard3-grid.jpg

输入: board = [[1, 0], [1, 0]]
输出: -1
解释:任意的变换都不能使这个输入变为合法的棋盘。

分维度计算

首先需要思考的是对矩阵做一次交换之后,矩阵的变换状态。比如我们以交换列为代表,在对任意两列进行交换之后,可以看到列交换是不会改变任意相邻两行之间的元素异同对应关系,比如相邻两行的两个元素 board[i][j],board[i+1][j]\textit{board}[i][j],\textit{board}[i+1][j] 原本就相同,任意列交换之后这个两个元素对应的关系保持不变,如果这两个元素本来就不同,经过列交换之后也仍然不同,因此可以推出矩阵一定只能包含有两种不同的行,要么与第一行的元素相同,要么每一行的元素刚好与第一行的元素“相反”。如果矩阵可以转换为合法的“棋盘”,假设第 1 行的元素为 [0,1,1,1,0],则其他行的元素要么为[0,1,1,1,0],要么为 [1,0,0,0,1]。最终的棋盘一定只有两种不同的行,要么以 0 开始的 [0,1,0,1,][0,1,0,1,\cdots],要么以 1 开始的 [1,0,1,0,][1,0,1,0,\cdots],因此我们可以推出棋盘也一定可以通过列变换将所有的行变为只有以上两种状态的行,否则无法得到最终合法的“棋盘”。同时我们可以观察到,先换行再换列跟先换列再换行结果是一样的,因为我们可以先将所有的行调整到正确的位置,再将所有的列调整到正确的位置。行与列之间的变换实际是相互独立的,二者互不影响,列变换不会影响相邻两行的异同关系,行变换不会不会影响相邻两列的异同关系。

由于最终只有两种不同的行,要达成最终的“棋盘”实际上等价于将矩阵的行表示成 0,1 相互交替的状态,如果一个行无法变为 0,1 交替的状态,则我们认为矩阵不存在可行的变换。假设矩阵的某行用 [0,1][0,1][0,1] 表示之后得到数组为 [0,1,1,1,0,0],那么只需求出这个数组变成 [0,1,0,1,0,1] 或者 [1,0,1,0,1,0] 的最少交换次数即可。同理,对于矩阵的列也是如此,这就将二维问题变成了两个一维问题。我们实际只需要分别将矩阵的第一行变为最终状态和第一列变为最终状态,最终的矩阵一定为合法“棋盘”。

var movesToChessboard = function(board) {
    const n = board.length;
    let rowMask = 0, colMask = 0;        

    /* 检查棋盘的第一行与第一列 */
    for (let i = 0; i < n; i++) {
        rowMask |= (board[0][i] << i);
        colMask |= (board[i][0] << i);
    }
    const reverseRowMask = ((1 << n) - 1) ^ rowMask;
    const reverseColMask = ((1 << n) - 1) ^ colMask;
    let rowCnt = 0, colCnt = 0;
    for (let i = 0; i < n; i++) {
        let currRowMask = 0;
        let currColMask = 0;
        for (let j = 0; j < n; j++) {
            currRowMask |= (board[i][j] << j);
            currColMask |= (board[j][i] << j);
        }
        /* 检测每一行的状态是否合法 */
        if (currRowMask !== rowMask && currRowMask !== reverseRowMask) {
            return -1;
        } else if (currRowMask === rowMask) {
            /* 记录与第一行相同的行数 */
            rowCnt++;
        }
        /* 检测每一列的状态是否合法 */
        if (currColMask !== colMask && currColMask !== reverseColMask) {
            return -1;
        } else if (currColMask === colMask) {
            /* 记录与第一列相同的列数 */
            colCnt++;
        }
    }
    const rowMoves = getMoves(rowMask, rowCnt, n);
    const colMoves = getMoves(colMask, colCnt, n);
    return (rowMoves == -1 || colMoves == -1) ? -1 : (rowMoves + colMoves); 
};

const getMoves = (mask, count, n) => {
    const ones = bitCount(mask);
    if ((n & 1) === 1) {
        /* 如果 n 为奇数,则每一行中 1 与 0 的数目相差为 1,且满足相邻行交替 */
        if (Math.abs(n - 2 * ones) !== 1 || Math.abs(n - 2 * count) !== 1 ) {
            return -1;
        }
        if (ones === (n >> 1)) {
            /* 以 0 为开头的最小交换次数 */
            return Math.floor(n / 2) - bitCount(mask & 0xAAAAAAAA);
        } else {
            return Math.floor((n + 1) / 2) - bitCount(mask & 0x55555555);
        }
    } else { 
        /* 如果 n 为偶数,则每一行中 1 与 0 的数目相等,且满足相邻行交替 */
        if (ones !== (n >> 1) || count !== (n >> 1)) {
            return -1;
        }
        /* 找到行的最小交换次数 */
        const count0 = Math.floor(n / 2) - bitCount(mask & 0xAAAAAAAA);
        const count1 = Math.floor(n / 2) - bitCount(mask & 0x55555555);  
        return Math.min(count0, count1);
    }
}

const bitCount = (num) => {
    return num.toString(2).split('0').join('').length
}