回溯算法——好的开始

0、前言和模板

前言:

第一次刷回溯算法,大概刷了8,9道

说真,起初以为上来就怼回溯会不会起点太高,后来,发现按照类型刷算法题真的会比一会一个二叉树一个贪心,一个动态好,毕竟会有一种拿捏感成就感

实践费曼学习法——咱先去理论学习了两个小时,基本在理论上拿捏了回溯,目前感觉回溯不就是个递归吗,只是给他增加了别的东西而已,最本质的就是递归

关于理论学习部分,看这个就够了回溯法,动态规划,贪心的区别

然后是一个好的刷题顺序,这部分我是从这个回溯刷题开始开始,有八道题,目前已经刷完,算是一个循序渐进的排序,包含了经典的子集问题,全排列问题等

当然了你可以直接看我的就行

下一个阶段打算跟着代码随想录的的刷题顺序刷题,但是不打算看他的长篇大论了

一点总结:

回溯算法,自上而下,遍历枚举每一种过程,从而判断是否满足

动态规划,自下而上,从已知的结果往上追溯,从而判断

贪心算法,这个根据经验做,比较难,是从局部最优得到整体最优结

请添加图片描述

回溯算法模板:

result = []
func backtrack(选择列表,路径)
	if 满足条件
		result.add(路径)
		return
  for 选择 in 选择列表
    做选择
    backtrack(选择列表,路径)
    撤销选择
1、子集问题

子集问题一:

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

func subsets(nums []int) [][]int {
    result := make([][]int, 0)
    list := make([]int, 0)
    backtrack(nums, 0, list, &result)
    return result
}
func backtrack(nums []int, pos int, list []int, result *[][]int){
    ans := make([]int,len(list))
    copy(ans,list)
    *result = append(*result,ans)
    for i := pos; i < len(nums); i++ {
        list = append(list,nums[i]) // 所谓选择就是树下走一步
        backtrack(nums, i+1, list, result) // 所谓处理就是对,backtrack函数里的值更新
        list = list[0 : len(list)-1] // 所谓撤销,就是使得中间list回到原来状态
    }
}

子集问题二:

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

func subsetsWithDup(nums []int) [][]int {
    result := make([][]int, 0)
    list := make([]int, 0)
    sort.Ints(nums) // 多了个排序,为了后续去重需要
    backtrack(nums,0,list,&result)
    return result
}
func backtrack(nums []int, pos int, list []int, result *[][]int){
    ans := make([]int, len(list))
    copy(ans,list)
    *result = append(*result, ans)
    for i := pos; i < len(nums); i++ {
        if i != pos && nums[i] == nums[i-1] { // 做到了去重,例如当pos为0时候,就不要1 2 2这个了,因为后边会再次出现一次。
            continue
        }
        list = append(list,nums[i])
        backtrack(nums,i+1,list,result)
        list = list[0 : len(list)-1]
    }
}
2、全排列问题

全排列问题一:

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

一定要带入实例,刚开始会绕的晕晕的,但是多晕几次,就会一次比一次清楚。

func permute(nums []int) [][]int {
    result := make([][]int, 0)
    list := make([]int, 0)
    visited := make([]bool, len(nums))
    backtrack(nums,visited,list,&result)
    return result
}
func backtrack(nums []int, visited []bool, list []int, result *[][]int){
    if len(list) == len(nums) {
        ans := make([]int, len(list))
        copy(ans,list)
        *result = append(*result, ans)
        return
    }
    
    for i := 0; i < len(nums); i++ {
        if visited[i] {
            continue
        }
            list = append(list, nums[i])
        visited[i] = true
        backtrack(nums, visited, list, result)
        visited[i] = false
        list = list[0 : len(list)-1]
    }
}

全排列问题二:

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

