[LeetCode] Transform to Chessboard 轉為棋盤


 

An N x N board contains only 0s and 1s. In each move, you can swap any 2 rows with each other, or any 2 columns with each other.

What is the minimum number of moves to transform the board into a "chessboard" - a board where no 0s and no 1s are 4-directionally adjacent? If the task is impossible, return -1.

Examples:
Input: board = [[0,1,1,0],[0,1,1,0],[1,0,0,1],[1,0,0,1]]
Output: 2
Explanation:
One potential sequence of moves is shown below, from left to right:

0110     1010     1010
0110 --> 1010 --> 0101
1001     0101     1010
1001     0101     0101

The first move swaps the first and second column.
The second move swaps the second and third row.


Input: board = [[0, 1], [1, 0]]
Output: 0
Explanation:
Also note that the board with 0 in the top left corner,
01
10

is also a valid chessboard.

Input: board = [[1, 0], [1, 0]]
Output: -1
Explanation:
No matter what sequence of moves you make, you cannot end with a valid chessboard.

Note:

  • board will have the same number of rows and columns, a number in the range [2, 30].
  • board[i][j] will be only 0s or 1s.

 

這道題給了我們一個二維數組,里面都是由0和1組成的,讓我們通過交換行或者列來形成一個棋盤。棋盤我們都見過吧,就是國際象棋的那種棋盤,黑白相間的那種,用數組表示就是0和1交替出現,相鄰位置上的數字必定不是一樣的。這道題默認的棋盤的起始位置可以是1或者0,然后依次類推可得到所有位置上的值。這道題最大的難點是在於判斷給定的數組最終能否組成棋盤,因為能通過交換組成棋盤的數組其實是有很多苛刻條件需要滿足的,只有這些條件都滿足了,才能到計算交換數到那一步。首先我們先來看長度為4的棋盤:

1 0 1 0

0 1 0 1

1 0 1 0

0 1 0 1

或者:

0 1 0 1

1 0 1 0

0 1 0 1

1 0 1 0

我們發現對於長度為偶數的棋盤,每一行0和1的個數都是相等的,不管我們如何交換行和列,0和1的個數都是不會變化的,再看看長度為奇數的棋盤,比如3:

1 0 1

0 1 0

1 0 1

或者:

0 1 0

1 0 1

0 1 0

我們發現對於長度為奇數的棋盤,各行的0和1個數不同,但是還是有規律的,每行的1的個數要么為 n/2,要么為 (n+1)/2,這個規律一定要保證,不然無法形成棋盤。

還有一個很重要的規律,我們觀察題目給的第一個例子,如果我們只看行,我們發現只有兩種情況 0110 和 1001,如果只看列,只有 0011 和 1100,我們發現不管棋盤有多長,都只有兩種情況,而這兩種情況上各位上是相反的,只有這樣的矩陣才有可能轉換為棋盤。那么這個規律可以衍生出一個規律,就是任意一個矩形的四個頂點只有三種情況,要么四個0,要么四個1,要么兩個0兩個1,不會有其他的情況。那么四個頂點亦或在一起一定是0,所以我們判斷只要亦或出了1,一定是不對的,直接返回-1。之后我們來統計首行和首列中的1個數,因為我們要讓其滿足之前提到的規律。統計完了首行首列1的個數,我們判斷如果其小於 n/2 或者大於 (n+1) / 2,那么一定無法轉為棋盤。我們還需要算下首行和首列跟棋盤位置的錯位的個數,雖然 01010 和 10101 都可以是正確的棋盤,我們先默認跟 10101 比較好了,之后再做優化處理。

最后的難點就是計算最小的交換步數了,這里要分n的奇偶來討論。如果n是奇數,我們必須得到偶數個,為啥呢,因為我們之前統計的是跟棋盤位置的錯位的個數,而每次交換行或者列,會修改兩個錯位,所以如果是奇數就無法還原為棋盤。舉個例子,比如首行是 10001,如果我們跟棋盤 10101 比較,只有一個錯位,但是我們是無法通過交換得到 10101的,所以我們必須要交換得到 01010,此時的錯位是4個,而我們通過 n - rowDiff 正好也能得到4,這就是為啥我們需要偶數個錯位。如果n是偶數,那么就不會出現這種問題,但是會出現另一個問題,比如我們是 0101,這本身就是正確的棋盤排列了,但是由於我們默認是跟 1010 比較,那么我們會得到4個錯位,所以我們應該跟 n - rowDiff 比較取較小值。列的處理跟行的處理完全一樣。最終我們把行錯位個數跟列錯位個數相加,再除以2,就可以得到最小的交換次數了,之前說過了每交換一次,可以修復兩個錯位,參見代碼如下:

 

class Solution {
public:
    int movesToChessboard(vector<vector<int>>& board) {
        int n = board.size(), rowSum = 0, colSum = 0, rowDiff = 0, colDiff = 0;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (board[0][0] ^ board[i][0] ^ board[0][j] ^ board[i][j]) return -1;
            }
        }
        for (int i = 0; i < n; ++i) {
            rowSum += board[0][i];
            colSum += board[i][0];
            rowDiff += (board[i][0] == i % 2);
            colDiff += (board[0][i] == i % 2);
        }
        if (n / 2 > rowSum || rowSum > (n + 1) / 2) return -1;
        if (n / 2 > colSum || colSum > (n + 1) / 2) return -1;
        if (n % 2) {
            if (rowDiff % 2) rowDiff = n - rowDiff;
            if (colDiff % 2) colDiff = n - colDiff;
        } else {
            rowDiff = min(n - rowDiff, rowDiff);
            colDiff = min(n - colDiff, colDiff);
        }
        return (rowDiff + colDiff) / 2;
    }
};

 

參考資料:

https://leetcode.com/problems/transform-to-chessboard/

https://leetcode.com/problems/transform-to-chessboard/discuss/132113/Java-Clear-Code-with-Detailed-Explanations

https://leetcode.com/problems/transform-to-chessboard/discuss/114847/Easy-and-Concise-Solution-with-Explanation-C%2B%2BJavaPython

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM