二刷回溯算法

二刷回溯

0、前言

二刷回溯主要是对第一次回溯的题补,第一次只是写了8、9道,当然不是回溯的全貌,算是巩固,题目参考主要是代码随想读的目录如下

学长推荐题目+代码随想录:

请添加图片描述

1、77. 组合

给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

我直接用了20分钟不到,写出来了,第一次完全靠自己写出来,感动!!!

func combine(n int, k int) [][]int {
    //k是结束条件
    list := make([]int, 0)
    res := make([][]int, 0)
    backtrack(1,n,k,list,&res)
    return res 
}
func backtrack(Index,n,k int, list []int, res *[][]int) {
    //结束条件,list长度=k
    if len(list) == k {
        ans := make([]int, len(list))
        copy(ans, list)
        *res = append(*res, ans)
        return
    }
    for i := Index; i <= n; i++ {
        list = append(list, i)
        backtrack(i+1,n,k,list,res)
        list = list[:len(list)-1]
    }
}
2、40. 组合总和 II

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

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

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

难点在于剪枝
第一次写的时候想的是做一个vis变量,为[]int类型,用0,1代表这一树层是否使用了同意元素,不过失败了
看了代码回想录的,发现他用的是map[int]bool做标记,按理说我们都是一个思路,不过是类型不同应该是要可以通过的,所以我唯一的不同就是这个点
不过第二次的时候我又用[]int,是成功了。

func combinationSum2(candidates []int, target int) [][]int {
    sort.Ints(candidates)
    list := make([]int, 0)
    history := make([]bool,len(candidates))
    res := make([][]int, 0)
    backtrack(0,target, list, candidates, history, &res)
    return res
}
func backtrack(Index,target int, list,candidates []int, history []bool, res *[][]int){
    //target是结束条件
    sum := 0
    for _,v := range list {
        sum += v
    }
    if sum == target {
        ans := make([]int, len(list))
        copy(ans, list)
        *res = append(*res, ans)
        return
    }else if sum > target {
        return
    }
    for i := Index; i < len(candidates); i++ {
        //要怎么去重的同时拿到1 1 6,,难点是-剪枝
        if i >= 1 && candidates[i] == candidates[i-1] && history[i-1] == false{
            continue
        }
        list = append(list, candidates[i])
        history[i] = true
        backtrack(i+1, target, list, candidates, history, res)
        history[i] = false
        list = list[:len(list)-1]
    } 
}
3、 216. 组合总和 III

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

只使用数字1到9
每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

比较简单,15分钟就写出来了

func combinationSum3(k int, n int) [][]int {
    //k是结束条件,n用来判断是不是
    list := make([]int, 0)
    res := make([][]int, 0)
    backtrack(1,n,k,list,&res)
    return res
}
func backtrack(Index,n,k int, list []int, res *[][]int){

    if len(list) == k{
        sum := 0
        for _,v := range list {
            sum += v
        }
        if sum == n {
            ans := make([]int, len(list))
            copy(ans, list)
            *res = append(*res, ans)
            return
        }
    }
    for i := Index; i <= 9; i++ {
        list = append(list, i)
        backtrack(i+1, n, k, list, res)
        list = list[:len(list)-1]
    }
}
4、51. N 皇后

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

我写的:

我先想了一个思路,然后去看代码回想录,和我想的一样,我取巧了,直接看了她的写的,不过都理解
但也有一点别的收获
他把res设为了全局变量,这样就连&和*都不需要了

收获:

全局的声明:var res [][]string

函数内初始化:res = [][]string{}

这样就不需要取值操作了——就是&,* 这些个

func solveNQueens(n int) [][]string {
    res := make([][]string, 0)
    board := make([][]string, n)
  	//下边就是把数组空的位置都初始化为".",代表空
    for i := 0; i < n; i++ {
        board[i] = make([]string, n)
    }
    for i := 0; i < n; i++{
        for j := 0; j < n; j++ {
            board[i][j] = "."
        }
    }
    backtrack(board, 0, &res)
    return res
}
func backtrack(board [][]string, row int, res *[][]string) {
    size := len(board)
    //结束条件是到board,就是到最后一行也添加皇后了
    if row == size {
        ans := make([]string, size)
        for i := 0; i < size; i++ {
            ans[i] = strings.Join(board[i], "")
        }
        *res = append(*res, ans)
        return
    }
    for col := 0; col < size; col++ {
        if !isValid(board, row, col){
            continue
        }
        board[row][col] = "Q"
        backtrack(board, row+1, res)
        board[row][col] = "."
    }
}
func isValid(board [][]string, row,col int) (res bool){
    n := len(board)
    //判断这一列上下行是否有Q
    for i := 0; i < row; i++ {
        if board[i][col] == "Q" {
            return false
        }
    }
    //判断这一行左右列是否有Q
    for i := 0; i < col; i++ {
        if board[row][i] == "Q" {
            return false
        }
    }
  	//为什么不判断左下线和右下线的原因很简单,因为下边的行还没有给皇后,所以不需要判断
    //判断左上线是否有Q
    for i, j := row, col; i >= 0 && j >= 0; i, j =  i-1, j-1 {
        if board[i][j] == "Q" {
            return false
        }
    }
    //判断右上线是否有Q
    for i, j := row, col; i >= 0 && j < n; i,j = i-1,j+1 {
        if board[i][j] == "Q" {
            return false
        }
    }
    return true
}
5、37. 解数独

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则:

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

