递归,回溯,剪枝

对递归的理解很浅,经常困在底层递归中,有推荐看sicp前两章的
记录下题目,斐波那契就跳过了

递归汉诺塔

题目链接

  • 递归
func hanota(A []int, B []int, C []int) []int {
    n:=len(A)
    dfs(&A,&B,&C,n)
    return C
}

//递归函数,a通过b向c移动n个数据
func dfs(a,b,c *[]int,n int){
    if n==1{
        *c = append(*c,(*a)[len(*a)-1])
        *a = (*a)[:len(*a)-1] //截断不包含后面的一个
    }
    if n>1{
        //n-1 通过c放b,1个底层放c,n-1通过a放c
        dfs(a,c,b,n-1)
        dfs(a,b,c,1)
        dfs(b,a,c,n-1)
    }
}

39 组合总和

  • dfs+回溯剪枝
var res [][]int
var path []int
func combinationSum(candidates []int, target int) [][]int {
    res = make([][]int,0)
    path = make([]int,0)
    dfs(candidates,target,0,0)
    return res
}

func dfs(candidates []int,target int,index int,sum int){
    if sum>target{ 
        return
    }
    if sum==target{
        tmp:=make([]int,len(path)) //拷贝,直接放path在res,之后的path会改变,导致1最终结果错误
        copy(tmp,path)
        res = append(res,tmp)
        return
    }

    for i:=index;i<len(candidates);i++{
        sum+=candidates[i]
        path = append(path,candidates[i])
        dfs(candidates,target,i,sum)  //注意index为i
        path = path[:len(path)-1]
        sum-=candidates[i]
        
    }
}

40 组合总和2

题目链接
包含重复元素,可以使用重复,例如

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

需要1排序并设置used数组标识i位置是否已经使用,重复元素前面使用的情况下可以继续使用,如果前面false,则说明已经重复,不再使用
说的有点绕,可以参考

  • 回溯+剪枝
import "sort"
var res [][]int
var path []int

func combinationSum2(candidates []int, target int) [][]int {
    //只使用一次,相同元素第一个使用的情况可以继续使用,否则不能重复使用,排序+flag
    used:=make([]bool,len(candidates))
    res=make([][]int,0)
    path = make([]int,0)
    sort.Ints(candidates)
    
    dfs(candidates,target,0,0,used)
    return res
}

func dfs(candidates []int,target int,index int,sum int,used []bool){
    if sum>target{
        return 
    }
    if sum==target{
        tmp:=make([]int,len(path))
        copy(tmp,path)
        res = append(res,tmp)
    }

    for i:=index;i<len(candidates);i++{
        if i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false{ //此种方式是在同层进行剪枝,前一个使用过则继续使用新的重复元素
            continue
        }
        sum+=candidates[i]
        path = append(path,candidates[i])
        used[i]=true
        dfs(candidates,target,i+1,sum,used)
        used[i] = false
        path = path[:len(path)-1]
        sum-=candidates[i]
    }
}

216 组和总和3

和39类似,只是不同重复数字,并且范围到9

  • 回溯+剪枝
var res[][]int
var path []int
func combinationSum3(k int, n int) [][]int {
    res = make([][]int,0)
    path = make([]int,0)
    dfs(1,0,k,n)
    return res
}

func dfs(index int,sum int,k int,target int){
    if sum>target{
        return 
    }
    if sum==target&&len(path)==k{
        res = append(res,append([]int{},path...))
        return 
    }
    for i:=index;i<10;i++{
        sum+=i
        path = append(path,i)
        dfs(i+1,sum,k,target)
        path = path[:len(path)-1]
        sum-=i
    }
}

77 组合

题目链接

  • 类似上题
var res [][]int
var path []int
func combine(n int, k int) [][]int {
    //全排列 需要index
    res = make([][]int,0)
    path = make([]int,0)
    dfs(1,n,k)
    return res
}

func dfs(index int,n int,k int){
    if len(path)==k{
        tmp:=make([]int,len(path))
        copy(tmp,path)
        res = append(res,tmp)
        return 
    }

    for i:=index;i<=n;i++{
        path = append(path,i)
        dfs(i+1,n,k)
        path = path[:len(path)-1]
    }
}