func permuteUnique(nums []int) [][]int {
    result := make([][]int, 0)
    list := make([]int, 0)
    visited := make([]bool, len(nums))
    sort.Ints(nums)
    backtrack(nums, visited, list, &result)
    return result
}
func backtrack(nums []int, visited []bool, list []int, result *[][]int){
    if len(list) == len(nums) {
        ans := make([]int, len(list))
        copy(ans,list)
        *result = append(*result,ans)
        return
    }
    for i := 0; i < len(nums); i++ {
        if visited[i] {  // 不要忘了,注意位置
            continue
        }
        if i != 0 && nums[i] == nums[i-1] && !visited[i-1] {   // 注意条件
            continue
        }
        list = append(list,nums[i])
        visited[i] = true
        backtrack(nums, visited, list, result)
        visited[i] = false
        list = list[0 : len(list)-1]
    }
}
3、提升

39. 组合总和

func combinationSum(candidates []int, target int) [][]int {
    result := make([][]int, 0)
    list := make([]int, 0)
    backtrack(0, 0, target, candidates, list, &result)
    return result
}
func backtrack(startIndex,sum,target int, candidates,list []int, result *[][]int){
    //目标_自下向上
    if sum  == target {
        ans := make([]int, len(list))
        copy(ans, list)
        *result = append(*result,ans)
        return
    }
    if sum > target {return}
    // 开始正主
    for i := startIndex; i < len(candidates); i++ {
        list = append(list, candidates[i])
        sum += candidates[i]
        backtrack(i, sum, target, candidates,list,result)
        list = list[0 : len(list)-1]
        sum -= candidates[i]
    }
}

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

func letterCombinations(digits string) []string {
    // 先把数字变成字母数组。
    length := len(digits)
    if length == 0{
        return nil
    }
    digitsMap := [10]string {
        "",     //0
        "",     //1
        "abc",  //2
        "def",  //3
        "ghi",  //4
        "jkl",  //5
        "mno",  //6
        "pqrs", //7
        "tuv",  //8
        "wxyz", //9
    }
    result := make([]string, 0)
    list := "" //中间暂存值
    backtrack(0, list, digits, digitsMap, &result)
    return result
}
func backtrack(Index int, list,digits string, digitsMap [10]string, result *[]string){
    // Index代表digits的当前下标
    if len(list) == len(digits) {
        // ans string
        // copy(ans, list)
        *result = append(*result, list)
        return
    }
    listK := digits[Index] - '0'
    dm := digitsMap[listK]
    for i := 0; i < len(dm); i++ {
        list = list + string(dm[i])
        backtrack(Index+1,list,digits,digitsMap,result) //index代表下一个选择
        list = list[ : len(list)-1]
    }
}

131. 分割回文串

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

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

func partition(s string) [][]string {
    //判断空
    // if len(s) == 0 {
    //     return nil
    // }
    // 判断条件变成了-是否回文
    result := make([][]string, 0)
    list := make([]string, 0)
    backtrack(0, s, list, &result)
    return result
}
//需要一个字符串变量
func backtrack(Index int, s string, list []string, result *[][]string){
    //结束条件看最后一个字符是否用了_出现最后一个字符
    if Index == len(s) {
        ans := make([]string, len(list))
        copy(ans, list)
        *result = append(*result, ans)
        //return
    }
    //选择列表是下一个字符,回文的方法是关键
    for i := Index; i < len(s); i++ {
        if huiwen(s,Index,i) {
            //是回文的话,就确定切割区间切割
            list = append(list,s[Index:i+1])
            backtrack(i+1,s,list,result) //记着,是i+1不是index+1
            list = list[:len(list)-1]
        }
    }
}
// 判断一个字符串是否为字符串,和我写的不一样
func huiwen(s string,Index,end int) bool {
    left := Index
    right := end
    for ; left < right; {
        if s[left] != s[right]{
            return false
        } 
        //移动左右指针
        left++
        right--
    }
    return true
}

93、复原 IP 地址

代码随想录的

