[leetcode]树的搜索和回溯算法

树的搜索

树的搜索 · SharingSource/LogicStack-LeetCode Wiki (github.com)

173. 二叉搜索树迭代器 (leetcode-cn.com)

image-20211226222309617

其实我就是先中序遍历一遍…(偷懒了)

type BSTIterator struct {
	result []*TreeNode
}

func Constructor(root *TreeNode) BSTIterator {
	result := make([]*TreeNode, 0, 100000)
	var dfs func(root *TreeNode)
	dfs = func(root *TreeNode) {
		if root == nil {
			return
		}
		dfs(root.Left)
		result = append(result, root)
		dfs(root.Right)
	}
	dfs(root)
	return BSTIterator{result: result}
}

func (this *BSTIterator) Next() (ret int) {
	ret = this.result[0].Val
	this.result = this.result[1:]
	return
}

func (this *BSTIterator) HasNext() bool {
	return len(this.result) > 0
}

331. 验证二叉树的前序序列化 (leetcode-cn.com)

image-20211226222446440

就是先往左找再往右找边界,如果最后边界刚好是总长就ok

//获取边界的终点
func isOK(nums []string, index int) int {
	if index >= len(nums) || index == -1 {
		return -1
	}
	if nums[index] == "#" { //遇到终止就返回下一个起始点
		return index + 1
	}
	return isOK(nums, isOK(nums, index+1))
}

func isValidSerialization(preorder string) bool {
	nums := strings.Split(preorder, ",")
	return isOK(nums, 0) == len(nums) //看看整个的终点是不是刚好是总长度
}

671. 二叉树中第二小的节点 - 力扣(LeetCode) (leetcode-cn.com)

image-20211226223127469

找到一个第二小的点🤣(大小比最开始的点大,比其他点小)

func findSecondMinimumValue(root *TreeNode) int {
    ans := -1
    rootVal := root.Val
    var dfs func(*TreeNode)
    dfs = func(node *TreeNode) {
        if node == nil || ans != -1 && node.Val >= ans {
            return
        }
        if node.Val > rootVal {
            ans = node.Val
        }
        dfs(node.Left)
        dfs(node.Right)
    }
    dfs(root)
    return ans
}

993. 二叉树的堂兄弟节点 (leetcode-cn.com)

image-20211226223945979

用bfs即可

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
type Node struct{
    parent *TreeNode
    node *TreeNode
    level int
}
func isCousins(root *TreeNode, x int, y int) bool {
    queue := make([]*Node,0,101)
    queue = append(queue,&Node{parent:nil,node:root,level:0})
    isOk := 0
    var x1,y1 *Node
    for len(queue)>0 {
        p := queue[0]
        queue = queue[1:]
        if p.node.Val == x{
            x1 = p
            isOk++
        }
        if p.node.Val == y{
            y1 = p
            isOk++
        }
        if isOk == 2{
            break
        }
        if p.node.Left!=nil{
            queue = append(queue,&Node{parent:p.node,node:p.node.Left,level:p.level+1})
        }
        if p.node.Right!=nil{
            queue = append(queue,&Node{parent:p.node,node:p.node.Right,level:p.level+1})
        }
    }
    return x1.level == y1.level && x1.parent != y1.parent
}

回溯算法

回溯算法 · SharingSource/LogicStack-LeetCode Wiki (github.com)

dfs模板

17. 电话号码的字母组合 - 力扣(LeetCode) (leetcode-cn.com)

image-20211226195459694

这个就是把数字的每一位所代表的的字母进行枚举

func letterCombinations(digits string) (ret []string) {
    if digits == ""{
        return
    }
    letterMap := []string{
		" ",    //0
		"",     //1
		"abc",  //2
		"def",  //3
		"ghi",  //4
		"jkl",  //5
		"mno",  //6
		"pqrs", //7
		"tuv",  //8
		"wxyz", //9
	}
    result := make([]byte,len(digits))
    var dfs func(step int)
    dfs = func(step int){
        if step >= len(digits){ //循环终止条件->数字每一位都枚举完了
            ret = append(ret,string(result))
            return
        }
        for _,v:=range letterMap[digits[step]-'0']{
            result[step] = byte(v)
            dfs(step+1)
        }
    }
    dfs(0)
    return
}

797. 所有可能的路径 (leetcode-cn.com)

image-20211226195815465

这个有点像图的邻接表下的dfs

