[17]岛屿数量和电话号码的字母组合

文章介绍了如何解决LeetCode上的两道题目,一是计算二维网格中的岛屿数量,提供了BFS、DFS和并查集三种解法;二是给出数字字符串,找出所有可能的字母组合,讨论了两种不同的实现方式。

*内容来自leetcode

1.岛屿数量

题目要求

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1


示例 2:

输入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3

思路

没有思路

三种方法,BFS,DFS,并查集

DFS

我们可以将二维网格看成一个无向图,竖直或水平相邻的 1 之间有边相连。

为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0。

最终岛屿的数量就是我们进行深度优先搜索的次数。

class Solution:
    def dfs(self,grid,r,l):
        grid[r][l] = '0'
        n,m = len(grid[0]),len(grid)
        #循环遍历四周的节点
        for i,j in [(r-1,l),(r+1,l),(r,l+1),(r,l-1)]:
            if 0 <= i < m and 0 <= j < n and grid[i][j] == '1':
                self.dfs(grid,i,j)
    def numIslands(self, grid: List[List[str]]) -> int:
        #DFS
        count = 0
        n = len(grid[0])
        m = len(grid)
        for i in range(m):
            for j in range(n):
                if grid[i][j] == "1":
                    count += 1 
                    self.dfs(grid,i,j)
        return count

BFS

为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则将其加入队列,开始进行广度优先搜索。在广度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0。直到队列为空,搜索结束。

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        #BFS
        count = 0
        n = len(grid[0])
        m = len(grid)
        if m == 0:
            return 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] == '1':
                    count += 1
                    grid[i][j] == '0'
                    duilie = collections.deque([(i,j)])
                    while duilie:
                        row,col = duilie.popleft()
                        for x,y in [(row+1,col),(row-1,col),(row,col+1),(row,col-1)]:
                            if 0 <= x < m and 0 <= y < n and grid[x][y] == '1':
                                duilie.append((x,y))
                                grid[x][y] = '0'
        return count

并查集

并查集的相关知识可见于:

算法学习笔记(1) : 并查集 - 知乎

针对当前的问题

为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则将其与相邻四个方向上的 11在并查集中进行合并。

最终岛屿的数量就是并查集中连通分量的数目。

#并查集
class FindUnion:
    def __init__(self,grid):
        m = len(grid[0])
        n = len(grid)
        self.fa = [-1]*(m*n)
        self.count = 0
        for i in range(n):
            for j in range(m):
                if grid[i][j] == '1':
                    self.fa[i*m+j] = i*m+j
                    self.count += 1
    
    def find(self,i):
        if self.fa[i] == i:
            return i
        else:
            return self.find(self.fa[i])
    
    def getUnion(self,x,y):
        headx = self.find(x)
        heady = self.find(y)
        if headx != heady:
            self.fa[heady] = headx
            self.count -= 1
    
    def getCount(self):
        return self.count
        
class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        union = FindUnion(grid)
        m = len(grid[0])
        n = len(grid)
        for i in range(n):
            for j in range(m):
                if grid[i][j] == '1':
                    grid[i][j] = '0'
                    for x,y in [(i+1,j),(i-1,j),(i,j+1),(i,j-1)]:
                            if 0 <= x < n and 0 <= y < m and grid[x][y] == '1':
                                union.getUnion(i*m+j,x*m+y)
        return union.getCount()

 在上述的实现中,使用的是最简单的并查集。没有对合并的过程进行优化。对并查集优化的代码如下

class FindUnion:
    def __init__(self,grid):
        m = len(grid[0])
        n = len(grid)
        self.fa = [-1]*(m*n)
        self.rank = [0]*(m*n)
        self.count = 0
        for i in range(n):
            for j in range(m):
                if grid[i][j] == '1':
                    self.fa[i*m+j] = i*m+j
                    self.count += 1
    #对find过程进行了优化,是每个节点直接连接到根节点
    def find(self,i):
        if self.fa[i] == i:
            return i
        else:
            self.fa[i] = self.find(self.fa[i])
            return self.fa[i]
    #对Union过程进行了优化,让并查集的树层次结构更少,加快速度
    def getUnion(self,x,y):
        headx = self.find(x)
        heady = self.find(y)
        if headx != heady:
            if self.rank[headx] <= self.rank[heady]:
                self.fa[headx] = heady
            else:
                self.fa[heady] = headx
            if self.rank[headx] == self.rank[heady] and headx != heady:
                self.rank[heady] += 1
            self.count -= 1
    
    def getCount(self):
        return self.count

