M1536.排布二进制网格的最少交换次数
greedy, matrix, https://leetcode.cn/problems/minimum-swaps-to-arrange-a-binary-grid/
给你一个 n x n 的二进制网格 grid,每一次操作中,你可以选择网格的 相邻两行 进行交换。
一个符合要求的网格需要满足主对角线以上的格子全部都是 0 。
请你返回使网格满足要求的最少操作次数,如果无法使网格符合要求,请你返回 -1 。
主对角线指的是从 (1, 1) 到 (n, n) 的这些格子。
示例 1:

输入:grid = [[0,0,1],[1,1,0],[1,0,0]]
输出:3示例 2:

输入:grid = [[0,1,1,0],[0,1,1,0],[0,1,1,0],[0,1,1,0]]
输出:-1
解释:所有行都是一样的,交换相邻行无法使网格符合要求。示例 3:

输入:grid = [[1,0,0],[1,1,0],[1,1,1]]
输出:0提示:
n == grid.lengthn == grid[i].length1 <= n <= 200grid[i][j]要么是0要么是1。
【王天纵 25地空学院】思路:把每行二进制全部转化为十进制,然后只需要考察每行是不是能被对应2的幂整除,从上往下遍历遇到第一个满足的就是要移动的(贪心)
python
class Solution:
def minSwaps(self, grid: List[List[int]]) -> int:
num = []
for i in range(len(grid)):
r = ''
for j in grid[i]:
r += str(j)
num.append(int(r, 2))
res, n = 0, len(num)
for i in range(n):
for j in range(len(num)):
if num[j] % (2 ** (n - i - 1)) == 0:
num.pop(j)
res += j
break
else:
return -1
return res题目要求我们通过交换相邻行的方式,使矩阵的主对角线以上的元素全部为
题目分析
根据题目对“主对角线以上均为 0”的描述,若把矩阵视作以
由于我们只能交换相邻的行,这等价于在一个数组中,通过相邻交换把数字移动到满足条件的位置,且要求交换的次数最少。
- 预处理: 我们可以遍历矩阵,将每一行转换成该行末尾连续
的个数,存入一个数组 zeros中。 - 贪心策略: 我们从第
行开始往下遍历,对于第 行,我们需要一个末尾连续 个数 的行。为了使交换次数最少,我们应当在 zeros数组中从位置开始往后寻找第一个满足条件的行。找到后,将其通过相邻交换“提升”到第 行的位置。 - 正确性保证: 为什么寻找“第一个”满足条件的行是最优的?因为需要连续
的数量条件是从大到小递减的( )。如果我们越过第一个满足条件的行去拿更后面的行,不仅当前步骤增加了不必要的交换次数,而且还会把原本满足当前严格条件的那个前面的行给“挤”下去,这对后续更宽松的条件并没有带来任何好处。 - 无法完成: 如果在寻找过程中,某一行的要求无法被满足(即从
到 都找不到符合要求的行),则说明永远无法排列成目标网格,直接返回 -1。
Python 3 代码实现
python
class Solution:
def minSwaps(self, grid: List[List[int]]) -> int:
n = len(grid)
zeros =[]
# 1. 预处理:计算每一行末尾连续 0 的个数
for row in grid:
count = 0
# 从后往前遍历
for j in range(n - 1, -1, -1):
if row[j] == 0:
count += 1
else:
break
zeros.append(count)
ans = 0
# 2. 遍历每一行,寻找符合条件的行并进行模拟交换
for i in range(n):
target = n - 1 - i
# 在当前及之后的行中寻找第一个满足 0 的个数 >= target 的行
match = -1
for j in range(i, n):
if zeros[j] >= target:
match = j
break
# 如果找不到,说明无法满足条件,返回 -1
if match == -1:
return -1
# 累加需要的交换次数(即距离差)
ans += match - i
# 模拟交换过程:将匹配到的行抽出,插入到当前需要的索引 i 处
# 相当于 match 到 i+1 的元素依次往下平移了一格
val = zeros.pop(match)
zeros.insert(i, val)
return ans复杂度
- 时间复杂度:
。首先预处理寻找每行末尾 的个数需耗费 的时间;接着对于外层长度为 的循环中,寻找匹配的行和模拟列表交换 ( pop和insert操作) 也是,故总计为 时间。在 的数据范围内运算极快,性能绝佳。 - 空间复杂度:
。我们使用了一个一维数组 zeros来存储每行符合末尾连续的个数。
【金于珑 工学院】思路:第一步先翻译,把每一行对应元素用“右侧连续0的数量”来代替。然后从上往下,以此寻找>=n-1,n-2,n-3,……的第一个数字所在的位置(贪心思路),将它移到grid的第1,2,3,……行,。但是出于效率,不直接移动而是使用懒删除,即把移动的数字改为-1,在后续计算步数时扣去-1的个数。如果能成功找到n-1次,则返回step,否则返回-1.
python
class Solution:
def minSwaps(self, grid: List[List[int]]) -> int:
n = len(grid)
step = 0
def count0(l):
cnt = 0
while l:
a = l.pop()
if a == 0:
cnt += 1
else:
break
return cnt
for i in range(n):
grid[i] = count0(grid[i])
for i in range(1, n):
lazy = 0 # 记录当前行之前有多少行已经被“移走”(标记为 -1)
find = False # 标记是否找到了满足条件的行
for j in range(n):
# 情况 A: 找到满足条件的行
if grid[j] >= n - i:
grid[j] = -1 # 【懒删除】标记该行已被使用,后续跳过
step += j - lazy # 计算交换次数:当前索引 - 已移走的行数 = 实际需要的相邻交换步数
find = True
break
# 情况 B: 遇到已经被移走的行
elif grid[j] == -1:
lazy += 1
# 如果遍历完所有行都没找到满足条件的,说明无解
if not find:
return -1
return step