Leetcode-第782题-困难-《变为棋盘》

题目

题目链接
一个 n × n n\times n n×n的二维网络 b o a r d board board仅由 0 0 0 1 1 1组成 。每次移动,你能任意交换两列或是两行的位置

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

  • 示例1:
    Leetcode-第782题-困难-《变为棋盘》_第1张图片

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

  • 示例2:
    Leetcode-第782题-困难-《变为棋盘》_第2张图片

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

  • 示例3:
    Leetcode-第782题-困难-《变为棋盘》_第3张图片

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

思路

Leetcode-第782题-困难-《变为棋盘》_第4张图片

观察上图,不难发现任意交换两列不会影响行之间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的个数,所以每一行的0和1的个数是固定不变的。若能变成一个棋盘,当n是偶数时:0的个数=1的个数=n//2;当n时奇数时:0和1的个数之差=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位;

  • 当数组 A A A元素是0时,左移 i i i位二进制还是全0,和 n u m num num的进行或运算, n u m num num不变,表示 n u m num num i i i位二进制就是0,不用改变。
  • 当数组 A A A元素是1时,左移 i i i位二进制变成第 i i i位是1,其他全是0,和 n u m num num的进行或运算, n u m num num i i i位由0变成1,表示存储 A A A的第 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 01位置,所以行操作与列操作可以看作是两个独立的操作(解耦)。

如果这个矩阵能够变成一个棋盘,受到【性质1】和【性质2】影响,只需要改动某一行和某一列变成0-1交替,其他行和列也会变成0-1交替的状态。

所以二维问题就变成了两个一维的问题。在这里只讨论第一行和第一列,即只需要分别将矩阵的第一行变为最终状态和第一列变为最终状态,最终的矩阵一定为合法“棋盘”。

只需要求出交换第一列和第一行使之称为0-1交替状态的步数之和即可。又因为是交换操作,只会用0交换1,用1交换0;所以只需要计算出需要保留的1的个数,再用1的总数-保留的个数就是需要交换的步数了。0同理。

交换需要分两种情况讨论:

  • 当n为偶数,此时最终的合法棋盘有两种可能,即第一行的元素的第一个元素 board [ 0 ] [ 0 ] = 0 \textit{board}[0][0] = 0 board[0][0]=0或者 board [ 0 ] [ 0 ] = 1 \textit{board}[0][0] = 1 board[0][0]=1
    若选择第一行以0开头,此时只需将偶数位上的 0 0 0全部替换为 1 1 1 即可;同理选择将第 1 1 1行变为以 1 1 1开头,此时只需将奇数位上的 0 0 0全部替换为 1 1 1即可。
  • 若n为奇数,则此时最终的合法棋盘只有一种可能。
    如果第一行中0的数目大于1的数目,此时第一行只能变为以0为开头交替的序列,此时我们只需要将偶数位上的0全部变为1;
    如果第一行中0的数目小于1的数目,此时第一行只能交换变为以1为开头交替的序列,此时我们只需要将奇数位上的0全部变为1;

由于我们采用 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

知识点总结

  • 当矩阵只有0或者1,可以使用int变量和位移操作压缩存储。
  • 判断一个整数是不是偶数:n&1==0则是偶数,反之为奇数
  • 存在一个整数 n u m 1 num1 num1,二进制表示为: 0 ⋯ ⋯ ⏟ 全0 1 ⋯ ⏟ n 个非0的二进制数 \underbrace{0\cdots \cdots}_{\text{全0}}\underbrace{1\cdots }_{n\text{个非0的二进制数}} 0 0⋯⋯n个非0的二进制数 1
    获取 n u m 2 num2 num2,二进制表示为 n u m 1 num1 num1的反码:
    (1) n u m 2 = 0 num2=0 num2=0
    (2)将 n u m 2 num2 num2 n n n位变成 1 1 1: ( n u m 2 < < n ) − 1 (num2<(num2<<n)1
    (3)与二进制进行异或^操作: ( n u m 2 = n u m 1     ˆ n u m 2 ) (num2 = num1\ \^\ num2) (num2=num1  ˆnum2)
  • 计算某整数二进制表示中1的个数: n u m . b i t _ c o u n t ( ) num.bit\_count() num.bit_count()
  • 可以使用异或操作+特定的数字+ n u m . b i t _ c o u n t ( ) num.bit\_count() num.bit_count()计算0或1的个数

你可能感兴趣的:(leetcode刷题,leetcode,算法,职场和发展)