开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情
DFS与backtrace回溯就是一种简单粗暴的算法技巧,就是一种暴力穷举的算法。很多排列组合相关问题都可以通过DFS来解决。
DFS就是深度遍历,backtrace回溯的话是在深度遍历完后会有恢复现场的操作。回溯算法和DFS算法非常类似,本质上就是一种暴力穷举算法。回溯算法和DFS算法的细微差别是:回溯算法是在遍历“树枝”,DFS算法是在遍历“节点”。
岛屿问题
岛屿系列问题可以用 DFS/BFS 算法或者 Union-Find 并查集算法 来解决。
用 DFS 算法解决岛屿题目是最常见的,每次遇到一个岛屿中的陆地,就用 DFS 算法吧这个岛屿「淹掉」。
如何使用 DFS 算法遍历二维数组?你把二维数组中的每个格子看做「图」中的一个节点,这个节点和周围的四个节点连通,这样二维矩阵就被抽象成了一幅网状的「图」。
为什么每次遇到岛屿,都要用 DFS 算法把岛屿「淹了」呢?主要是为了省事,避免维护 visited 数组。遍历图是需要 visited 数组记录遍历过的节点防止走回头路。
因为 dfs 函数遍历到值为 0 的位置会直接返回,所以只要把经过的位置都设置为 0,就可以起到不走回头路的作用。
岛屿问题举例: 200. 岛屿数量
class Solution {
func maxAreaOfIsland(_ grid: [[Int]]) -> Int {
var res = 0
let m = grid.count
let n = grid[0].count
var grid = grid
for i in 0..<m {
for j in 0..<n {
// 淹没岛屿并更新最大岛屿面积
res = max(res, dfs(grid: &grid, i: i, j: j))
}
}
return res
}
func dfs(grid: inout [[Int]], i: Int, j: Int) -> Int {
if i < 0 || j < 0 || i >= grid.count || j >= grid[0].count {
return 0
}
if grid[i][j] == 0 {
// 说明已经是海水了
return 0
}
grid[i][j] = 0
return 1
+ dfs(grid: &grid, i: i, j: j+1)
+ dfs(grid: &grid, i: i+1, j: j)
+ dfs(grid: &grid, i: i-1, j: j)
+ dfs(grid: &grid, i: i, j: j-1)
}
}
其他经典的岛屿问题的题有:695. 岛屿的最大面积、1254. 统计封闭岛屿的数目、1020. 飞地的数量、1905. 统计子岛屿、130. 被围绕的区域、733. 图像渲染
排列组合子集问题
排列组合子集问题可以分为三种形式:
- 形式一、元素无重不可复选,即 nums 中的元素都是唯一的,每个元素最多只能被使用一次
- 形式二、元素可重不可复选,即 nums 中的元素可以存在重复,每个元素最多只能被使用一次
- 形式三、元素无重可复选,即 nums 中的元素都是唯一的,每个元素可以被使用若干次
上面用组合问题举的例子,但排列、组合、子集问题都可以有这三种基本形式,所以共有 9 种变化。
除此之外,题目也可以再添加各种限制条件,比如让你求和为 target 且元素个数为 k 的组合,那这么一来又可以衍生出一堆变体,怪不得面试笔试中经常考到排列组合这种基本题型。
但无论形式怎么变化,其本质就是穷举所有解,而这些解呈现树形结构,所以合理使用回溯算法框架,稍改代码框架即可搞定。
排列组合子集问题举例:78. 子集
题解:主要使用回溯算法思路,本质上子集问题就是遍历一棵回溯树。
class Solution {
var temp: [Int] = []
var result: [[Int]] = []
func subsets(_ nums: [Int]) -> [[Int]] {
dfs(nums: nums, start: 0)
return result
}
func dfs(nums: [Int], start: Int) {
result.append(temp)
for i in start..<nums.count {
temp.append(nums[i])
dfs(nums: nums, start: i+1)
temp.removeLast()
}
}
}
其他排列组合子集问题还有:77. 组合、46. 全排列、90. 子集 II、40. 组合总和 II、216. 组合总和 III、47. 全排列 II、39. 组合总和
其他经典DFS
DFS本质上就是一种暴力穷举算法,在穷举的基础上增加回溯或者剪支基本就能完成大部分题型。 经典DFS题还有: 494. 目标和、剑指 Offer 62. 圆圈中最后剩下的数、17. 电话号码的字母组合、22. 括号生成、112. 路径总和、113. 路径总和II、51. N皇后、52. N皇后 II、93. 复原IP地址、698. 划分为K个相等的子集