提示:

board.length == 9
board[i].length == 9
board[i][j] 是一位数字或者 ‘.’
题目数据 保证 输入数独仅有一个解

我第一次只能写到如下这么多了

//做一个二维的字典或者数组,方便第三个判断,0-02,1-35,2-68或者0-13,1-46,2-79,
jgg := [3][2]{
    {0,2},
    {3,5},
    {6,8},
}
func solveSudoku(board [][]byte)  {
    list := make([]int, 0)
    backtrack(list []int, &board)
    return board
}
func backtrack(board *[][]byte) {
    //到第九行结束,一行一行的填入,每一行要填入9个数字
    //记住,这个数独只有唯一一个解
    if Index == 9 {
        ans := make([]int, len(list))
        copy(ans, list)
        *board
    }
    for i := 0; i <= 9; i++ {
        //我的难点就是不知道怎么去知道这个行,列,然后去嗯...给他整上数字
    }
}

//判断所填入数字是否满足条件
func mz(board *[][]byte, shu,row,col int) bool{
    //左右这个数没出现过,i不变,j变,情况下,可以添加这个数字
    for j := 0; j < 9 ; j++ {
        if board[row][j] == shu {
            return false
        }
    }
    //上下这个数没出现过, i变,j不变
    for i := 0; i < 9; i++ {
        if board[i][col] == shu {
            return false
        }
    }
    //判断他所在区域的9个格子是否出现-除以三-判断是第几个格子
    h := row/3
    l := col/3
    for i := jgg[h][0]; i <= jgg[h][1] {
        for j := jgg[l][0]; j <= jgg[l][1]{
            if board[i][j] == shu {
                return false
            }
        }
    }
    return true
}

看题解理解后如下啊:

思路:

  1. 第一步咱们很清楚的知道,需要判断加入的数是否满足条件,所以写了mz函数,感觉我判断格子的方法有点臃肿了
  2. 好,开始正题,进入backtrack
  3. 为什么这次没有和solveSudoku分开单独写一个呢,在于这个题目人家不要返回值,而且填写的是board,二维的,咱要是在分开的backtrack函数用*[][]int,其实,传不进去单个值,所以直接在solveSudoku函数先声明,再初始化,再使用
    4.好了开始说说初始化,也就是backtrack的主体,首先是这个双重for循环,记得水仙花数,打印图形吗,这类的题目就是用了双重for循环来代表要添加的元素所在的行列
  4. 接着判断不是’.'的不需要变成数字,跳过当前循环,进入下一层
  5. 重重点开始
  6. 首先是’1’-'9’是选择列表,但是因为board是[][]byte类型,在你追加的时候需要类型转换byte(shu),判断是否满足mz的传参也需要
  7. 接着是循环,不过这里用了if,多了一层判断,为真就return true
  8. 接着回溯,都知道
  9. 接着下边又有两个return,说实话,我还不太理解这三个return到底有什么妙用
  10. 关于方格的判断条件啊,还是人家代码随想录写得好,值得看看,不过别担心,不难理解
func solveSudoku(board [][]byte)  {
    var backtrack func(board [][]byte) bool
    backtrack = func(board [][]byte) bool {
        //双重for循环,代表行列
        for  i := 0; i < 9; i++ {
            for j := 0; j < 9; j++ {
                //只有当前位置为.的时候才可以添加数字,否则跳过这个数
                if board[i][j] != '.'{
                    continue
                }
                //重点,填写1-9是选择列表,
                for shu := '1'; shu <= '9'; shu++ {
                    if mz(board,byte(shu),i,j) == true {
                        //追加
                        board[i][j] = byte(shu)
                        //循环
                        if backtrack(board) == true {
                            return true
                        }
                        //回溯
                        board[i][j] = '.'
                    }
                }
                return false
            }
        }
        return true
    }
    backtrack(board)
}
func backtrack(board *[][]byte) {

}

