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

题目

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

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

  • 示例1:

    在这里插入图片描述

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

  • 示例2:

    在这里插入图片描述

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

  • 示例3:

    在这里插入图片描述

输入: 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的个数,所以每一行的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<<n-1  其实就是……0000+n个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<<n)-1 (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的个数

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340