开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 14 天,点击查看活动详情
作者: 千石
支持:点赞、收藏、评论
欢迎各位在评论区交流
前言
本文内容来自我平时学习的一些积累,如有错误,还请指正
在题目实战部分,我将代码实现和代码解释设置在了解题思路的下方,方便各位作为参考刷题
一些话
本文内容来自我平时学习的一些积累,如有错误,还请指正
在题目实战部分,我将代码实现和代码解释设置在了解题思路的下方,方便各位作为参考刷题
题目练习步骤:
- 给自己10分钟,读题并思考解题思路
- 有了思路以后开始写代码,如果在上一步骤中没有思路则停止思考并且看该题题解
- 在看懂题解(暂时没看懂也没关系)的思路后,背诵默写题解,直至能熟练写出来
- 隔一段时间,再次尝试写这道题目
前置知识
剪枝
剪枝是一种常用的算法优化技术,其本质是减少搜索空间,避免无效搜索,从而提高算法效率。在数据结构与算法中,剪枝常常用于搜索、动态规划等算法中。
剪枝的实现方式有很多种,以下是一些常用的剪枝技术:
- 回溯剪枝
回溯算法通常采用深度优先搜索策略,枚举所有可能的解,然后进行剪枝,排除一些不符合条件的解。回溯剪枝是一种剪枝技术,通过记录已经搜索过的状态,避免重复搜索,从而减少搜索次数。回溯剪枝常常用于求解排列、组合等问题。
- 动态规划剪枝
动态规划剪枝通常用于解决最优化问题。该技术通常采用备忘录或者状态转移方程等方式,记录已经计算过的结果,避免重复计算,从而提高算法效率。动态规划剪枝通常用于求解最短路径、最大连续子序列和等问题。
- 前缀和剪枝
前缀和剪枝通常用于解决区间和问题。该技术通过预处理前缀和数组,快速计算任意区间的和,避免重复计算,从而提高算法效率。前缀和剪枝常常用于求解子数组和等问题。
- 启发式剪枝
启发式剪枝通常采用一些启发式规则,根据问题的特点,对搜索空间进行剪枝。启发式剪枝的关键是设计好剪枝规则,通常需要对问题进行深入理解和分析。启发式剪枝常常用于求解最优化问题,如TSP(旅行商问题)等。
-
剪枝的特性:
- 提高算法效率:剪枝可以减少搜索空间,避免无效搜索,从而提高算法效率。
- 需要合适的剪枝策略:剪枝的效果取决于剪枝策略的合理性和优化程度,需要根据具体问题选择适合的剪枝策略。
- 可能会导致漏解:某些剪枝策略可能会排除一些合法解,需要进行验证和修正。
- 与算法结合使用:剪枝通常需要与其他算法结合使用,如回溯、动态规划、分治等。
实战
题目一:51. N 皇后
解题思路
这是一道经典的搜索问题,可以采用回溯算法来求解,思路如下:
- 初始化一个空的棋盘,大小为N*N,其中所有的位置都为0(表示没有皇后)。
- 从第一行开始,逐行搜索。对于每一行,从第一列开始,逐列尝试放置皇后。如果该位置可以放置皇后(即该位置的同一行、同一列、同一对角线上都没有其他皇后),则将该位置标记为1,表示放置了皇后。
- 如果所有的皇后都已经放置完毕,将当前棋盘保存到结果中,并回溯到上一行,尝试该行的下一个位置。
- 如果当前行的所有位置都已经尝试完毕,回溯到上一行,将该行当前位置标记为0(表示没有皇后),继续尝试该行的下一个位置。
- 如果所有的行都已经尝试完毕,算法结束,返回所有合法的解。
Tips:在每一行中,我们只能放置一个皇后,因此每个皇后的位置是互斥的。此外,每个皇后所在的行、列、主对角线和副对角线上都不能有其他皇后,因此需要使用列剪枝和斜线剪枝来排除不合法的解。
在回溯算法的基础上,我们可以通过剪枝来优化算法效率。具体来说,我们可以通过以下两种剪枝方式来优化算法:
- 列剪枝:在搜索的过程中,我们可以维护一个列表,记录每一列是否已经有皇后占用。当我们在搜索的某一行尝试放置皇后时,只需要考虑未被占用的列,即可避免重复搜索。
- 斜线剪枝:在搜索的过程中,我们可以维护两个集合,分别记录已经被占用的主对角线和副对角线。当我们在搜索的某一行尝试放置皇后时,只需要考虑未被占用的主对角线和副对角线,即可避免重复搜索。
复杂度分析
时间复杂度:在最坏情况下,N皇后问题的时间复杂度是指数级别的,即。但是通过列剪枝和斜线剪枝,可以大大减少搜索空间,提高算法效率。具体来说,列剪枝可以将搜索空间减少到,而斜线剪枝可以将搜索空间减少到。因此,综合考虑,N皇后问题的时间复杂度可以近似为。
代码实现
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
self.res = []
self.cols = [False] * n # 记录每一列是否已经有皇后占用
self.diag1 = [False] * (2 * n - 1) # 记录主对角线是否已经有皇后占用
self.diag2 = [False] * (2 * n - 1) # 记录副对角线是否已经有皇后占用
self.helper([], n, 0)
return self.res
def helper(self, cur, n, row):
if row == n:
self.res.append(self.format_res(cur))
return
for col in range(n):
if self.valid_pos(row, col, n):
self.cols[col] = True
self.diag1[row + col] = True
self.diag2[row - col + n - 1] = True
cur.append(col)
self.helper(cur, n, row + 1)
cur.pop()
self.cols[col] = False
self.diag1[row + col] = False
self.diag2[row - col + n - 1] = False
def valid_pos(self, row, col, n):
return not self.cols[col] and not self.diag1[row + col] and not self.diag2[row - col + n - 1]
def format_res(self, cur):
res = []
for col in cur:
row_str = '.' * col + 'Q' + '.' * (len(cur) - col - 1)
res.append(row_str)
return res