Leetcode 934:最短的桥(超详细的解法!!!)

探讨在二维二进制数组中,如何找到使两座由1组成的岛连接所需的最少0翻转次数。通过改进的floodfill算法,实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在给定的二维二进制数组 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. 1 <= A.length = A[0].length <= 100
  2. A[i][j] == 0A[i][j] == 1

解题思路

这个问题非常有意思,同样也非常复杂。我们首先想到的做法是通过floodfill找到两个岛屿,然后遍历两个岛屿的元素寻找距离最小值。关于寻找岛屿这个问题,可以参考如下几个问题

Leetcode 200:岛屿的个数(最详细的解法!!!)

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来访问周边的点,等。这些都会加快我们的代码。

另外一个比较好的思路是,先通过BFSDFS找到一个岛屿,然后再通过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

如有问题,希望大家指出!!!

<think>嗯,我现在是一个大一计算机专业的新生,想要学习数据结构,但不知道怎么开始。首先,数据结构到底是什么呢?好像听说过数组、链表这些词,但具体有什么用呢?老师上课讲的时候,感觉有点抽象,不太明白为什么需要这些东西。 我应该从哪里开始学呢?是不是需要先掌握好C语言?因为之前学过一些C语言,但可能还不够扎实。比如指针和内存管理这些概念,是不是在数据结构里很重要?如果基础不牢的话,会不会影响学习数据结构的效果? 听说数据结构有很多种,比如栈、队列、树、图等等。这些不同的结构有什么应用场景呢?比如栈用在什么地方,队列又解决什么问题?有没有实际的例子帮助理解? 学习方法方面,是不是应该多写代码?光看书或者听课可能不够,得动手实践。但是刚开始的时候,可能会遇到很多问题,比如代码写不出来,或者调试很久都找不到错误。这时候该怎么办呢?有没有推荐的练习平台或者项目? 还有,数据结构在面试中好像很重要,特别是大公司的技术面试。那学习数据结构除了应付考试和面试,实际工作中有哪些应用呢?比如在开发软件、游戏或者做算法的时候,数据结构的作用体现在哪里? 另外,有没有好的学习资源推荐?比如书籍、在线课程或者视频教程。听说《算法导论》很有名,但可能比较难,适不适合初学者?或者有没有更入门的书籍? 还有一个问题,如何检验自己是否真正掌握了某个数据结构?是通过做题的数量,还是能够自己实现并优化代码?有没有一些标准或者方法可以评估自己的学习效果? 时间安排上,每天应该花多少时间学习数据结构?需不需要做长期的学习计划,比如每周学习一个结构,同时复习前面的内容? 可能还有一些误区需要注意,比如只注重理论不写代码,或者反过来只写代码不理解原理。怎样才能平衡理论和实践呢? 后,数据结构和其他计算机课程有什么联系?比如操作系统、数据库、算法这些,数据结构是不是它们的基础?学好数据结构对后续学习这些课程有帮助吗?</think>学习数据结构是计算机科学的核心基础之一,掌握它能帮助你高效解决问题并深入理解计算机系统。以下是针对你的问题的分步指南: --- ### 一、为什么学数据结构? 1. **程序效率的根基** - 数据存储方式直接影响算法时间复杂度 - 例:链表($O(n)$查找) vs 哈希表($O(1)$查找) 2. **真实场景应用** - 数据库索引 → B+树 - 浏览器历史记录 → 栈 - 任务调度 → 优先队列 3. **职业发展必备** - 大厂面试必考(LeetCode题型占比超60%) - 开源项目贡献基础(如Linux内核红黑树) --- ### 二、如何高效学习? #### 阶段1:夯实基础(2-3周) 1. **C语言重点强化** - 指针操作:理解`int*** p`的多级指针 - 内存管理:手动实现`malloc`/`free` - 结构体与联合体:自定义数据结构载体 2. **核心数据结构入门** ```c // 示例:链表节点定义 typedef struct Node { int data; struct Node* next; } Node; ``` - 重点掌握:数组/链表/栈/队列 #### 阶段2:系统学习(8-10周) 1. **学习方法论** - **可视化工具**:用[Data Structure Visualizations](https://www.cs.usfca.edu/~galles/visualization/Algorithms.html)动态观察结构变化 - **手写实现**:从零实现每种结构(如AVL树旋转) - **复杂度分析**:用数学归纳法证明操作时间复杂度 2. **推荐学习顺序** | 数据结构 | 关键点 | 应用场景 | |--------------|-------------------------|---------------------| | 栈/队列 | LIFO/FIFO特性 | 函数调用栈/消息队列 | | 树 | 二叉树遍历(前/中/后序)| 文件系统目录结构 | | 图 | DFS/BFS算法 | 社交网络关系 | | 哈希表 | 冲突解决(开放寻址法) | 缓存系统 | #### 阶段3:实战进阶(持续) 1. **LeetCode刷题策略** - 按类型分类练习(如链表专题) - 经典题目举例: - 反转链表(多种解法) - 二叉树层次遍历 - Dijkstra路径 2. **项目实践** - 实现JSON解析器(使用树结构) - 开发简易数据库(B+树索引) - 编写游戏寻路AI(A*算法) --- ### 三、必看学习资源 1. **教材推荐** - 《数据结构(C语言版)》严蔚敏 → 国内经典教材 - 《算法(第4版)》Sedgewick → 图示丰富易理解 2. **在线课程** - 浙江大学-数据结构(慕课)→ 配套PTA练习平台 - MIT 6.006 Introduction to Algorithms → 英文字幕版 3. **工具推荐** - CLion IDE:强大的C/C++调试工具 - Valgrind:检测内存泄漏 --- ### 四、避坑指南 1. **常见误区** - ❌ 死记代码模板 → ✅ 理解设计思想 - ❌ 跳过数学证明 → ✅ 推导时间复杂度 - ❌ 忽视边界条件 → ✅ 测试空指针/极端输入 2. **调试技巧** - 使用GDB逐步跟踪链表指针 - 打印内存地址验证结构正确性 ```c printf("Node %p: data=%d, next=%p\n", node, node->data, node->next); ``` --- ### 五、与其他课程的联系 1. **操作系统** - 进程调度 → 优先队列 - 文件系统 → B树 2. **数据库** - 索引结构 → B+树 - 事务管理 → 日志队列 3. **编译原理** - 语法分析 → 栈结构 - 符号表 → 哈希表 --- ### 六、学习效果检验 1. **能力自测表** - 能否在白板手写红黑树插入逻辑 - 能否解释哈希表负载因子的影响 - 能否比较邻接矩阵与邻接表的优劣 2. **进阶挑战** - 参与ACM-ICPC竞赛 - 贡献开源项目(如Redis的跳表实现) --- ### 总结 数据结构是连接理论与实践的梁,建议采用“理论→手写实现→应用优化”的三段式学习法。每周投入10-15小时,坚持3个月可建立扎实基础。记住:**理解原理比记忆代码更重要**,这将为后续学习操作系统、算法等课程打下坚实基础。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值