782 变为棋盘——Leetcode天天刷2022.8.23【数学:降维计算】

782 变为棋盘——Leetcode天天刷(2022.8.23)【数学:降维计算】

前言

打了下今天的每日一题,不得不说,不愧是 Hard,虽然感觉自己经过集训的洗礼,能直接手撕了。But,想了几分钟,确实没什么好的思路,但是,可以知道纯暴力的棋盘搜索是不行的。

在看了 官方题解三叶小姐姐 的文字思路后,才知道这题目可以降维来求解!理解了思路后,即使不看代码,也可以凭借对思路的理解来手撸代码实现了。

题目描述

题目传送门

感兴趣的可以点击跳转打题。

简述一下:就是一个 n ∗ n n * n nn 的二维数组,每个单元格不是 0 0 0 就是 1 1 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
解释: 任意的变换都不能使这个输入变为合法的棋盘。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/transform-to-chessboard
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处

提示信息

在这里插入图片描述

本地调试运行

这个就还好,不想树之类的题目,需要写额外的代码来构造树,我们只需要做好输入格式即可。

输入格式

假定多组输入,每组第一行输入一个整数n,然后接下来n行,每行输入n个整数。

输出格式

根据题目的类函数结构,我们直接输出结果即可。

输入样例

4
0 1 1 0
0 1 1 0
1 0 0 1
1 0 0 1

2
0 1
1 0

2
1 0
1 0

输出样例

2
0
-1

像leetcode这种OJ比较人性化,如果没通过会返回不能通过的测试数据,如果是普遍的一些OJ,我们其实可以在本地写多一个数据生成器,加一个暴力算法,来确保我们代码能够正确。

解法——数学,降维计算,模拟

参考自官方题解和三叶小姐姐的题解文字思路!

我们首先需要明白一个本质问题:行列变化的独立性,以 **行交换 **为例,我们任意交换两行,我们会发现列的种类没有发生变化,只是0,1的分布改变了。从这个特性,我们就可以知道,行,列交换的顺序先后,对结果不会有影响!这也是我们可以做降维计算的基础!

降维计算,其实就是分维度来计算,我们原本是需要考虑通过行列变化来使得整个矩阵变为棋盘,这时我们可以不用考虑整个棋盘,而是考虑其中的一行一列!

首先我们考虑 有解 的情况:如果有解,那么对于第一行,如果n为偶数,那么0和1的数量一定相同;如果是奇数,那么差值为1,这个对每一行,每一列都成立。而对于其他行,和第一行的对称位置,要么就完全相同,要么就是完全相反,且相同的行数,和不同的行数,和0,1同理,差值不会超过1。

所以,我们只需要将第一行变为0101。。。或者1010。。。这样的间隔形式即可。

列同理。

那么这个变为棋盘的问题,就拆分成了两个部分:先判断是否有解,在判断需要变换的次数。

判断是否有解