func allPathsSourceTarget(graph [][]int) (ret [][]int) {
    n := len(graph)
    res := make([]int,0,n)
    res = append(res,0)
    var dfs func(idx int)
    dfs = func(idx int){
        if idx == n-1 { //到达目的地终止
            ret = append(ret,append([]int{},res...))
            return
        }
        for _,v:=range graph[idx]{ //遍历连接的点
            res = append(res,v)
            dfs(v)
            res = res[:len(res)-1]
        }
    }
    dfs(0)
    return
}

79. 单词搜索 (leetcode-cn.com)

image-20211226200421078

这个题有点像是迷宫问题的解法(dfs遍历就对了)

var next = [4][2]int{{0,1},{0,-1},{-1,0},{1,0}}

func exist(board [][]byte, word string) bool {
    var dfs func(x,y,idx int,isOK [][]bool)bool
    dfs = func(x,y,idx int,isOK [][]bool)bool{
        if idx == len(word){
            return true
        }
        for i:=0;i<4;i++{
            nx := x+next[i][0]
            ny := y+next[i][1]
            if nx<0||ny<0||nx>=len(board)||ny>=len(board[nx])||isOK[nx][ny]||board[nx][ny]!=word[idx]{
                continue
            }
            isOK[nx][ny] = true
            if dfs(nx,ny,idx+1,isOK){
                return true
            }
            isOK[nx][ny] = false
        }
        return false
    }
    for i,row := range board{ //遍历棋盘,从每个可行的点开始进行dfs
        for j,v :=range row{
            if v == word[0] {
                isOK := make([][]bool,len(board)) //标记数组
                for i:=range isOK{
                    isOK[i] = make([]bool,len(board[i]))
                }
                isOK[i][j] = true
                if dfs(i,j,1,isOK){
                    return true
                }
            }
        }
    }
    return false
}

排列组合

剑指 Offer 38. 字符串的排列 (leetcode-cn.com)

image-20211226200628237

这个主要考察的是排列组合+去重

排列组合有点像啊哈算法上计算n张牌的全排列问题,有n个桶,每个桶放一张牌,遍历n个桶,在每个桶中遍历每张牌,如果有某张牌没有被使用就放到当前的桶中,知道所有的桶都被装满了就算一种放法.

而去重则一般对这种数组进行排序,然后进行判断

//如果前一个字符没有被遍历,且和此时的字符相等,则会出现重复(因为上一次已经用过这个组合了)
//ps:比如3个c(c0c1c2) 第一次:c0c1c2 第二次c0c2c1重复 第三次c1c0c2重复...
if b || j>0 && !vis[j-1] && t[j] == t[j-1] {
    continue
}
func permutation(s string) (ans []string) {
    n := len(s)
    t := []byte(s)
    sort.Slice(t, func(i, j int) bool { return t[i] < t[j] }) //排序
    vis := make([]bool, n) //标记数组
    perm := make([]byte, 0, n)
    var dfs func(int)
    dfs = func(i int){
        if i == n { //都放满了就放到结果李
            ans = append(ans,string(perm))
            return
        }
        for j,b := range vis {
            if b || j>0 && !vis[j-1] && t[j] == t[j-1] { //去重
                continue
            }
            vis[j] = true
            perm = append(perm,t[j])
            dfs(i+1)
            perm = perm[:len(perm)-1]
            vis[j] = false
        }
    }
    dfs(0)
    return
}

从n个数里挑几个使其满足条件的种类数

39. 组合总和 (leetcode-cn.com)

image-20211226202302121

在面对每一个数的时候要么选,要么跳过.维持一个当前剩余值和当前面对的数,要么选择这个数(如果可以选,然后把剩余值减掉响应的部分),要么直接跳过.(因为都是不重复的数,不存在去重问题)

func combinationSum(candidates []int, target int) (ret [][]int) {
	result := make([]int, 0, 510)
	var dfs func(target, idx int)
	dfs = func(target, idx int) {
		if idx == len(candidates) { //到达边界,直接返回
			return
		}
		if target == 0 { //凑出目标值,就加入结果
			ret = append(ret, append([]int{}, result...))
			return
		}
        if target-candidates[idx] >= 0 { //不跳过,看看可以选择不
			result = append(result, candidates[idx])
			dfs(target-candidates[idx], idx)
			result = result[:len(result)-1]
		}
		dfs(target, idx+1) //跳过这个位置的数
	}
	dfs(target, 0)
	return
}

另一种写法(和下面那个题保持一致)

