常见算法设计(三)

参考资料:
目录
[1. 回溯法概述](#1. 回溯法概述)
[1. 全排列 (Permutations)](#1. 全排列 (Permutations))
[2. 全排列 II (Permutations II)------去重](#2. 全排列 II (Permutations II)——去重)
[3. 排列序列 (Permutation Sequence)](#3. 排列序列 (Permutation Sequence))
[(二) 组合问题 (Combination)](#(二) 组合问题 (Combination))
[1. 组合 (Combinations)](#1. 组合 (Combinations))
[2. 电话号码的字母组合 (Letter Combinations of a Phone Number)](#2. 电话号码的字母组合 (Letter Combinations of a Phone Number))
[4.子集 II (Subsets II)](#4.子集 II (Subsets II))
[3. 求解搜索问题](#3. 求解搜索问题)
[1. 子集和问题 (Subset Sum)](#1. 子集和问题 (Subset Sum))
[(1) 子集和 (Subset Sum)](#(1) 子集和 (Subset Sum))
[(2) 组合总和 (Combination Sum)](#(2) 组合总和 (Combination Sum))
[(3) 组合总和 II (Combination Sum II)](#(3) 组合总和 II (Combination Sum II))
[(4) 组合总和 III (Combination Sum III)](#(4) 组合总和 III (Combination Sum III))
[2. 0-1背包问题 (0-1 Knapsack)](#2. 0-1背包问题 (0-1 Knapsack))
[(1) 0-1背包 (0-1 Knapsack)](#(1) 0-1背包 (0-1 Knapsack))
[(2) 分割等和子集 (Partition Equal Subset Sum)](#(2) 分割等和子集 (Partition Equal Subset Sum))
[3. 变量和相等问题 (Variable Sum Equation)](#3. 变量和相等问题 (Variable Sum Equation))
[(1) N 皇后 (N-Queens)](#(1) N 皇后 (N-Queens))
[(2) 解数独 (Sudoku Solver)](#(2) 解数独 (Sudoku Solver))
[4. 求解空间搜索问题](#4. 求解空间搜索问题)
[1. 基础搜索模型:单词搜索与路径问题](#1. 基础搜索模型:单词搜索与路径问题)
[(1) 单词搜索](#(1) 单词搜索)
[(2) 单词搜索 II](#(2) 单词搜索 II)
[(3) 不同路径 I](#(3) 不同路径 I)
[(4) 不同路径 II](#(4) 不同路径 II)
[(5) 最短路径](#(5) 最短路径)
[2. 状态扩散模型:迷宫](#2. 状态扩散模型:迷宫)
[(1) 迷宫问题](#(1) 迷宫问题)
[(2) 腐烂的橘子](#(2) 腐烂的橘子)
[3. 状态扩散模型:岛屿问题](#3. 状态扩散模型:岛屿问题)
[1. 岛屿数量](#1. 岛屿数量)
[2. 岛屿的最大面积](#2. 岛屿的最大面积)
[3. 岛屿的周长](#3. 岛屿的周长)
[4. 岛屿的最大边长](#4. 岛屿的最大边长)
[5. 岛屿的形状](#5. 岛屿的形状)
三、回溯法
1. 回溯法概述
定义与核心思想
回溯法,穷举法,也称为暴力搜索法,是一种最直接、最简单的算法设计技术。
其核心思想是遍历所有可能的解空间,逐一检查每个解是否满足问题的约束条件,并从中找出符合要求的解。
回溯法的优缺点
优点
实现简单,逻辑清晰,不易出错。
缺点
当解空间非常大时,计算时间会呈指数级增长,效率极低,甚至无法在合理时间内完成。
2.求解排列组合问题
(一)排列问题 (Permutation)
排列问题要求从 n 个元素中,按顺序选取 k 个元素进行排列,或者求所有元素的排列。
核心是顺序重要。
1. 全排列 (Permutations)

题目描述:
给定一个不含重复数字的数组 nums,返回其所有可能的全排列。
力扣链接:permutations
解题思路:
使用回溯法。
固定第一个元素,然后递归求解剩余 n-1 个元素的全排列。
Python代码:
python
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
def backtrack(path, used):
if len(path) == len(nums):
res.append(path[:])
return
for i in range(len(nums)):
if not used[i]:
used[i] = True
path.append(nums[i])
backtrack(path, used)
path.pop()
used[i] = False
backtrack([], [False] * len(nums))
return res
2. 全排列 II (Permutations II)------去重
题目描述:
给定一个可包含重复数字的数组 nums,返回其所有不重复的全排列。
力扣链接:permutations-ii
解题思路:
在回溯法的基础上,需要去重。
在每一层递归中,如果当前数字与前一个数字相同且前一个数字未被使用,则跳过,以避免重复排列。
Python代码:
python
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = []
def backtrack(path, used):
if len(path) == len(nums):
res.append(path[:])
return
for i in range(len(nums)):
if used[i] or (i > 0 and nums[i] == nums[i-1] and not used[i-1]):
continue
used[i] = True
path.append(nums[i])
backtrack(path, used)
path.pop()
used[i] = False
backtrack([], [False] * len(nums))
return res
3. 排列序列 (Permutation Sequence)
题目描述:
给出 n 和 k,返回从 1 到 n 组成的排列中,第 k 个排列。
力扣链接:permutation-sequence
解题思路:
利用数学方法,而不是生成所有排列。
根据 k 的大小,确定第一个数字,然后递归处理剩余数字。
Python代码:
python
class Solution:
def getPermutation(self, n: int, k: int) -> str:
nums = [str(i) for i in range(1, n+1)]
k -= 1
factorial = [1] * n
for i in range(1, n):
factorial[i] = factorial[i-1] * i
res = []
for i in range(n, 0, -1):
index = k // factorial[i-1]
k %= factorial[i-1]
res.append(nums.pop(index))
return ''.join(res)
(二) 组合问题 (Combination)
组合问题要求从 n 个元素中,不考虑顺序地选取 k 个元素。核心是顺序不重要。
1. 组合 (Combinations)
题目描述:
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
力扣链接:combinations
解题思路:
使用回溯法。
从 1 开始,依次选择数字,每次选择后,从剩余数字中选择 k-1 个。
Python代码:
python
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
res = []
def backtrack(start, path):
if len(path) == k:
res.append(path[:])
return
for i in range(start, n + 1):
path.append(i)
backtrack(i + 1, path)
path.pop()
backtrack(1, [])
return res
2. 电话号码的字母组合 (Letter Combinations of a Phone Number)
题目描述:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
力扣链接:letter-combinations-of-a-phone-number
解题思路:
使用回溯法。
将数字映射到对应的字母,然后递归组合。
Python代码:
python
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits:
return []
phone = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
'6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}
res = []
def backtrack(index, path):
if index == len(digits):
res.append(''.join(path))
return
for letter in phone[digits[index]]:
path.append(letter)
backtrack(index + 1, path)
path.pop()
backtrack(0, [])
return res
3.子集 (Subsets)
题目描述:
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
力扣链接:https://leetcode.cn/problems/subsets/
解题思路:
使用回溯法。
对于每个元素,选择或不选择,递归生成所有子集。
Python代码:
python
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
def backtrack(start, path):
res.append(path[:])
for i in range(start, len(nums)):
path.append(nums[i])
backtrack(i + 1, path)
path.pop()
backtrack(0, [])
return res
4.子集 II (Subsets II)
题目描述:给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
力扣链接:subsets-ii
解题思路:
在回溯法的基础上,需要去重。
先对数组排序,然后在同一层递归中,如果当前数字与前一个数字相同,则跳过。
Python代码:
python
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = []
def backtrack(start, path):
res.append(path[:])
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i-1]:
continue
path.append(nums[i])
backtrack(i + 1, path)
path.pop()
backtrack(0, [])
return res
3. 求解搜索问题
1. 子集和问题 (Subset Sum)
(1) 子集和 (Subset Sum)
题目:
给定一个非负整数数组 nums 和一个目标整数 target,请找出 nums 的一个子集,使其元素之和等于 target。
如果存在这样的子集,返回 True,否则返回 False。
力扣链接:partition-equal-subset-sum
(这是一个变体,要求将数组分成两个子集,使得两个子集的和相等,即 target = sum(nums) / 2)
条件:数组中的元素是非负整数。
思路:
使用 DFS 回溯。
对于每个元素,有两种选择:选择或不选择。递归搜索所有可能的子集,检查其和是否等于 target。
Python代码:
python
class Solution:
def canPartition(self, nums: list[int]) -> bool:
total = sum(nums)
if total % 2 != 0:
return False
target = total // 2
nums.sort(reverse=True) # 优化:从大到小排序,提前剪枝
def backtrack(index, current_sum):
if current_sum == target:
return True
if current_sum > target or index == len(nums):
return False
# 选择当前元素
if backtrack(index + 1, current_sum + nums[index]):
return True
# 不选择当前元素
if backtrack(index + 1, current_sum):
return True
return False
return backtrack(0, 0)
(2) 组合总和 (Combination Sum)
题目:
给定一个无重复元素的数组 candidates 和一个目标数 target,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
力扣链接:combination-sum
条件:数组中的元素无重复,数字可以重复使用。
思路:
使用 DFS 回溯。
对于每个数字,可以选择多次,因此递归时从当前数字开始,而不是 i+1。
Python代码:
python
class Solution:
def combinationSum(self, candidates: list[int], target: int) -> list[list[int]]:
res = []
def backtrack(start, path, remaining):
if remaining == 0:
res.append(path[:])
return
if remaining < 0:
return
for i in range(start, len(candidates)):
path.append(candidates[i])
backtrack(i, path, remaining - candidates[i]) # 可以重复选择,所以是 i 而不是 i+1
path.pop()
backtrack(0, [], target)
return res
(3) 组合总和 II (Combination Sum II)
题目:
给定一个数组 candidates 和一个目标数 target,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
力扣链接:combination-sum-ii
条件:数组中的元素可能有重复,每个数字只能使用一次。
思路:
使用 DFS 回溯 + 去重。
先对数组排序,然后在同一层递归中,如果当前数字与前一个数字相同,则跳过。
Python代码:
python
class Solution:
def combinationSum2(self, candidates: list[int], target: int) -> list[list[int]]:
candidates.sort()
res = []
def backtrack(start, path, remaining):
if remaining == 0:
res.append(path[:])
return
for i in range(start, len(candidates)):
if i > start and candidates[i] == candidates[i-1]:
continue
if candidates[i] > remaining:
break
path.append(candidates[i])
backtrack(i + 1, path, remaining - candidates[i])
path.pop()
backtrack(0, [], target)
return res
(4) 组合总和 III (Combination Sum III)
题目:
找出所有相加之和为 n 的 k 个数的组合。
组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
力扣链接:combination-sum-iii
条件:数字范围固定为 1-9,组合中不存在重复数字。
思路:
使用 DFS 回溯。
数字范围固定,因此可以提前确定循环范围。
Python代码:
python
class Solution:
def combinationSum3(self, k: int, n: int) -> list[list[int]]:
res = []
def backtrack(start, path, remaining):
if len(path) == k and remaining == 0:
res.append(path[:])
return
if len(path) > k or remaining < 0:
return
for i in range(start, 10):
path.append(i)
backtrack(i + 1, path, remaining - i)
path.pop()
backtrack(1, [], n)
return res
2. 0-1背包问题 (0-1 Knapsack)
(1) 0-1背包 (0-1 Knapsack)
题目:给定 n 个物品,每个物品有重量 w_i 和价值 v_i,以及一个容量为 W 的背包,求在不超过背包容量的前提下,能装入背包的物品的最大总价值。
力扣链接:partition-equal-subset-sum
条件:每个物品只能选择一次(0-1)。
思路:
使用 DFS 回溯 + 记忆化。
对于每个物品,有两种选择:放入或不放入背包。
递归搜索所有可能的组合,并记录满足容量约束下的最大价值。
Python代码:
python
class Solution:
def knapsack_01(self, weights, values, W):
n = len(weights)
memo = [[-1] * (W + 1) for _ in range(n)]
def backtrack(index, remaining_capacity):
if index == n or remaining_capacity == 0:
return 0
if memo[index][remaining_capacity] != -1:
return memo[index][remaining_capacity]
# 不选择当前物品
value_without = backtrack(index + 1, remaining_capacity)
# 选择当前物品(如果重量允许)
value_with = 0
if weights[index] <= remaining_capacity:
value_with = values[index] + backtrack(index + 1, remaining_capacity - weights[index])
memo[index][remaining_capacity] = max(value_without, value_with)
return memo[index][remaining_capacity]
return backtrack(0, W)
(2) 分割等和子集 (Partition Equal Subset Sum)
题目:
给定一个只包含正整数的非空数组 nums,判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
力扣链接:partition-equal-subset-sum
条件:数组中的元素是正整数。
思路:
这是一个 0-1 背包问题的变体。
将问题转化为:是否存在一个子集,其和为 sum(nums) / 2。
如果存在,则可以将数组分割成两个等和子集。
Python代码(动态规划):
python
class Solution:
def canPartition(self, nums: list[int]) -> bool:
total = sum(nums)
if total % 2 != 0:
return False
target = total // 2
dp = [False] * (target + 1)
dp[0] = True
for num in nums:
for i in range(target, num - 1, -1):
dp[i] = dp[i] or dp[i - num]
return dp[target]
3. 变量和相等问题 (Variable Sum Equation)
(1) N 皇后 (N-Queens)
题目:
在 n x n 的棋盘上放置 n 个皇后,使得它们不能互相攻击(即任意两个皇后都不能在同一行、同一列或同一对角线上)。
力扣链接:n-queens
条件:皇后不能在同一行、同一列或同一对角线上。
思路:
使用 DFS 回溯。
逐行放置皇后,对于每一行,尝试在每一列放置皇后,并检查是否与已放置的皇后冲突。
Python代码:
python
class Solution:
def solveNQueens(self, n: int) -> list[list[str]]:
def backtrack(row, cols, diag1, diag2):
if row == n:
res.append(['.' * c + 'Q' + '.' * (n - c - 1) for c in cols])
return
for col in range(n):
d1 = row - col
d2 = row + col
if col in cols or d1 in diag1 or d2 in diag2:
continue
backtrack(row + 1, cols + [col], diag1 + [d1], diag2 + [d2])
res = []
backtrack(0, [], [], [])
return res
(2) 解数独 (Sudoku Solver)
题目:编写一个程序,通过填充空格来解决数独问题。
力扣链接:https://leetcode.cn/problems/sudoku-solver/
条件:数独的规则是每行、每列、每个 3x3 子网格内数字 1-9 不重复。
思路:
使用 DFS 回溯。
遍历数独的每个空格,尝试填入 1-9 的数字,并检查是否有效。
如果有效,则继续递归;如果无效或递归失败,则回溯。
Python代码:
python
class Solution:
def solveSudoku(self, board: list[list[str]]) -> None:
def is_valid(board, row, col, num):
for i in range(9):
if board[row][i] == str(num) or board[i][col] == str(num):
return False
start_row, start_col = 3 * (row // 3), 3 * (col // 3)
for i in range(3):
for j in range(3):
if board[start_row + i][start_col + j] == str(num):
return False
return True
def backtrack():
for i in range(9):
for j in range(9):
if board[i][j] == '.':
for num in range(1, 10):
if is_valid(board, i, j, num):
board[i][j] = str(num)
if backtrack():
return True
board[i][j] = '.'
return False
return True
backtrack()
4. 求解空间搜索问题
1. 基础搜索模型:单词搜索与路径问题
(1) 单词搜索
题目:
给定一个 m x n 二维字符网格 board 和一个字符串 word。
如果 word 存在于网格中,返回 true。单词必须按照字母顺序,通过相邻的单元格内的字母构成,同一个格子不能重复使用。
力扣链接:word-search
条件:网格中字母可重复出现,但同一条路径不能走回头路。
思路:
使用 DFS 回溯。
从每个格子出发,向四个方向递归搜索,用原地修改标记已访问,走不通则回溯。
Python代码:
python
class Solution:
def exist(self, board: list[list[str]], word: str) -> bool:
m, n = len(board), len(board[0])
def backtrack(i, j, k):
if k == len(word):
return True
if i < 0 or i >= m or j < 0 or j >= n or board[i][j] != word[k]:
return False
temp = board[i][j]
board[i][j] = '#'
res = (backtrack(i + 1, j, k + 1) or
backtrack(i - 1, j, k + 1) or
backtrack(i, j + 1, k + 1) or
backtrack(i, j - 1, k + 1))
board[i][j] = temp
return res
for i in range(m):
for j in range(n):
if board[i][j] == word[0]:
if backtrack(i, j, 0):
return True
return False
(2) 单词搜索 II
题目:
给定一个 m x n 二维字符网格 board 和一个单词列表 words,返回所有在二维网格和单词列表中同时出现的单词。
力扣链接:word-search-ii
条件:单词列表可能很大,逐个调用题79的方法会超时。
思路:
将所有单词存入前缀树(Trie),在网格上回溯时,根据当前路径的前缀去 Trie 中查找,剪枝优化。
Python代码:
python
class TrieNode:
def __init__(self):
self.children = {}
self.word = None
class Solution:
def findWords(self, board: list[list[str]], words: list[str]) -> list[str]:
trie = TrieNode()
for w in words:
node = trie
for ch in w:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.word = w
m, n = len(board), len(board[0])
res = []
def backtrack(i, j, node):
ch = board[i][j]
if ch not in node.children:
return
node = node.children[ch]
if node.word is not None:
res.append(node.word)
node.word = None
board[i][j] = '#'
for di, dj in [(1,0), (-1,0), (0,1), (0,-1)]:
ni, nj = i + di, j + dj
if 0 <= ni < m and 0 <= nj < n:
backtrack(ni, nj, node)
board[i][j] = ch
for i in range(m):
for j in range(n):
backtrack(i, j, trie)
return res
(3) 不同路径 I
题目:
机器人位于 m x n 网格左上角,每次只能向下或向右移动一步,到达右下角共有多少条不同路径?
力扣链接:unique-paths
条件:无障碍物,只能向右、向下。
思路:使用 DFS 穷举 + 记忆化优化,避免重复计算。
Python代码:
python
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
memo = [[0] * n for _ in range(m)]
def dfs(i, j):
if i == m - 1 and j == n - 1:
return 1
if i >= m or j >= n:
return 0
if memo[i][j] != 0:
return memo[i][j]
memo[i][j] = dfs(i + 1, j) + dfs(i, j + 1)
return memo[i][j]
return dfs(0, 0)
(4) 不同路径 II
题目:网格中增加了障碍物(1 表示障碍,0 表示空地),求从左上角到右下角的不同路径数。
力扣链接:unique-paths-ii
条件:起点或终点有障碍物时直接返回 0。
思路:在题62的基础上增加剪枝:遇到障碍物直接返回 0。
Python代码:
python
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: list[list[int]]) -> int:
m, n = len(obstacleGrid), len(obstacleGrid[0])
if obstacleGrid[0][0] == 1 or obstacleGrid[m-1][n-1] == 1:
return 0
memo = [[0] * n for _ in range(m)]
def dfs(i, j):
if i == m - 1 and j == n - 1:
return 1
if i >= m or j >= n or obstacleGrid[i][j] == 1:
return 0
if memo[i][j] != 0:
return memo[i][j]
memo[i][j] = dfs(i + 1, j) + dfs(i, j + 1)
return memo[i][j]
return dfs(0, 0)
(5) 最短路径
题目:
给定一个 n x n 的二进制矩阵 grid,0 表示可通行,1 表示障碍。
返回从左上角到右下角的最短路径长度(可走8个方向)。不存在返回 -1。
力扣链接:shortest-path-in-binary-matrix
条件:起点或终点为障碍则返回 -1。求最短路径必须用 BFS。
思路:使用 BFS 层序扩展,第一次到达终点时的步数即为最短路径。
Python代码:
python
from collections import deque
class Solution:
def shortestPathBinaryMatrix(self, grid: list[list[int]]) -> int:
n = len(grid)
if grid[0][0] == 1 or grid[n-1][n-1] == 1:
return -1
queue = deque([(0, 0, 1)])
grid[0][0] = 1
directions = [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)]
while queue:
r, c, steps = queue.popleft()
if r == n - 1 and c == n - 1:
return steps
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < n and 0 <= nc < n and grid[nr][nc] == 0:
grid[nr][nc] = 1
queue.append((nr, nc, steps + 1))
return -1
2. 状态扩散模型:迷宫
(1) 迷宫问题
题目:
给定一个 m x n 的迷宫 maze(0=通路,1=墙壁),球从 start 出发,可以沿四个方向滚动,球会一直滚动直到撞到墙壁才停下。判断球能否停在 destination。
力扣链接:the-maze
条件:球不能在通路中间停下,必须滚到撞墙为止。
思路:使用 BFS,每次扩展不是走一格,而是沿一个方向一直走到撞墙才停下。
Python代码:
python
from collections import deque
class Solution:
def hasPath(self, maze: list[list[int]], start: list[int], destination: list[int]) -> bool:
m, n = len(maze), len(maze[0])
queue = deque([(start[0], start[1])])
maze[start[0]][start[1]] = 2
directions = [(1,0), (-1,0), (0,1), (0,-1)]
while queue:
r, c = queue.popleft()
if r == destination[0] and c == destination[1]:
return True
for dr, dc in directions:
nr, nc = r, c
while 0 <= nr + dr < m and 0 <= nc + dc < n and maze[nr + dr][nc + dc] != 1:
nr += dr
nc += dc
if maze[nr][nc] != 2:
maze[nr][nc] = 2
queue.append((nr, nc))
return False
(2) 腐烂的橘子
题目:
网格中 0=空格、1=新鲜橘子、2=腐烂橘子。
每分钟腐烂橘子会传染上下左右的新鲜橘子。
返回所有橘子腐烂的最短分钟数。
不可能则返回 -1。
力扣链接:rotting-oranges
条件:可能有多个腐烂橘子同时存在。
思路:使用多源 BFS,初始时将所有腐烂橘子入队,同步扩展传染过程。
Python代码:
python
from collections import deque
class Solution:
def orangesRotting(self, grid: list[list[int]]) -> int:
m, n = len(grid), len(grid[0])
queue = deque()
fresh = 0
for i in range(m):
for j in range(n):
if grid[i][j] == 2:
queue.append((i, j, 0))
elif grid[i][j] == 1:
fresh += 1
if fresh == 0:
return 0
directions = [(1,0), (-1,0), (0,1), (0,-1)]
max_minutes = 0
while queue:
r, c, minutes = queue.popleft()
max_minutes = max(max_minutes, minutes)
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < m and 0 <= nc < n and grid[nr][nc] == 1:
grid[nr][nc] = 2
fresh -= 1
queue.append((nr, nc, minutes + 1))
return max_minutes if fresh == 0 else -1
3. 状态扩散模型:岛屿问题
岛屿问题的核心是:
在二维网格中,1 代表陆地,0 代表水域。需要根据陆地的连通性(上下左右)进行统计或转换。
岛屿数量:DFS/BFS 遍历,每遇到一个新岛屿,计数加一。
岛屿面积:DFS/BFS 遍历,计算当前岛屿的陆地数量。
岛屿周长:遍历每个陆地,检查其相邻格子,是水域或边界则周长加一。
岛屿最大值:动态规划,记录每个格子能构成的最大正方形边长。
岛屿形状:DFS 遍历,记录相对坐标,使用集合去重。
1. 岛屿数量
题目:给定一个由 '1'(陆地)和 '0'(水)组成的二维网格,计算网格中岛屿的数量。一个岛屿是由水平或垂直方向上相邻的陆地连接而成的组合。
力扣链接:number-of-islands
条件:网格中的 1 和 0 代表陆地和水域。
思路:遍历整个网格,遇到一个未访问的陆地(1),就触发一次 DFS 或 BFS,将整个岛屿的所有陆地标记为已访问,岛屿数量加一。
Python代码(DFS):
python
class Solution:
def numIslands(self, grid: list[list[str]]) -> int:
if not grid:
return 0
m, n = len(grid), len(grid[0])
count = 0
def dfs(i, j):
if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != '1':
return
grid[i][j] = '0' # 标记已访问
dfs(i + 1, j)
dfs(i - 1, j)
dfs(i, j + 1)
dfs(i, j - 1)
for i in range(m):
for j in range(n):
if grid[i][j] == '1':
count += 1
dfs(i, j)
return count
2. 岛屿的最大面积
题目:
给定一个由 '1'(陆地)和 '0'(水)组成的二维网格,计算网格中最大的岛屿面积。
一个岛屿的面积是岛上为 '1' 的单元格的数目。
力扣链接:max-area-of-island
条件:网格中的 1 和 0 代表陆地和水域。
思路:与题200类似,但在 DFS 过程中,计算当前岛屿的面积,并更新全局最大面积。
Python代码(DFS):
python
class Solution:
def maxAreaOfIsland(self, grid: list[list[int]]) -> int:
if not grid:
return 0
m, n = len(grid), len(grid[0])
max_area = 0
def dfs(i, j):
if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != 1:
return 0
grid[i][j] = 0 # 标记已访问
area = 1
area += dfs(i + 1, j)
area += dfs(i - 1, j)
area += dfs(i, j + 1)
area += dfs(i, j - 1)
return area
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
max_area = max(max_area, dfs(i, j))
return max_area
3. 岛屿的周长
题目:给定一个 N x N 的网格,每个单元格都有一个值 grid[i][j],表示该单元格的陆地高度。网格的周长被定义为所有陆地单元格的边中,其值与相邻陆地单元格不同的边的数量。
力扣链接:island-perimeter
条件:网格中的 1 和 0 代表陆地和水域。
思路:遍历每个陆地格子,检查其上下左右四个方向。如果相邻是水域(0)或边界,则周长加一。
Python代码:
python
class Solution:
def islandPerimeter(self, grid: list[list[int]]) -> int:
m, n = len(grid), len(grid[0])
perimeter = 0
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
# 上
if i == 0 or grid[i-1][j] == 0:
perimeter += 1
# 下
if i == m - 1 or grid[i+1][j] == 0:
perimeter += 1
# 左
if j == 0 or grid[i][j-1] == 0:
perimeter += 1
# 右
if j == n - 1 or grid[i][j+1] == 0:
perimeter += 1
return perimeter
4. 岛屿的最大边长
题目:给定一个由 '1'(陆地)和 '0'(水)组成的二维网格,计算网格中最大的正方形岛屿的边长。一个岛屿是正方形,当且仅当它的所有边都是陆地,并且所有角都是直角。
力扣链接:maximal-square
条件:网格中的 1 和 0 代表陆地和水域。
思路:使用动态规划。对于每个格子 (i, j),如果它是陆地,则其能构成的正方形边长为 1 + min(上, 左, 左上)。
Python代码(DP):
python
class Solution:
def maximalSquare(self, matrix: list[list[str]]) -> int:
if not matrix or not matrix[0]:
return 0
m, n = len(matrix), len(matrix[0])
dp = [[0] * (n + 1) for _ in range(m + 1)]
max_side = 0
for i in range(1, m + 1):
for j in range(1, n + 1):
if matrix[i-1][j-1] == '1':
dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])
max_side = max(max_side, dp[i][j])
return max_side * max_side
5. 岛屿的形状
题目:给定一个由 '1'(陆地)和 '0'(水)组成的二维网格,计算网格中不同岛屿的数量。如果两个岛屿的形状相同,则视为同一个岛屿。
条件:网格中的 1 和 0 代表陆地和水域。
思路:使用 DFS 遍历每个岛屿,记录其形状(例如,相对于起点的相对坐标),并使用集合去重。
Python代码(DFS + 形状记录):
python
class Solution:
def numDistinctIslands(self, grid: list[list[int]]) -> int:
if not grid:
return 0
m, n = len(grid), len(grid[0])
shapes = set()
def dfs(i, j, origin_i, origin_j, shape):
if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != 1:
return
grid[i][j] = 0 # 标记已访问
shape.append((i - origin_i, j - origin_j))
dfs(i + 1, j, origin_i, origin_j, shape)
dfs(i - 1, j, origin_i, origin_j, shape)
dfs(i, j + 1, origin_i, origin_j, shape)
dfs(i, j - 1, origin_i, origin_j, shape)
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
shape = []
dfs(i, j, i, j, shape)
shapes.add(tuple(shape))
return len(shapes)