只需要满足我们前面提到的条件即可,我们再梳理下:

  1. 第一行、列中1,0的数目差的绝对值,设为 sub,有如下公式
    s u b = { 0 , n % 2 = = 0 1 , n % 2 = = 1 sub = \left\{ \begin{aligned} 0,\qquad n \% 2 == 0 \\ 1,\qquad n \% 2 == 1 \end{aligned} \right. sub={0,n%2==01,n%2==1

  2. 其他行,和第一行,要么完全相同,要么完全相反,列同理

  3. 接条件2,相同和相反的行数以及列数,差值的绝对值和条件1的公式相同。

计算移动次数

我们只需判断第一行和第一列即可。

具体如下, 记移动次数为times

因为下标是从0开始,我们可以取偶数位下标的数字,我们分别计算第一行、列的偶数位下标对应的数字加和,记为 r1c1

  1. n为偶数,那么无外乎是把偶数位不是1的数字换成1,或者把数字1换成0,有如下公式
    t i m e s + = min ⁡ ( r 1 , n / 2 − r 1 ) times += \min(r1, n/2-r1) times+=min(r1,n/2r1)
    列同理。

  2. n为奇数,我们则需要根据0,1谁的数量多,决定将多的放偶数位。

思路清晰后,就上代码!

Code(C++)

#include<iostream>
#include<vector>
#include<cmath>
#include<algorithm>

using namespace std;

class Solution
{
public:
	// 分维度计算
	// 分两步走,第一步判断是否有解——棋盘是否正确!第二步计算移动的次数
	// 有解需要判断的条件:1.第一行、列中1,0的数目差值绝对值=1(n为奇数)或者=0(偶数)
	// 2.其他行和列中不是第一行/列的相同,就是相反,并整个棋盘中的相同和相反状态的数量差值<=1
	// 计算移动次数,由于行、列移动的独立性,我们只需要判断第一行、第一列的情况即可
	// 根据n的奇偶性,和0,1的数量,在判断奇偶位的0/1的数目,即为移动次数
	int movesToChessboard(vector<vector<int>>& board)
	{
		int n = board.size();
		int rowMask = 0, ReverRowMask = 0;		// 第一行的状态
		// 第一行中0和1的数量
		int count0 = 0, count1 = 0;
		// 遍历第一行,计算状态掩码和反状态掩码,并记录0,1的个数
		for (int i = 0; i < n; ++i)
		{
			rowMask <<= 1;
			ReverRowMask <<= 1;
			if (board[0][i])
			{
				rowMask |= 1;
				++count1;
			}
			else
			{
				ReverRowMask |= 1;
				++count0;
			}
		}
		// 判断第一行中0,1个数是否合理
		if (!((n % 2 == 1 && abs(count0 - count1) == 1) || (n % 2 == 0 && count0 == count1)))
		{
			return -1;
		}
		int couRM = 1, couRRM = 0;		// 和第一行状态相同的行数,相反的行数
		// 接下来遍历每一行
		for (int i = 1; i < n; ++i)
		{
			int rm = 0;
			// 计算该行的状态
			for (int j = 0; j < n; ++j)
			{
				rm <<= 1;
				rm |= board[i][j];
			}
			// 和第一行的状态以及反转态比较并计数
			if (rm == rowMask)
			{
				++couRM;
			}
			else if (rm == ReverRowMask)
			{
				++couRRM;
			}
			// 如果两个都不同,那就无解
			else
			{
				return -1;
			}
		}
		// 接下来根据n的奇偶性,和rouMask相同和相反的行数,来判断是否有解
		if (!((n % 2 == 1 && abs(couRM - couRRM) == 1) || (n % 2 == 0 && couRM == couRRM)))
		{
			return -1;
		}
		// 行判断完了,接下来就是列的判断,基本同上
		int colMask = 0, rColMask = 0;
		int count2 = 0, count3 = 0;
		for (int i = 0; i < n; ++i)
		{
			colMask <<= 1;
			rColMask <<= 1;
			if (board[i][0])
			{
				colMask |= 1;
				++count2;
			}
			else
			{
				rColMask |= 1;
				++count3;
			}
		}
		if (!((n % 2 == 1 && abs(count2 - count3) == 1) || (n % 2 == 0 && count2 == count3)))
		{
			return -1;
		}
		int couCM = 1, couRCM = 0;
		for (int i = 1; i < n; ++i)
		{
			int cm = 0;
			for (int j = 0; j < n; ++j)
			{
				cm <<= 1;
				cm |= board[j][i];
			}
			if (colMask == cm)
			{
				++couCM;
			}
			else if (cm == rColMask)
			{
				++couRCM;
			}
			else
			{
				return -1;
			}
		}
		if (!((n % 2 == 1 && abs(couCM - couRCM) == 1) || (n % 2 == 0 && couCM == couRCM)))
		{
			return -1;
		}

		// 接下来是计算需要移动的次数
		int ans = 0;
		int r1 = 0, c1 = 0;		// 计算第一行、第一列中,下标从0开始的偶数位1的个数
		for (int i = 0; i < n; i += 2)
		{
			r1 += board[0][i];
			c1 += board[i][0];
		}
		// n为偶数,偶数下标用0,用1都行
		if (n % 2 == 0)
		{
			// 有两种策略,一种是偶数下标中的0换为1,一种是将1换为0,取最小值
			ans += min(count1 - r1, r1) + min(count2 - c1, c1);
		}
		// 第一个必须是数量较多的
		else
		{
			// 第一行0比1多
			if (count0 > count1)
			{
				ans += r1;		// 用0换1
			}
			else
			{
				ans += count1 - r1;		// 用1换0
			}
			if (count3 > count2)
			{
				ans += c1;
			}
			else
			{
				ans += count2 - c1;
			}
		}
		return ans;
	}
};

int main()
{
	int n;
	Solution* sol = new Solution();
	while (~scanf("%d", &n))
	{
		vector<vector<int>> board(n, vector<int>(n));
		for (int i = 0; i < n; ++i)
		{
			for (int j = 0; j < n; ++j)
			{
				scanf("%d", &board[i][j]);
			}
		}
		printf("%d\n", sol->movesToChessboard(board));
	}

	delete sol;
	return 0;
}

算法分析+解题效率

时间复杂度 O ( n 2 ) O(n^2) O(n2),遍历两次矩阵。

空间复杂度 O ( 1 ) O(1) O(1),只使用了几个变量。

在这里插入图片描述

后话

不愧是Hard,打的有点慢!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 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