46 全排列

  • 不含重复数字
var res [][]int
var path []int

func permute(nums []int) [][]int {
     //不需要index,used记录即可,因为排列1选完后面还可以选前面
    used:=make([]bool,len(nums))
    res = make([][]int,0)
    path = make([]int,0)
    dfs(nums,used)
    return res
}

func dfs(nums []int,used []bool){
    if len(path)==len(nums){
        tmp := make([]int,len(path))
        copy(tmp,path)
        res = append(res,tmp)
        return 
    }
    
    for i:=0;i<len(nums);i++{
        if used[i]{
            continue
        }
        
        path = append(path,nums[i])
        used[i] = true
        dfs(nums,used)
        used[i] = false
        path = path[:len(path)-1]
    }
}

47 全排列2

题目链接

  • 排序+used标记,前面使用过不再使用.
    因为没有index,需要对此次used[i]进行判断,避免重复选取

import "sort"
var res [][]int
var path []int

func permuteUnique(nums []int) [][]int {
     //包含重复元素,需要排序并且对前面的剪枝
    sort.Ints(nums)
    used:=make([]bool,len(nums))
    res = make([][]int,0)
    path = make([]int,0)
    dfs(nums,used)
    return res
}

func dfs(nums []int,used []bool){
    if len(path)==len(nums){
        tmp := make([]int,len(path))
        copy(tmp,path)
        res = append(res,tmp)
        return 
    }
    
    for i:=0;i<len(nums);i++{
        if i>0&&nums[i]==nums[i-1]&&used[i-1]==false{ //前一个没用则不能用此次的,否则重复,[1,1,2],前面1false,则第二个1开头就不用再次计算
            continue
        }
        //此次元素已经使用过则不再使用
        if used[i]==true{
            continue
        }
        path = append(path,nums[i])
        used[i] = true
        dfs(nums,used)
        used[i] = false
        path = path[:len(path)-1]
    }
}

784 字母大小写全排列

  • index递归,大小写转换,ascii,a-32=A
var  res []string
func letterCasePermutation(s string) []string {
    //不算全排列,转换之后进行判断
    path:=[]byte(s)
    res = make([]string,0)
    dfs(path,0)
    return res
}

func dfs(path []byte,index int){
    //每次更改都放入res
    res =  append(res,string(path))

    for i:=index;i<len(path);i++{
        //数字不需要转换
        if isNumber(path[i]){
            continue
        }
        //字母转换,之后恢复
        if isChar(path[i]){
            path[i]-=32 //大小写相差32,小写大
        }else{
            path[i]+=32
        }
        dfs(path,i+1)
        if isChar(path[i]){
            path[i]-=32
        }else{
            path[i]+=32
        }
    }
}

//判断是否是数字
func isNumber(str byte)bool{
    if str>='0'&&str<='9'{
        return true
    }else{
        return false
    }
}

//判断是否是字母,进行大小写转换
func isChar(str byte)bool{
    if str>='a'&&str<='z'{
        return true
    }else{
        return false
    }
}

78 子集

题目链接
不包含重复元素的子集

  • index 递归回溯
var res [][]int
var path []int
func subsets(nums []int) [][]int {
    //index递归,包含空集
    res = make([][]int,0)
    path = make([]int,0)
    dfs(nums,0)
    return res
}

func dfs(nums []int,index int){
    res = append(res,append([]int{},path...))

    for i:=index;i<len(nums);i++{
        path = append(path,nums[i])
        dfs(nums,i+1)
        path = path[:len(path)-1]
    }
}

90 子集II

包含重复元素,不能重复子集,

  • index+排序+used数组去重
import "sort"
var res [][]int
var path []int
func subsetsWithDup(nums []int) [][]int {
    //index递归,包含重复元素,需要排序+used数组去除
    res = make([][]int,0)
    path = make([]int,0)
    used:=make([]bool,len(nums))
    sort.Ints(nums)
    dfs(nums,0,used)
    return res
}