func restoreIpAddresses(s string) []string {
	var res,path []string
	backTracking(s,path,0,&res)
	return res
}
func backTracking(s string,path []string,startIndex int,res *[]string){
	//终止条件
	if startIndex==len(s)&&len(path)==4{
		tmpIpString:=path[0]+"."+path[1]+"."+path[2]+"."+path[3]
		*res=append(*res,tmpIpString)
	}
	for i:=startIndex;i<len(s);i++{
		//处理
		path:=append(path,s[startIndex:i+1])
		if i-startIndex+1<=3&&len(path)<=4&&isNormalIp(s,startIndex,i){
			//递归
			backTracking(s,path,i+1,res)
		}else {//如果首尾超过了3个,或路径多余4个,或前导为0,或大于255,直接回退
			return
		}
        //回溯
		path=path[:len(path)-1]
	}
}
func isNormalIp(s string,startIndex,end int)bool{
	checkInt,_:=strconv.Atoi(s[startIndex:end+1])
	if end-startIndex+1>1&&s[startIndex]=='0'{//对于前导 0的IP(特别注意s[startIndex]=='0'的判断,不应该写成s[startIndex]==0,因为s截取出来不是数字)
		return false
	}
	if checkInt>255{
		return false
	}
	return true
}

第一次自己写,写成如下,感觉很接近了

func restoreIpAddresses(s string) []string {
    result := make([]string, 0)
    backtrack(0, 0, "", s, &result)
    return result
}
func backtrack(Index,ds int,list,s string, result *[]string) {
    if len(list) == len(s)+3 && ds == 3{ //结束条件,就是用了s的最后一个字符
        *result = append(*result, list)
    }
    //点数只能有三个
    for i := Index; i < len(s) && ds <= 3; i++ {
        //整数,没有前导0,没有其他符号,不能大于255,
        // 这个切割区间,前导不为零,继续下层bacetrack(特例子,只有一位数手,不算是前导)
        // 思考如何找到这个区间用于判断
        str := s[Index:i+1]
        s1, _ := strconv.Atoi(str)

        if s1 > 255 || s1 < 0{ // 有效区间是0~255,不满足的话直接跳过这个情况
            break
        }
        //前置不为零时候,长度是大于1的,当只有一个数是0的情况也满足
        if (str[0] != 0 && len(str) > 1) || len(str) == 1 { 
            list = list + str
            if len(list) < len(s)+3 && ds < 3{
                list += "."
                ds++
            }
            backtrack(i+1, ds, list, s, result)
            list = list[:len(list)-1] // 应该不只是len-1了
        }
    }
}
//果然,回溯法最难的还是,判断条件的难找啊

看过代码随想录的再去改正,发现及其相似了,唯独最关键的path就是我的list没有用[]string类型,导致越想越头疼。真可谓点睛之笔,但是为啥我就没想出来呢。

func restoreIpAddresses(s string) []string {
    list := make([]string, 0) // 作为路径,就是中间暂存值
    result := make([]string, 0)
    backtrack(0,list,s,&result)
    return result
}
func backtrack(Index int,list []string,s string, result *[]string) {
    if Index == len(s) && len(list) == 4{ //结束条件,就是用了s的最后一个字符 list长度只能为4
        ans := list[0] + "." + list[1] + "." + list[2] + "." + list[3]
        *result = append(*result, ans)
    }
    for i := Index; i < len(s); i++ {
        //前置不为零时候,长度是大于1的,当只有一个数是0的情况也满足
        list := append(list, s[Index:i+1])
        //数字截取的有效长度为3, list路径<=4个,正常IP,
        if i-Index+1 <= 3 && len(list) <= 4 && isIP(s,Index,i) { 
            backtrack(i+1, list, s, result)
        }else {
            //否则,这种不存在直接return这种结果,不必再执行for循环
            return
        }
        list = list[:len(list)-1]
    }
}
//判断ip条件,函数抽离
func isIP (s string, startIndex,end int) bool {
    str := s[startIndex:end+1]
    s1, _ := strconv.Atoi(str) //转化为数字了
    //当这个数,长度大于一位,前导不能为零,不能大于255
    if end - startIndex+1 > 1 && str[0] == '0' {
        return false
    }
    if s1 > 255 {
        return false
    }
    return true
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不之道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值