算法(4): 搜索

3.1 深度优先DFS

问题1:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 image.png Input: “23” Output: [“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”]

思路:深度优先搜索,从根节点到每个叶子节点的所有路径即结果,深度为子串的长度

示例代码:

var (
	letterMap = []string{
		" ",    //0
		"",     //1
		"abc",  //2
		"def",  //3
		"ghi",  //4
		"jkl",  //5
		"mno",  //6
		"pqrs", //7
		"tuv",  //8
		"wxyz", //9
	}
	res   = []string{}
)

func letterCombinations(digits string) []string {
	if digits == "" {
		return []string{}
	}
    res = []string{}
	dfs(digits, 0, "")
	return res
}

func dfs(digits string, i int, s string) {
	if i == len(digits) {
		res = append(res, s)
		return
	}
	curs := letterMap[digits[i] - '0']
	for _, ch := range curs {
		dfs(digits, i+1, s + string(ch))
	}
}

问题2:给定一个数组,要求在这个数组中找出 k 个数之和为 target 的所有组合。 Input: nums = [1, 0, -1, 0, -2, 2], and target = 0. Output: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]

思路:深度优先搜索,层数为k。注意去重的处理。

示例代码:

import "sort"

var res = [][]int{}

func fourSum(nums []int, target int) [][]int {
    if len(nums) == 0 {
		return [][]int{}
	}
    sort.Ints(nums)
    res = [][]int{}
	r := []int{}
	dfs_sum(nums, 0, r, target)
	return res
}

func dfs_sum(nums []int, i int, r []int, target int)  {
    if i == 4 || 0 == len(nums) {
        if len(r) >= 4 && r[0] + r[1] + r[2] + r[3] == target {
            res = append(res, []int{r[0], r[1], r[2], r[3]})
		}
		return
	}
	for j := 0; j < len(nums); j++ {
        if j > 0 && nums[j] == nums[j-1] {
            continue
        }
        r = append(r, nums[j])
        dfs_sum(nums[j+1:], i + 1, r, target)
        if len(r) >= 1 {
            r = r[:len(r)-1]
        }
	}
}

3.2 宽度优先搜索 BFS

宽度优先搜索通常用在求最短路径,按层遍历,第一次满足要求的层数就是最短的路径。BFS需要借助队列来保存每一层的节点,访问过的节点要做标记避免重复访问。

3.2.1 二进制矩阵中的最短路径

LeetCode No.1091

题目描述:给你一个 n x n 的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1 。二进制矩阵中的 畅通路径 是一条从 左上角 单元格(即,(0, 0))到 右下角 单元格(即,(n - 1, n - 1))的路径,该路径同时满足下述要求: 1 路径途经的所有单元格都的值都是 0 。 2 路径中所有相邻的单元格应当在 8 个方向之一 上连通(即,相邻两单元之间彼此不同且共享一条边或者一个角)。 畅通路径的长度 是该路径途经的单元格总数。

思路:从左上角开始,遍历所有可以走的方向,BFS方式求到达右下角的最小层数。

示例代码:

type Pos struct {
	x, y int
}

func shortestPathBinaryMatrix(grid [][]int) int {
	if len(grid) == 0 || len(grid[0]) == 0 {
		return -1
	}
	M, N := len(grid), len(grid[0])
	direction := []Pos{{1, -1}, {1, 0}, {1, 1}, {0, -1}, {0, 1}, {-1, -1}, {-1, 0}, {-1, 1}}
	Q := list.New()
	// 根节点入队
	Q.PushBack(Pos{0, 0})
	path_len := 0
	for Q.Len() != 0 {
		// 保存当前层的长度
		level_size := Q.Len()
		path_len++
		// 依次处理当前层的所有节点
		for i := 0; i < level_size; i++ {
			cur_pos := Q.Front().Value.(Pos)
			Q.Remove(Q.Front())
			if grid[cur_pos.x][cur_pos.y] == 1 {
				continue
			}
			// 如果到达右下角返回结果
			if cur_pos.x == (M - 1) && cur_pos.y == (N - 1) {
				return path_len
			}
			// 访问过的位置标记为1
			grid[cur_pos.x][cur_pos.y] = 1
			// 遍历所有能走的方向,加入队列
			for _, dr := range direction {
				nx, ny := cur_pos.x + dr.x, cur_pos.y + dr.y
				if nx < 0 || nx >= M || ny < 0 || ny >= N {
					continue
				}
				Q.PushBack(Pos{nx, ny})
			}
		}
	}
	return -1
}

3.2.2 完全平方数

LeetCode No.279

题目描述:给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

思路:转化为BFS问题模型,从1到根号n的所有平方数(1,4,9…)为每次所有可能的路径。从n开始遍历,当n=0时即为结果。

示例代码:

func numSquares(n int) int {
	numsqs := []int{}
	for i, ii := 1, 1; ii <= n; i, ii = i + 1, (i + 1) * (i + 1){
		numsqs = append(numsqs, ii)
	}
	mark := map[int]bool{}
	Q := list.New()
	Q.PushBack(n)
	mark[n] = true
	ans := 0
	for Q.Len() != 0 {
		lv_size := Q.Len()
		ans++
		for i := 0; i < lv_size; i++ {
			cur := Q.Front().Value.(int)
			Q.Remove(Q.Front())
			for _, nq := range numsqs {
				if cur == nq {
					return ans
				}
				if cur < nq {
					break
				}
				if mark[cur - nq] {
					continue
				}
				mark[cur - nq] = true
				Q.PushBack(cur - nq)
			}
		}
	}
	return n
}