# 思路:思路来源于@lee215
# 对于最终要满足有效棋盘的数组,必须满足以下三个条件:
#
# 1. 所有行的形式只有两种: 如果一种是00011111,另一种必然是它的逆序,11100000。
# 因为如果还有其他形式的行,最终必然不能在若干次变换后成为有效棋盘
# 列,也是相同的
# 因此,为了保证这一性质,对于每个子棋盘,必须满足四个角点是四个0或者四个1或者两个0和两个1
# 因为如果是奇数个1,必然出现多种行的形式
#
# 2. 行列1的总数需要满足odd为n//2 or n//2+1 , even为n//2
#
# 3.这两种形式的行数和列数,
# 必须能保证最终形成01010或者10101,判断形式与2类似,在此不再赘述
通过观察,我们发现一个合法的棋盘必须具备如下两个条件:
1)只能有且仅有两种行类型,例如如果一种行类型为01010011,那么另外一种行类型只能为01010011或者10101100;该限制条件同样适合于列类型。此外,任何棋盘内部的四边形,要么四个角都是1,要么四个角都是0,要么两个0和两个1。
2)每一行和每一列中,0和1的数量都是相等的,假设棋盘是N * N大小的,那么:a)如果N = 2 * K,那么每一行每一列有且仅有K个0和K个1;b)如果N = 2 * K + 1,那么每一行每一列要么有K个1和K+1个0,要么有K+1个1和K个0。
由于每次的行交换和列交换都不破坏上述性质,所以以上两个条件就成了判断是否可以形成合法棋盘的充要条件。一旦我们判断出来某个棋盘是合法的,我们就开始计算最小的交换次数。基于上述性质,我们对第一行进行整理(通过交换列来实现),以N = 5为例来说明:
1)如果N是偶数,我们计算实现01010和10101需要的最小移动步数,并且取最小者;
2)如果N是奇数,我们就只有一种取法,所以就取移动步数为偶数的最小次数。
现在考虑换行的性质,实际上不管你换哪两行或者哪两列,最小步数只要看首列和首行即可。举个例子:
N = 4
0 ? ? ?
1 ? ? ?
1 ? ? ?
0 ? ? ?
看第一列,我们怎么交换呢?可以交换最后两行得到:
0 ? ? ?
1 ? ? ?
0 ? ? ?
1 ? ? ?
交换一次即可。注意:因为1和1之间必须要被相互隔开,所以后续的?我们不用去关注。因为如果:
0 ? ? ?
1 ? 1 ?
0 ? ? ?
1 ? 0 ?
出现第三列相异的情况时,不管我们后续交换哪两行,都无法满足01交叉的性质。(反证法)
比如第三列第四行出现了0,但根据01交叉性质来说,0只能在偶数行,可一旦把0换到偶数行,第一列的01交叉性质又被破坏了,显然矛盾。
换个角度来考虑,当首行和首列都满足01交叉性质时:
0 1 0 1
1 ? ? ?
0 ? ? ?
1 ? ? ?
这些“?”就已经被唯一确定了,所以只要计算交换首行和首列的次数即可。如果“?”中没有满足01交叉性质,在check时就能被发现了。
class Solution {
public:
//求最小移动次数
int movesToChessboard(vector<vector<int>>& board)
{
int n = board.size();
//任意一个矩形的四个顶点只有三种情况,要么四个0,要么四个1,要么两个0两个1,不会有其他的情况
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;
}
}
}
int rowSum = 0, colSum = 0, rowDiff = 0, colDiff = 0;
//10101以这种形式为最终合法棋盘
//每行的1的个数要么为 n/2,要么为 (n+1)/2
for(int i = 0; i < n; ++i)
{
rowSum += board[0][i];
colSum += board[i][0];
rowDiff += (board[0][i] == i % 2);
colDiff += (board[i][0] == i % 2);
}
if(rowSum < n/2 || (n+1)/2 < rowSum)
{
return -1;
}
if(colSum < n/2 || (n+1)/2 < colSum)
{
return -1;
}
//10101
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);
}
//每交换1次会修改2个错位
return (rowDiff + colDiff) / 2;
}
};