func combinationSum(candidates []int, target int) (ret [][]int) {
	result := make([]int, 0, 510)
	var dfs func(target, idx int)
	dfs = func(target, idx int) {
		if target == 0 { //凑出目标值,就加入结果
			ret = append(ret, append([]int{}, result...))
			return
		}
        for i:=idx;i<len(candidates);i++ {
            if target-candidates[i] >= 0 { //不跳过,看看可以选择不
			    result = append(result, candidates[i])
			    dfs(target-candidates[i], i)
			    result = result[:len(result)-1]
		    }
        }
	}
	dfs(target, 0)
	return
}

40. 组合总和 II (leetcode-cn.com)

image-20211226202759897

这个既要考虑去重,还得考虑组合

先排序来防止重复(感觉和前面的去重还不太一样,这个只要保证一个数使用一次就行),

func combinationSum2(candidates []int, target int) (ret [][]int) {
    sort.Ints(candidates)
    results := make([]int,0,len(candidates))
    var dfs func(target,index int)
    dfs = func(target,index int) {
        if target == 0 {
            ret = append(ret,append([]int{},results...))
            return
        }
        for i:=index;i<len(candidates);i++ {
            if i>index && candidates[i]==candidates[i-1] { //保证一个数只用一次
                continue
            }
            if target-candidates[i]>=0 { //试探下可以用不
                results = append(results,candidates[i])
                dfs(target-candidates[i],i+1)
                results = results[:len(results)-1]
            }else { //因为从小到大排序,在这里可以剪枝下
                break
            }
        }
    }
    dfs(target,0)
    return
}

子集

78. 子集 (leetcode-cn.com)

image-20211226204433043

类似上面第三个类,可以选,也可以不选,没有额外限制条件

func subsets(nums []int) (ret [][]int) {
	result := make([]int, 0, len(nums))
	var dfs func(index int)
	dfs = func(index int) {
		if index == len(nums) {
			ret = append(ret, append([]int{}, result...)) //这种写法防止切片被后续修改
			return
		}
		result = append(result, nums[index])
		dfs(index + 1)
		result = result[:len(result)-1]
		dfs(index + 1)
	}
	dfs(0)
	return
}

90. 子集 II (leetcode-cn.com)

image-20211226221103179

这个在上面一个的基础上,增加了去重

同样要先排序,然后去重,和排列组合那个挺像的

if !isChoose && index > 0 && nums[index] == nums[index-1] {
	return
}

考虑数组 [1,2,2][1,2,2],选择前两个数,或者第一、三个数,都会得到相同的子集。

也就是说,对于当前选择的数 xx,若前面有与其相同的数 yy,且没有选择 yy,此时包含 xx 的子集,必然会出现在包含 yy 的所有子集中。

我们可以通过判断这种情况,来避免生成重复的子集。代码实现时,可以先将数组排序;迭代时,若发现没有选择上一个数,且当前数字与上一个数相同,则可以跳过当前生成的子集。

func subsetsWithDup(nums []int) (ret [][]int) {
	sort.Ints(nums)
	result := make([]int, 0, len(nums))
	var dfs func(isChoose bool, index int)
	dfs = func(isChoose bool, index int) {
		if index == len(nums) {
			ret = append(ret, append([]int{}, result...))
			return
		}
		dfs(false, index+1)
		if !isChoose && index > 0 && nums[index] == nums[index-1] {
			return
		}
		result = append(result, nums[index])
		dfs(true, index+1)
		result = result[:len(result)-1]
	}
	dfs(false, 0)
	return
}

分隔子串

131. 分割回文串 (leetcode-cn.com)

image-20211226221659899

先判断在哪个位置可以切,然后dfs下去,

func partition(s string) (ret [][]string) {
    //先用dp计算回文子串用于后续判断用
    isOK := make([][]bool,len(s))
    for i:=range isOK{
        isOK[i] = make([]bool,len(s))
        isOK[i][i] = true
    }
    for i := len(isOK)-2;i>=0;i--{
        for j:=i+1;j<len(isOK[i]);j++{
            if s[i] == s[j] && ((j-1<i+1)||isOK[i+1][j-1]) {
                isOK[i][j] = true
            }
        }
    }
    //dfs进行搜索
    result := make([]string,0,len(s))
    var dfs func(idx int)
    dfs = func(idx int){
        if idx == len(s) {
            ret = append(ret,append([]string{},result...))
            return
        }
        for j:=idx;j<len(s);j++ {
            if isOK[idx][j] { //如果在这个idx位置可以分割,就dfs下去
                result = append(result,s[idx:j+1])
                dfs(j+1)
                result = result[:len(result)-1]
            }
        }
    }
    dfs(0)
    return
}

未完待续…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值