——中级算法-树和图部分完结——

——中级算法-回溯算法——

2.电话号码的字母组合

题目要求

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:

输入:digits = ""
输出:[]
示例 3:

输入:digits = "2"
输出:["a","b","c"]

思路

先初始化一个空的result字符列表,列表的大小由电话号码决定。然后根据电话号码把对应字母添加到result字符串中。具体见代码及注释

class Solution:
    def checkNum(self,digit,result):
        times = len(result)
        #生成要添加的字符列表
        #生成的字符列表大小与result列表大小一致
        if digit == '2':
            strs = ['a','b','c']*(times//3)
            
        if digit == '3':
            strs = ['d','e','f']*(times//3)
            
        if digit == '4':
            strs = ['g','h','i']*(times//3)
            
        if digit == '5':
            strs = ['j','k','l']*(times//3)
            
        if digit == '6':
            strs = ['m','n','o']*(times//3)
            
        if digit == '7':
            strs = ['p','q','r','s']*(times//4)
            
        if digit == '8':
            strs = ['t','u','v']*(times//3)
            
        if digit == '9':
            strs = ['w','x','y','z']*(times//4)
            
        for i in range(len(result)):
            result[i] = result[i] + strs[I]
        #将每次得到的结果排序,而每次新添加的strs列表是没有排序的
        #这样才能保证得到的列表没有重复的
        result.sort()
        return result

    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return []
        n = len(digits)
        #获取最后结果的列表大小
        nums = 1
        for i in range(n):
            if digits[i] == '7' or digits[i] == '9':
                nums *= 4
            else:
                nums *= 3
        result = [''] * nums
        #往结果列表里添加新字母
        for i in range(n):
            result = self.checkNum(digits[i],result)
        return result

官方给出的,回溯法

首先使用哈希表存储每个数字对应的所有可能的字母,然后进行回溯操作。

回溯过程中维护一个字符串,表示已有的字母排列(如果未遍历完电话号码的所有数字,则已有的字母排列是不完整的)。该字符串初始为空。每次取电话号码的一位数字,从哈希表中获得该数字对应的所有可能的字母,并将其中的一个字母插入到已有的字母排列后面,然后继续处理电话号码的后一位数字,直到处理完电话号码中的所有数字,即得到一个完整的字母排列。然后进行回退操作,遍历其余的字母排列。

回溯算法用于寻找所有的可行解,如果发现一个解不可行,则会舍弃不可行的解。在这道题中,由于每个数字对应的每个字母都可能进入字母组合,因此不存在不可行的解,直接穷举所有的解即可。

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return []
        phoneMap = {
            "2": "abc",
            "3": "def",
            "4": "ghi",
            "5": "jkl",
            "6": "mno",
            "7": "pqrs",
            "8": "tuv",
            "9": "wxyz",
        }
        def trackBack(index):
            if index == len(digits):
                combinations.append("".join(combination))
            else:
                digit = digits[index]
                for letter in phoneMap[digit]:
                    combination.append(letter)
                    trackBack(index+1)
                    combination.pop()

        combination = list()
        combinations = list()
        trackBack(0)
        return combinations

# 力扣hot100刷题记录表 ### 一,哈希部分 - [ ] 1. 两数之 (简单) - [ ] 2. 字母异位词分组(中等) - [ ] 3. 最长连续序列(中等) ### 二,双指针部分 - [ ] 4. 移动零(简单) - [ ] 5. 盛水最多的容器 (中等) - [ ] 6. 三数之 (中等) - [ ] 7. 接雨水(困难) ### 三,滑动窗口 - [ ] 8. 无重复字符的最长子串(中等) - [ ] 9. 找到字符中所有的字母异位词(中等) ### 四,子串 - [ ] 10. 为k的子数组(中等) - [ ] 11. 滑动窗口最大值(困难) - [ ] 12. 最小覆盖子窜(困难) ### 五,普通数组 - [ ] 13. 最大子数组(中等) - [ ] 14. 合并区间(中等) - [ ] 15. 轮转数组(中等) - [ ] 16. 除自身以外数组的乘积(中等) - [ ] 17. 缺失的第一个正数(困难) ### 六,矩阵 - [ ] 18. 矩阵置零(中等) - [ ] 19. 螺旋矩阵 (中等) - [ ] 20. 旋转图像 (中等) - [ ] 21. 搜索二维矩阵Ⅱ (中等) ### 七,链表 - [ ] 22. 相交链表 (简单) - [ ] 23. 反转链表 (简单) - [ ] 24. 回文链表 (简单) - [ ] 25. 环形链表 (简单) - [ ] 26. 环形链表Ⅱ (中等) - [ ] 27. 合并两个有序链表 (简单) - [ ] 28. 两数相加 (中等) - [ ] 29. 删除链表的倒数第 N 个结点 (中等) - [ ] 30. 两两交换链表中的节点 (中等) - [ ] 31. K个一组翻转链表 (困难) - [ ] 32. 随机链表的复制 (中等) - [ ] 33. 排序链表 (中等) - [ ] 34. 合并 K 个升序链表 (困难) - [ ] 35. LRU 缓存 (中等) ### 八,二叉树 - [ ] 36. 二叉树的中序遍历 (简单) - [ ] 37. 二叉树的最大深度 (简单) - [ ] 38. 翻转二叉树 (简单) - [ ] 39. 对称二叉树 (简单) - [ ] 40. 二叉树的直径 (简单) - [ ] 41. 二叉树的层序遍历 (中等) - [ ] 42. 将有序数组转换为二叉搜索树 (简单) - [ ] 43. 验证二叉搜索树 (中等) - [ ] 44. 二叉搜索树中第 K 小的元素 (中等) - [ ] 45. 二叉树的右视图 (中等) - [ ] 46. 二叉树展开为链表 (中等) - [ ] 47. 从前序与中序遍历序列构造二叉树 (中等) - [ ] 48. 路径总 III (中等) - [ ] 49. 二叉树的最近公共祖先 (中等) - [ ] 50. 二叉树中的最大路径 (困难) ### 九,图论 - [ ] 51. 岛屿数量 (中等) - [ ] 52. 腐烂的橘子 (中等) - [ ] 53. 课程表 (中等) - [ ] 54. 实现 Trie(前缀树) (中等) ### 十,回溯 - [ ] 55.全排列(中等) - [ ] 56.子集(中等) - [ ] 57.电话号码字母组合(中等) - [ ] 58.组合总(中等) - [ ] 59.括号生成(中等) - [ ] 60.单词搜索(中等) - [ ] 61.分割回文串(中等) - [ ] 62.N 皇后 (困难) ### 十一,二分查找 - [ ] 63. 搜索插入位置 (简单) - [ ] 64. 搜索二维矩阵 (中等) - [ ] 65. 在排序数组中查找元素的第一个最后一个位置 (中等) - [ ] 66. 搜索旋转排序数组 (中等) - [ ] 67. 寻找旋转排序数组中的最小值 (中等) - [ ] 68. 寻找两个正序数组的中位数 (困难) ### 十二,栈 - [ ] 69. 有效的括号 (简单) - [ ] 70. 最小栈 (中等) - [ ] 71. 字符串解码 (中等) - [ ] 72. 每日温度 (中等) - [ ] 73. 柱状图中最大的矩形 (困难) ### 十三,堆 - [ ] 74. 数组中的第K个最大元素 (中等) - [ ] 75. 前K 个高频元素 (中等) - [ ] 76. 数据流的中位数 (闲难) ### 十四,贪心算法 - [ ] 77. 买卖股票的最佳时机 (简单) - [ ] 78. 跳跃游戏 (中等) - [ ] 79. 跳跃游戏 III (中等) - [ ] 80. 划分字母区间 (中等) ### 十五,动态规划 - [ ] 81. 爬楼梯(简单) - [ ] 82. 杨辉三角 (简单) - [ ] 83. 打家劫舍 (中等) - [ ] 84. 完全平方数 (中等) - [ ] 85. 零钱兑换 (中等) - [ ] 86. 单词拆分 (中等) - [ ] 87. 最长递增子序列 (中等) - [ ] 88. 乘积最大子数组 (中等) ### 十六,多维动态规划 - [ ] 91. 不同路径 (中等) - [ ] 92. 最小路径 (中等) - [ ] 93. 最长回文子串 (中等) - [ ] 94. 最长公共子序列 (中等) - [ ] 95. 编辑距离 (中等) ### 十七,技巧 - [ ] 96. 只出现一次的数字 (简单) - [ ] 97. 多数元素 (简单) - [ ] 98. 颜色分类 (中等) - [ ] 99. 下一个排列 (中等) - [ ] 100. 寻找重复数 (中等) 如何使用
07-20
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值