递归法

递归法

22 括号生成

力扣

题目描述:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

思路:这道题可以用递归+回溯的方法,满足( 的数量不能大于n,( 数量比 )多的时候才能加右( 。string长度等于2n的时候就递归到头了。优先向左括号增多的方向递归。

backTracking = func(s *[]string, cur string, left int, right int, max int) {
        if len(cur) == max*2 { // 递归到头了
            *s = append(*s, cur)
        }

        // 这个遍历顺序类似于一个中序遍历,也满足了字典顺序,即把( 看作1,)看作0,总是以一个较大的值排列
        // 左括号数量不能大于n
        if left < max {
            cur += "("
            backTracking(s, cur, left+1, right, max)
            cur = cur[:len(cur)-1]
        }
        // 左括号数量要大于右括号数量,才能网上加右括号
        if left > right {
            cur += ")"
            backTracking(s, cur, left, right+1, max)
            cur = cur[:len(cur)-1]
        }
    }
    backTracking(&res, "", 0, 0, n)
    return res 
}

491 递增子序列

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

输入: nums = [4,6,7,7]
输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]

注意点:

  • 同一层相同的值不能重复使用,所以要进行去重

  • 这题跨层一定不能得出重复的值,所以跨层不需要管

  • 注意递归下标

  • 赋值 res 之前先复制

  • 回溯不能删除标记

  • 二维数组传递指针,不要使用全局变量的形式!

func findSubsequences(nums []int) [][]int {
    res := make([][]int, 0)
    tmp := make([]int, 0)
    backTracking(nums, tmp, 0, &res)
    return res
}

// res: 暂时生成的递归子序列; idx: 开始遍历nums数组中下标为idx的元素, nums: 输入数组;
// tmp: 生成的中间结果的数组;
func backTracking(nums, tmp []int, idx int, res *[][]int) {
    if len(tmp) >= 2 { // 子序列长度满足要求
        t := make([]int, len(tmp))
        copy(t, tmp)
        *res = append(*res, t)
    }

    // 设置 used 数组,防止同父节点的同层节点相同的值重复使用!
    history := make(map[int]struct{})
    for i := idx; i < len(nums); i++ {
        // 防止重复使用
        _, exist := history[nums[i]]
        if exist {
            continue
        }
        if len(tmp) == 0 || (len(tmp) > 0 && nums[i] >= tmp[len(tmp)-1]) {
            // nums 元素递增且没有出现过
            tmp = append(tmp, nums[i])    // 加入
            history[nums[i]] = struct{}{} // 加入标记
            /**
            --------------  bug 记录 ----------------
            backTracking(nums, tmp, idx+1, res)
            下一层应该从遍历到的子节点的下一个开始,而不是从父节点的下一个开始!!!!
            */
            backTracking(nums, tmp, i+1, res) // 递归
            tmp = tmp[:len(tmp)-1]            // 回溯
            /**
            --------------  bug 记录 ----------------
            delete(history, (*nums)[i])   不能删除标记,因为标记的作用就是在当前层中不要找值相等的元素
            */
        }
    }
}

40 组合总和 II

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意: 解集不能包含重复的组合。

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出: [[1,1,6],[1,2,5],[1,7],[2,6]]

注意点:

  • 题中已经明确解集不能包含重复的组合,所以必须进行跨层去重!

  • 既然跨层需要去重,那么同层必然也需要去重,相同值的子节点不能重复加入。

  • 跨层去重采取 map[string] 来去重,将数组 排序+输出 为字符串形式,然后进行去重。

  • 同层去重就使用 map[int] 即可。

  • 注意看数组是怎么输出为字符串形式的。

// 递归+回溯求解
func combinationSum2(candidates []int, target int) [][]int {
    res := make([][]int, 0)
    subRes := make([]int, 0)
    history := make(map[string]struct{}) // 跨层不能得出相同的值
    backTracking_comb(subRes, 0, &res, history, candidates, target)
    return res
}

func backTracking_comb(subRes []int, idx int, res *[][]int, history map[string]struct{}, candidates []int, target int) {
    sum := 0
    for i := 0; i < len(subRes); i++ {
        sum += subRes[i]
    }
    if sum == target {
        tmp := make([]int, len(subRes))
        copy(tmp, subRes)
        // 排序
        sort.Slice(tmp, func(i, j int) bool {
            return tmp[i] < tmp[j]
        })
        // 编码
        s := encode(tmp)
        // 查看是否存在
        if _, exists := history[s]; !exists {
            history[s] = struct{}{}
            *res = append(*res, tmp)
        }
    }

    subHistory := make(map[int]struct{}) // 同一层不能得出相同的值
    for i := idx; i < len(candidates); i++ {
        if _, exists := subHistory[candidates[i]]; exists {
            continue
        }
        if candidates[i]+sum > target { // 加入之后超限了
            continue
        }
        subRes = append(subRes, candidates[i])
        subHistory[candidates[i]] = struct{}{}
        backTracking_comb(subRes, i+1, res, history, candidates, target)
        subRes = subRes[:len(subRes)-1]
    }
}

func encode(tmp []int) string {
    return fmt.Sprintf("%v", tmp)
}

131. 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

输入: s = "aab"
输出: [["a","a","b"],["aa","b"]]

注意点:

  • 同层和跨层都不会产生相同的结果,所以不需要进行去重

  • 重点在划分的时候,边界怎么控制的,这里是 idx 为边界,从 idx 开始划分,idx 是闭区间的。一直划分到 i,这里 i 也是闭区间的,因此 i <= length-1。然后下次递归从 i + 1 开始划分。

  • idx 如果等于 length,说明划分结束了,直接加入结果即可。

func partition(s string) [][]string {
	res := make([][]string, 0)
	subRes := make([]string, 0)
	backTracking_part(subRes, 0, &res, []byte(s))
	return res
}

// idx: 从idx开始分割,idx处的元素未分割,idx最后的值为 (length - 1) + 1
func backTracking_part(subRes []string, idx int, res *[][]string, originalStr []byte) {
	if idx == len(originalStr) {
		tmp := make([]string, len(subRes))
		copy(tmp, subRes)
		// 跨层应该也不能产生相同的结果
		*res = append(*res, tmp)
	}

	// 同层不可能产生相同结果
	for i := idx; i < len(originalStr); i++ {
		if !judge(originalStr[idx : i+1]) { // [idx, i] (i <= length-1)
			continue
		}
		subRes = append(subRes, string(originalStr[idx:i+1]))
		backTracking_part(subRes, i+1, res, originalStr)
		subRes = subRes[:len(subRes)-1]
	}
}

func judge(s []byte) bool {
	if len(s) == 1 {
		return true
	}
	if len(s) == 0 {
		return false
	}
	i := 0
	j := len(s) - 1
	for i < j {
		if s[i] != s[j] {
			return false
		}
		i++
		j--
	}
	return true
}
posted @ 2022-12-10 15:29  kohn  阅读(25)  评论(0)    收藏  举报