在给定的二维二进制数组 A
中,存在两座岛。(岛是由四面相连的 1
形成的一个最大组。)
现在,我们可以将 0
变为 1
,以使两座岛连接起来,变成一座岛。
返回必须翻转的 0
的最小数目。(可以保证答案至少是 1。)
示例 1:
输入:[[0,1],[1,0]]
输出:1
示例 2:
输入:[[0,1,0],[0,0,0],[0,0,1]]
输出:2
示例 3:
输入:[[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
输出:1
提示:
1 <= A.length = A[0].length <= 100
A[i][j] == 0
或A[i][j] == 1
解题思路
这个问题非常有意思,同样也非常复杂。我们首先想到的做法是通过floodfill
找到两个岛屿,然后遍历两个岛屿的元素寻找距离最小值。关于寻找岛屿这个问题,可以参考如下几个问题
Leetcode 130:被围绕的区域(最详细的解法!!!)
class Solution:
def shortestBridge(self, A):
"""
:type A: List[List[int]]
:rtype: int
"""
islandA, islandB, A_len, result = set(), set(), len(A), float('inf')
for i in range(A_len):
for j in range(A_len):
if A[i][j] == 0:
continue
if not islandA:
self._floodFill(A, islandA, i, j)
if not islandB and (i, j) not in islandA:
self._floodFill(A, islandB, i, j)
for i in islandA:
for j in islandB:
result = min(result, self._distance(i, j))
return result
def _floodFill(self, A, island, i, j):
island.add((i, j))
if 0 < i and (i-1, j) not in island and A[i - 1][j]:
self._floodFill(A, island, i - 1, j)
if 0 < j and (i, j-1) not in island and A[i][j - 1]:
self._floodFill(A, island, i, j - 1)
if i < len(A) - 1 and (i+1, j) not in island and A[i + 1][j]:
self._floodFill(A, island, i + 1, j)
if j < len(A[0]) - 1 and (i, j+1) not in island and A[i][j + 1]:
self._floodFill(A, island, i, j + 1)
def _distance(self, a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1]) - 1
我们在记录岛屿数量的时候使用了set
这个数据结构,这样我们就不用另外开辟一块空间作为visited
。我们这里同样没有使用for _ in range(4)
这种写法去遍历方位,这是之前问题Leetcode 200:岛屿的个数(最详细的解法!!!)中我们提到的问题,不再赘述。我们这里使用的距离函数是曼哈顿距离,这是根据题目意思来的。但是这种思路使用python
来解的话超时了。显然这种做法不是最好的。
我们有一个更好的做法,我们首先遍历输入的数组,找到其中的一个1
,我们以这个1
为起点进行floodfill
,并且同时将包含这个点的岛屿的所有点变成2
1* 1 1 1 1
1 0 0 0 1
1 0 1 0 1
1 0 0 0 1
1 1 1 1 1
变为
2* 2 2 2 2
2 0 0 0 2
2 0 1 0 2
2 0 0 0 2
2 2 2 2 2
这样我们就将两个岛屿给区分了出来。接着我们将现在的2
这个岛屿不断扩展出去,直到和1
这个岛屿相遇。扩展出去的土地的标记值不断增加(我们是一圈一圈往外扩展)。
2* 2 2 2 2
2 3 3 3 2
2 3 1 3 2
2 3 3 3 2
2 2 2 2 2
最后我们的结果就应该是扩展出的最外层岛的标号减去2
。
class Solution:
def shortestBridge(self, A):
"""
:type A: List[List[int]]
:rtype: int
"""
x, y, r, c = 0, 0, len(A), len(A[0])
for i in range(r):
for j in range(c):
if A[i][j] == 1:
x, y = i, j
break
else:
continue
break
d = [(-1, 0), (0, 1), (1, 0), (0, -1)]
visited = [[0]*c for _ in range(r)]
def _floodfill(i, j):
if 0 <= i < r and 0 <= j < c and not visited[i][j] and A[i][j] == 1:
A[i][j] = 2
visited[i][j] = 1
for k in range(4):
_floodfill(i + d[k][0], j + d[k][1])
def _expand(A, i, j, s):
if 0 <= i < r and 0 <= j < c and not visited[i][j]:
visited[i][j] = 1
if A[i][j] == 0:
A[i][j] = s + 1
return A[i][j] == 1
_floodfill(x, y)
s = 2
while 1:
for i in range(r):
for j in range(c):
if A[i][j] != s:
continue
for k in range(4):
if _expand(A, i + d[k][0],j + d[k][1], s):
return s - 2
s += 1
上面这种写法还有需要值得优化的地方,比如说我们可以将visited
做成set
,我们不是通过for
来访问周边的点,等。这些都会加快我们的代码。
另外一个比较好的思路是,先通过BFS
或DFS
找到一个岛屿,然后再通过BFS
将这个岛屿的周边扩展出去知道碰到另外一个岛屿。
class Solution:
def shortestBridge(self, A):
"""
:type A: List[List[int]]
:rtype: int
"""
x, y, r, c = 0, 0, len(A), len(A[0])
for i in range(r):
for j in range(c):
if A[i][j] == 1:
x, y = i, j
break
else:
continue
break
d = [(-1, 0), (0, 1), (1, 0), (0, -1)]
visited = [[0]*c for _ in range(r)]
islandA, result = list(), 0
def _floodfill(i, j):
if 0 <= i < r and 0 <= j < c and not visited[i][j] and A[i][j] == 1:
visited[i][j] = 1
islandA.append((i, j))
for k in range(4):
_floodfill(i + d[k][0], j + d[k][1])
_floodfill(x, y)
while islandA:
stack = list()
for i, j in islandA:
if i - 1 >= 0 and not visited[i-1][j]:
if A[i-1][j] == 1:
return result
else:
stack.append((i-1, j))
visited[i-1][j] = 1
if i + 1 < r and not visited[i+1][j]:
if A[i+1][j] == 1:
return result
else:
stack.append((i+1, j))
visited[i+1][j] = 1
if j - 1 >= 0 and not visited[i][j-1]:
if A[i][j-1] == 1:
return result
else:
stack.append((i, j-1))
visited[i][j-1] = 1
if j + 1 < c and not visited[i][j+1]:
if A[i][j+1] == 1:
return result
else:
stack.append((i, j+1))
visited[i][j+1] = 1
islandA = stack
result += 1
return result
我将该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!