day30|回溯法6-总结+N皇后问题

332.重新安排行程

51. N皇后

personal thinking

我的思路就是依次记录每一层的值,然后根据函数实现对应判断逻辑和递归逻辑。

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        # 问题:怎么判断下边和上边有不再下边或者上边的元素
        # 树的深度为n
        depth = 0
        result = []
        path = []
        ans = []
        temp = ['.'] * n
        def location(i):
            temp1 = temp.copy()
            temp1[i] = 'Q'
            return ''.join(temp1)
        def isValid(row,col):
            # 生成不符合要求的位置:
            not_valid = []
            depth = 0
            while depth < row:
                not_valid.append(ans[depth][1]) #获取列的坐标
                not_valid.append(ans[depth][1]+(row-depth))
                not_valid.append(ans[depth][1]-(row-depth))
                depth += 1
            if col in not_valid:
                return True
            return False

        def backtracking(depth):
            if depth == n: 
                result.append(path.copy())
                return
            for i in range(0,n): 
                if ans and isValid(depth,i):
                    continue
                path.append(location(i))
                ans.append((depth,i))
                depth += 1
                backtracking(depth)
                path.pop()
                ans.pop()
                depth -= 1
        if n == 1:
            return [["Q"]]
        backtracking(0)
        return result

其他题解

  • 利用二位数组作为结果的产出
  • 回溯的时候将pop改成改变二维数组的数值即可
  • 判断的时候可以分别对左上右上和同列的数值进行判断,从而得到valid的数值
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        # 思想利用二维数组进行数据的存储和修改
        if not n: return []
        board = [['.'] * n for _ in range(n)]
        res = []
        def isVaild(board,row, col):
            #判断同一列是否冲突
            for i in range(len(board)):
                if board[i][col] == 'Q':
                    return False
            # 判断左上角是否冲突,看对应的数值部分是否有冲突即可
            i = row -1
            j = col -1
            while i>=0 and j>=0:
                if board[i][j] == 'Q':
                    return False
                i -= 1
                j -= 1
            # 判断右上角是否冲突
            i = row - 1
            j = col + 1
            while i>=0 and j < len(board):
                if board[i][j] == 'Q':
                    return False
                i -= 1
                j += 1
            return True

        def backtracking(board, row, n):
            # 如果走到最后一行,说明已经找到一个解
            if row == n:
                temp_res = []
                for temp in board:
                    temp_str = "".join(temp)
                    temp_res.append(temp_str)
                res.append(temp_res)
            for col in range(n):
                if not isVaild(board, row, col):
                    continue
                board[row][col] = 'Q' 
                backtracking(board, row+1, n)
                board[row][col] = '.' # 回溯的话将原来的数值改回.即可,格局打开一些
        backtracking(board, 0, n)
        return res

37. 解数独

回溯算法总结

什么是回溯算法?

回溯算法的本质其实就是暴力搜索,并不是高效的算法,只不过通过剪枝可以减少运行的时间。

回溯法通常能够解决什么问题?

组合问题:N个数里面按一定规则找出k个数的集合
排列问题:N个数按一定规则全排列,有几种排列方式
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
棋盘问题:N皇后,解数独等等

回溯法解决问题的模板?

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

在该模板中在不同的问题中,需要更改的地方

  • 终止条件是什么
  • 是否需要递归返回startIndex值
  • 递归返回的startIndex是当前结点还是下一个结点
  • 结果记录的函数应该在哪里出现
  • for循环中if-continue剪枝操作的条件,防止层序重复值的出现。
    关键还是具体问题,具体分析,分析当前问题属于什么种类,做出树形结构是什么样的,怎么进行判断和剪枝

利用树形图像更加直观的理解回溯法:

在这里插入图片描述

分不同的问题,如何使用递归的方法进行求解?

组合总和问题:

  1. 问题1:给定k和n,求解列表中对应和的集合。
    剪枝:已选元素总和如果已经大于n(题中要求的和)了,那么往后遍历就没有意义了,直接剪掉
  2. 问题2:只有总和限制没有数量限制
    如果是一个集合来求组合的话,就需要startIndex;如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex
  3. 问题3:集合中元素会有重复但是求解集中不能包含重复的组合
    建立临时数组used
    在这里插入图片描述
    切割问题:
    难点:如何模拟切割线;切割问题中如何递归终止;如何截取子串;如何判断回文
    突破点:利用求解组合问题的方式来求解切割问题
    本题还有细节,例如:切割过的地方不能重复切割所以递归函数需要传入i + 1

子集问题:
在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果

排列问题:
排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方
在排列问题中需要注意以下两种方式:每层都是从0开始搜索而不是startIndex;需要used数组记录path里都放了哪些元素了
注意树枝去重和数层去重的区别。

去重问题:利用全局变量used进行去重;利用set集合进行去重。

回溯问题的时空复杂度情况

子集问题分析:
时间复杂度:O(2n),因为每一个元素的状态无外乎取与不取,所以时间复杂度为O(2n)
空间复杂度:O(n),递归深度为n,所以系统栈所用空间为O(n),每一层递归所用的空间都是常数级别,注意代码里的result和path都是全局变量,就算是放在参数里,传的也是引用,并不会新申请内存空间,最终空间复杂度为O(n)

排列问题分析:
时间复杂度:O(n!),这个可以从排列的树形图中很明显发现,每一层节点为n,第二层每一个分支都延伸了n-1个分支,再往下又是n-2个分支,所以一直到叶子节点一共就是 n * n-1 * n-2 * … 1 = n!。
空间复杂度:O(n),和子集问题同理。
组合问题分析:

时间复杂度:O(2^n),组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。
空间复杂度:O(n),和子集问题同理。
N皇后问题分析:

时间复杂度:O(n!) ,其实如果看树形图的话,直觉上是O(n^n),但皇后之间不能见面所以在搜索的过程中是有剪枝的,最差也就是O(n!),n!表示n * (n-1) * … * 1。
空间复杂度:O(n),和子集问题同理。
解数独问题分析:

时间复杂度:O(9^m) , m是’.'的数目。
空间复杂度:O(n2),递归的深度是n2

在这里插入图片描述
其他注意点:

  1. 在记录结果时注意进行copy():result.append(path.copy())
  2. 在一些问题中需要先对原始列表进行排序:nums = sorted(nums)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值