//判断所填入数字是否满足条件
func mz(board [][]byte, shu byte, row,col int) bool {
    //做一个二维的字典或者数组,方便第三个判断,0-02,1-35,2-68或者0-13,1-46,2-79,
    jgg := [3][2]int{
        {0,2},
        {3,5},
        {6,8},
    }
    //左右这个数没出现过,i不变,j变,情况下,可以添加这个数字
    for j := 0; j < 9 ; j++ {
        if board[row][j] == shu {
            return false
        }
    }
    //上下这个数没出现过, i变,j不变
    for i := 0; i < 9; i++ {
        if board[i][col] == shu {
            return false
        }
    }
    //判断他所在区域的9个格子是否出现-除以三-判断是第几个格子
    h := row/3
    l := col/3
    for i := jgg[h][0]; i <= jgg[h][1]; i++ {
        for j := jgg[l][0]; j <= jgg[l][1]; j++{
            if board[i][j] == shu {
                return false
            }
        }
    }
    return true
}

这个是代码随想录的

func solveSudoku(board [][]byte)  {
    var backtracking func(board [][]byte) bool
    backtracking=func(board [][]byte) bool{
        for i:=0;i<9;i++{
            for j:=0;j<9;j++{
                //判断此位置是否适合填数字
                if board[i][j]!='.'{
                    continue
                }
                //尝试填1-9
                for k:='1';k<='9';k++{
                    if isvalid(i,j,byte(k),board)==true{//如果满足要求就填
                        board[i][j]=byte(k)
                       if  backtracking(board)==true{
                           return true
                       }
                        board[i][j]='.'
                    }
                }
                return false
            }
        }
        return true // 这三个return都让人很不理解啊啊啊啊
    }
    backtracking(board)
}
//判断填入数字是否满足要求
func isvalid(row,col int,k byte,board [][]byte)bool{
    for i:=0;i<9;i++{//行
        if board[row][i]==k{
            return false
        }
    }
    for i:=0;i<9;i++{//列
        if board[i][col]==k{
            return false
        }
    }
    //方格
    startrow:=(row/3)*3
    startcol:=(col/3)*3
    for i:=startrow;i<startrow+3;i++{
        for j:=startcol;j<startcol+3;j++{
            if board[i][j]==k{
                return false
            }
        }
    }
    return true
}
6、6189. 按位与最大的最长子数组

**说明:**这个不用看了,就是我当时参加周赛遇到的一题目,写错了

给你一个长度为 n 的整数数组 nums

考虑 nums 中进行 **按位与(bitwise AND)**运算得到的值 最大非空 子数组。

  • 换句话说,令 knums 任意 子数组执行按位与运算所能得到的最大值。那么,只需要考虑那些执行一次按位与运算后等于 k 的子数组。

返回满足要求的 最长 子数组的长度。

数组的按位与就是对数组中的所有数字进行按位与运算。

子数组 是数组中的一个连续元素序列。

============================================================================================================================================================

这道题是312场周赛一道题,我写错了,呜呜呜

func longestSubarray(nums []int) int {
    //先拿到所有子数组,对子数组按位与,找到最大值,返回这个子数组有几个数
    //需要一个数组存储,按位与得到的结果吗?
    
    jg := make([]int, 0) // 用来存放和res相一致顺序的子数组的按位与结果
    list := make([]int, 0)
    res := make([][]int, 0)
    backtrack(1,nums,&jg,list,&res)
    zui := 0
    zui_xb := 0
    for i := 0; i < len(jg); i++ {
        if jg[i] > zui {
            zui = jg[i]
            zui_xb = i
        }
    }
    return len(res[zui_xb])
}
func backtrack(Index int, nums []int, jg *[]int, list []int, res *[][]int){
    
    ans := make([]int, len(list))
    copy(ans, list)
    *res = append(*res, ans)
    
    awjg := 0
    if len(list) == 1 {
        awjg = list[0]
    }else if len(list) == 0 {
        awjg = 0   
    }else {
        for i := 1; i < len(ans); i++ {
            ans[i] = ans[i] & ans[i-1]
        }
        awjg = ans[len(ans)-1]
    }
    *jg = append(*jg, awjg)
    
    for i := 0; i < len(nums); i++ {
        list = append(list, i)
        backtrack(i+1,nums,jg,list,res)
        list = list[:len(list)-1]
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不之道

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

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

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

打赏作者

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

抵扣说明:

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

余额充值