回溯法 - 软考备战(四十三)

常见算法设计(三)

参考资料:

7.4 回溯算法 | 算法通关手册(LeetCode)

13.1 回溯算法 - Hello 算法


目录

常见算法设计(三)

三、回溯法

[1. 回溯法概述](#1. 回溯法概述)

定义与核心思想

回溯法的优缺点

2.求解排列组合问题

(一)排列问题 (Permutation)

[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))

3.子集 (Subsets)

[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'(水)组成的二维网格,计算网格中不同岛屿的数量。如果两个岛屿的形状相同,则视为同一个岛屿。

力扣链接:making-a-large-island

条件:网格中的 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)
相关推荐
AC赳赳老秦2 小时前
OpenClaw进阶技巧:批量修改文件内容、替换关键词,解放双手
java·linux·人工智能·python·算法·测试用例·openclaw
Robot_Nav2 小时前
Shape-Aware MPPI(SA MPPI)算法:基于RC-ESDF的任意形状机器人实时轨迹优化
算法·机器人·sa-mppi
小O的算法实验室3 小时前
2026年ESWA,自适应基于排序的协同进化学习粒子群算法+边缘计算服务器部署,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
cpp_25013 小时前
P1832 A+B Problem(再升级)
数据结构·c++·算法·动态规划·题解·洛谷·背包dp
꧁细听勿语情꧂4 小时前
合并两个有序表、判断链表的回文结构、相交链表、环的链表一和二
c语言·开发语言·数据结构·算法
木井巳4 小时前
【递归算法】解数独
java·算法·leetcode·决策树·深度优先·剪枝
大肥羊学校懒羊羊4 小时前
完数与盈数的计算题解
数据结构·c++·算法
阿Y加油吧4 小时前
算法实战笔记:LeetCode 31 下一个排列 & 287 寻找重复数
笔记·算法·leetcode
穿条秋裤到处跑4 小时前
每日一道leetcode(2026.04.24):距离原点最远的点
算法·leetcode·职场和发展