782. 变为棋盘

# 思路:思路来源于@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;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值