题目链接
一个 n × n n\times n n×n的二维网络 b o a r d board board仅由 0 0 0和 1 1 1组成 。每次移动,你能任意交换两列或是两行的位置。
返回 将这个矩阵变为 “棋盘” 所需的最小移动次数 。如果不存在可行的变换,输出 -1。
输入: board = [[0,1,1,0],[0,1,1,0],[1,0,0,1],[1,0,0,1]]
输出: 2
解释: 一种可行的变换方式如下,从左到右:
第一次移动交换了第一列和第二列。
第二次移动交换了第二行和第三行。
输入: board = [[0, 1], [1, 0]]
输出: 0
解释: 注意左上角的格值为0时也是合法的棋盘,也是合法的棋盘.
输入: board = [[1, 0], [1, 0]]
输出: -1
解释: 任意的变换都不能使这个输入变为合法的棋盘。
观察上图,不难发现任意交换两列不会影响行之间0和1的异同关系【性质1】。
所以,想要凑成棋盘,矩阵一定只能包含有两种不同的行【性质2】,要么与第一行的元素相同,要么每一行的元素刚好与第一行的元素“相反”。
如第一排元素是 [ 0 , 1 , 1 , 0 ] [0,1,1,0] [0,1,1,0],其他行只能是 [ 0 , 1 , 1 , 0 ] [0,1,1,0] [0,1,1,0]或者 [ 1 , 0 , 0 , 1 ] [1,0,0,1] [1,0,0,1]。
这个矩阵只由0和1组成,且长度在30以内。可以使用一个32位的int数字表示。
如何表示?
假设第一行是 A = [ 1 , 0 , 1 , 0 ] A=[1,0,1,0] A=[1,0,1,0],使用 i n t int int类型的 n u m num num来表示:
num = 0
for i in len(A):
num |= A[i]<<i
代码解释:
num=0,则num的二进制表示: ⋯ 0000000 \cdots0000000 ⋯0000000
然后将 A A A数组的第 i i i个元素左移i位;
最后 n u m num num二进制表示为: ⋯ 0001010 \cdots0001010 ⋯0001010
首先编成判断矩阵能否变成棋盘的代码:
n = len(board)#获取棋盘维度n
rowMask = colMask = 0#定义用于表示第一行和第一列的int类型变量
for i in range(n):
#将矩阵存储到int变量中
rowMask |= board[0][i] << i
colMask |= board[i][0] << i
#创建与第一行和第一列相反的变量
#1<
#^表示异或,同0异1
reverseRowMask = ((1 << n) - 1) ^ rowMask
reverseColMask = ((1 << n) - 1) ^ colMask
#用于记录有多少个和第一行/第一列相同的行/列
rowCnt = colCnt = 0
for i in range(n):
#矩阵第i行/列的int表示
currRowMask = currColMask = 0
for j in range(n):
currColMask |= board[j][i] << j
currRowMask |= board[i][j] << j
#(和第一行不一样 并且 和第一行不是相反关系) 并且 (和第一列不一样 并且 和第一列不是相反关系)
if (currRowMask != rowMask and currRowMask != reverseRowMask) and (currColMask != colMask and currColMask != reverseColMask):
return -1
#和第一行/列一样就+1,这个数后面会用于判断能否变成棋盘
rowCnt += currRowMask == rowMask
colCnt += currColMask == colMask
接下来判断变成棋盘需要走几步
交换两列并不会影响行之间的 0 − 1 0-1 0−1位置,所以行操作与列操作可以看作是两个独立的操作(解耦)。
如果这个矩阵能够变成一个棋盘,受到【性质1】和【性质2】影响,只需要改动某一行和某一列变成0-1交替,其他行和列也会变成0-1交替的状态。
所以二维问题就变成了两个一维的问题。在这里只讨论第一行和第一列,即只需要分别将矩阵的第一行变为最终状态和第一列变为最终状态,最终的矩阵一定为合法“棋盘”。
只需要求出交换第一列和第一行使之称为0-1交替状态的步数之和即可。又因为是交换操作,只会用0交换1,用1交换0;所以只需要计算出需要保留的1的个数,再用1的总数-保留的个数就是需要交换的步数了。0同理。
交换需要分两种情况讨论:
由于我们采用 32 32 32位整数表示每一行或者每一列,在快速计算偶数位或者上的 1 1 1的数目时可以采用位运算掩码。比如 32 32 32位整数 x x x,我们只保留 x x x偶数位上的1,此时我们需要去掉奇数位上的1,此时只需将x与掩码:
( 10101010101010101010101010101010 ) 2 = 0 x A A A A A A A A (1010 1010 1010 1010 1010 1010 1010 1010)_2=0xAAAAAAAA (10101010101010101010101010101010)2=0xAAAAAAAA
相与即可;
我们只保留 x x x奇数位上的 1 1 1,此时我们需要去掉偶数位上的 1 1 1,此时只需将 x x x与掩码:
( 01010101010101010101010101010101 ) 2 = 0 x 55555555 (0101 0101 0101 0101 0101 0101 0101 0101)_2=0x55555555 (01010101010101010101010101010101)2=0x55555555
相与即可。
相与后使用 b i t c o u n t ( ) bit_count() bitcount()计算1的个数就是需要保留的个数了,使用总数-保留数即可得出移动步数。
代码如下:
def getMoves(mask, count):
'''
mask:第一行/列的int变量
count:有几行/列和第一行/列一样
'''
ones = mask.bit_count()#1的个数
if n & 1: # 若长度是奇数
if abs(n - 2 * ones) != 1 or abs(n - 2 * count) != 1:#0和1的数量相差1,不满足则不能变成棋盘
return -1
if ones == n // 2: # 若1的个数是偶数,ones == n//2为1的个数
return n // 2 - (mask & 0xAAAAAAAA).bit_count()#1的个数-需要保留的个数=移动步数
else:#1的个数是奇数,(n+1)//2为1的个数
return (n+1)//2 - (mask & 0x55555555).bit_count()
else:#若长度是偶数
if ones !=n//2 or count != n//2:
return -1
#交换0或者交换0,选最小的
count0 = n // 2 - (mask & 0xAAAAAAAA).bit_count()
count1 = n // 2 - (mask & 0x55555555).bit_count()
return min(count0, count1)
class Solution(object):
def movesToChessboard(self, board):
"""
:type board: List[List[int]]
:rtype: int
"""
n = len(board)
rowMask = colMask = 0
for i in range(n):
rowMask |= board[0][i] << i
colMask |= board[i][0] << i
reverseRowMask = ((1 << n) - 1) ^ rowMask
reverseColMask = ((1 << n) - 1) ^ colMask
rowCnt = colCnt = 0
for i in range(n):
currRowMask = currColMask = 0
for j in range(n):
currColMask |= board[j][i] << j
currRowMask |= board[i][j] << j
if (currRowMask != rowMask and currRowMask != reverseRowMask) and (currColMask != colMask and currColMask != reverseColMask):
return -1
rowCnt += currRowMask == rowMask
colCnt += currColMask == colMask
def getMoves(mask, count):
ones = mask.bit_count()
if n & 1: # 长度是奇数
if abs(n - 2 * ones) != 1 or abs(n - 2 * count) != 1:
return -1
if ones == n // 2:
# 1是偶数,ones == n//2为1的个数
return n // 2 - (mask & 0xAAAAAAAA).bit_count()
else:
return (n+1)//2 - (mask & 0x55555555).bit_count()
else:
if ones !=n//2 or count != n//2:
return -1
count0 = n // 2 - (mask & 0xAAAAAAAA).bit_count()
count1 = n // 2 - (mask & 0x55555555).bit_count()
return min(count0, count1)
rowMoves = getMoves(rowMask, rowCnt)
colMoves = getMoves(colMask, colCnt)
return -1 if rowMoves == -1 or colMoves == -1 else rowMoves + colMoves