func dfs(nums []int,index int,used []bool){
    res = append(res,append([]int{},path...))

    for i:=index;i<len(nums);i++{
        if i>0&&nums[i-1]==nums[i]&&used[i-1]==false{
            continue
        }
        used[i]=true
        path = append(path,nums[i])
        dfs(nums,i+1,used)
        path = path[:len(path)-1]
        used[i]=false
    }
}

139 单词拆分(*)

判断是否单词是否能分解

  • dfs+记忆化
func wordBreak(s string, wordDict []string) bool {
    //dfs + 记忆化   
    // index 每次+1
    used := make(map[int]bool,len(s))
    hash:=make(map[string]bool)
    for _,v:=range wordDict{
        hash[v]=true
    }
    return dfs(s,0,hash,used)
}

//dfs 细节很多 
func dfs(s string,index int,hash map[string]bool,used map[int]bool)bool{
     if index==len(s){
         return true
     }
     if value,ok:=used[index];ok{ //有值则直接返回
         return value
     }
     for i:=index+1;i<=len(s);i++{   //==len(s) 不包含,
          str:=s[index:i] //取字符串
          if hash[str]&&dfs(s,i,hash,used){ //i会在下次循环+1
              used[i] = true  //记录方便复用
              return true
          }
     }
     used[index] = false//不存在也要标示
     return false
}


  • dp
    dp[i] 代表0到i-1字符串是否符合,dp[0]=true
func wordBreak(s string, wordDict []string) bool {
    //dp 
    //word放map 
    hash:=make(map[string]bool,0)
    for _,v:=range wordDict{
        hash[v]=true
    }

    dp:=make([]bool,len(s)+1)
    dp[0]=true
    for i:=1;i<=len(s);i++{   //还可以条件优化 dp[i]==true break.   dp[j]==false continue
        for j:=i-1;j>=0;j--{
            str:=s[j:i] //前段字符,从下表j到i-1
            if hash[str]&&dp[j]{ //dp[j] 为0到j-1,刚好覆盖
                dp[i] = true
            }
        }
    }
    return dp[len(s)]
}

140 单词拆分2 (**)

  • hash+dfs
func wordBreak(s string, wordDict []string) []string {
    // hash+dfs
    hash:=make(map[string]bool)
    for _,v:=range wordDict{
        hash[v]=true
    }
    var res []string
    var path []string
    var dfs func(index int)
    dfs = func(index int){
        if index==len(s){
            res = append(res,strings.Join(path," "))
            return 
        }
        for i:=index+1;i<=len(s);i++{
            if hash[s[index:i]]{ //符合才继续寻找,否则剪枝
                path = append(path,s[index:i])
                dfs(i)
                path = path[:len(path)-1]
            }
        }
    }
    dfs(0)
    return res
}

131 分割回文串(*)

dp和dfs结合的经典题目

  • dp+dfs
var path []string
var res [][]string
func partition(s string) [][]string {
    //dp+fds
    path =make([]string,0)
    res = make([][]string,0)

    dp:=make([][]bool,len(s))
    for i:=0;i<len(s);i++{
        dp[i] = make([]bool,len(s))
    }
    
    //判断是否回文,并且存储
    for r:=0;r<len(s);r++{
        for l:=0;l<=r;l++{
            if s[l]==s[r]&&(r-l<=2||dp[l+1][r-1]){
                dp[l][r] = true
            }
        }
    }
    dfs(s,0,dp)
    return res
}

func dfs(s string,index int,dp [][]bool){
    if index==len(s){
        tmp:=make([]string,len(path))
        copy(tmp,path)
        res = append(res,tmp)
        return 
    }

    for i:=index;i<len(s);i++{
        if dp[index][i]{ //回文则继续
            path = append(path,s[index:i+1]) //i不包含则应该+1才是正确范围
            dfs(s,i+1,dp)
            path = path[:len(path)-1]
        }
    }
}
posted @ 2021-09-18 20:48  海拉尔  阅读(63)  评论(0编辑  收藏  举报