iOS算法刷题之DFS

258 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情

DFS深度优先搜索与backtrace回溯.png 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. 子集 II40. 组合总和 II216. 组合总和 III47. 全排列 II39. 组合总和

其他经典DFS

DFS本质上就是一种暴力穷举算法,在穷举的基础上增加回溯或者剪支基本就能完成大部分题型。 经典DFS题还有: 494. 目标和剑指 Offer 62. 圆圈中最后剩下的数17. 电话号码的字母组合22. 括号生成112. 路径总和113. 路径总和II51. N皇后52. N皇后 II93. 复原IP地址698. 划分为K个相等的子集