Leetcode回溯算法(dfs)

17.电话号码的字母组合

22.括号生成

做法:dfs + 剪枝。剪枝的做法是判断当前字符串中左右括号的个数

37.解数独

做法:dfs。对于dfs类型的题目:我们只需要把要处理的最小单元的处理逻辑写出来就好了,而不需要考虑下一个点是否符合我们继续dfs的条件

39.组合总和

方法:回溯 + 剪枝。剪枝很重要!!!如果遇到与我们想得到的结果不符合的,我们在本次搜索中就砍掉它,是为剪枝!!!具体怎么回溯,自己想。
当然这道题目也可以使用动态规划,关于这个方法我理解得不深,需要加强理解。

40.组合总和II

方法1:动态规划。该问题实际上是一个01背包问题,可以使用01背包问题模板求解。
方法2:回溯+剪枝!!!这里的难点在于去重。可惜这种方法我不理解。

46.全排列

方法:dfs,然后就没什么好说的了,毕竟基础题目。

47.全排列II

方法:dfs
注意事项:给定的序列中包含重复数字,因此我们要防止重复排列出现。关键点在于我们如何处理相同数字。一种有效的做法是保证相同数字的相对次序保持不变。具体而言,在使用当前数字时,我们要考虑在它之前的且与它相同的数字是否被使用过,如果否,跳过本次循环;如果之前与 它相同的数字被使用过,我们就使用该数字。

51.N皇后
77.组合

对全排列的代码进行了一丢丢改进。

观察组合[1,2][1,3][1,4][2,3][2,4]…… 不难发现:

  1. 放置数字1后,就不再考虑数字1
  2. 放置数字2后,就不考虑数字1和数字2
  3. 放置数字3后,就不考虑数字1、数字2和数字3 ……
  4. 依次类推,放置数值k后,就不考虑包括k之前的全部数字。

因此,我们在进行dfs的时候,需要传入一个记录当前放置数字的变量num。
在dfs循环体内部,循环不是从1开始(与全排列的不同),而是从num + 1开始。

对了,还有一点很重要:记得剪枝啊!!!剪枝可以提高时间复杂度和空间复杂度。

78.子集

方法:和 组合 的方法完全一样。不过我认为官方题解的第二个解决方法特别巧妙。

79.单词搜索

方法:dfs。
看到搜索,就应该立马想到深度优先搜索(dfs)和广度优先搜索(bfs)。
广度优先搜索的不足: 最开始使用的是bfs,但是bfs有一个问题:bfs使用一个状态数组指示当前元素是否被访问过,由于不能重复修改状态数组st[i][j],因此它不能遍历所有可能的路径。
但是dfs就没有这个问题,因此它可以用来解决这个问题。

90.子集II

方法:这道题目的去重和组合总和II完全相同。经过这道题目,我理解了组合总和II的去重。
把相同的数字放在一起处理。比如数组[0,1,2,2,2,3,4,5],该数组中有33,那么我们在循环的时候考虑

  1. 不放3
  2. 放一个3
  3. 放两个3
  4. 放三个3

无论放几个3,下一步递归的数组下标总是5

93.复原IP地址

方法:每次递归有三种分隔方法:

  1. 分隔一个数字
  2. 分隔两个数字
  3. 分隔三个数字

当然我们必须判断

  1. 是否可以分隔两个数字和三个数字
  2. 分隔出来的数字是否有效

95.不同的二叉搜索树II

方法自下而上回溯,类似于归并排序。
最开始我使用的是自上而下的回溯。在整个过程中,发现递归终止条件难以判断,一时间陷入思维困境中。后来看了题解,才知道这道题目自上而下回溯是行不通的。

131.分割回文串

方法:每次递归枚举回文串的长度:

  1. 假设我们搜索到字符串的第i个字符,且s[0 : i - 1]的所有字符都已经分割成若干个回文串,并且分割结果放入答案数组ans中。
  2. 接下来我们从第i个字符字符开始,枚举回文串的长度,判断s.substr(i,len)是否是回文串。
  3. 如果是,递归
  4. 否则,递增回文串长度,重复上述步骤

优化:记忆化搜索或者动态规划预处理

133.克隆图

相当nice的题目,值得一看

140.单词拆分

看到字符串的长度很小,而且要求所有可能的句子,我的第一反应的深度优先遍历dfs。具体步骤如下:

  1. 对字符串 s s s进行预处理。定义一个二维数组stst[i][j]表示字符串s[i : j]是否是字符串列表中的单词。注意:与上一题不同,这里预处理是判断s[i : j]是否是字符串列表中的单词,而不是判断s[i : j]是否由字符串列表中的单词拼接而成。
  2. dfs。

200.岛屿数量

第一次做的时候开了一个状态数组,用它表示当前陆地是否访问过
第二次做的时候直接在原地修改,即若当前陆地访问过,则grid[x][y] = '0',极大地提高了程序的运行速度。

216.组合总和III

每一个数字都有使用不使用两种选择。

241.为运算表达式设计优先级

方法:区间DP / DFS + 分治(类似于归并排序)

以表达式a + b * c - d * f为例,以任意一个运算符对其进行划分。对于第一个运算符+,整体表达式的值等于+左边表达式的值和右边表达式值的和。因此,只要递归计算每一个运算符左右两边式子的所有可能结果,即可得到整个表达式所有可能的结果。

若划分的区间里不存在运算符,则搜索结果是运算数本身。

301.删除无效的括号

题目分析
题目要求输出的字符串是有效字符串,且相比于原字符串,删除的括号个数最少。如果把删除的括号最少的个数预处理出来,那么问题转换为在给定删除括号个数的情况下,求解所有有效的且不重复的字符串。


思路和算法

  1. 预处理删除括号个数的最小值。用变量cnt统计删除括号个数的最小值,具体步骤如下:
    1. 数据结构:栈
    2. 遍历字符串,遇到左括号入栈;遇到右括号,如果栈不为空,则出栈,否则说明该右括号是无效括号,cnt1
    3. 结束遍历后,如果栈不空,意味着栈中的左括号全是无效括号,更新cntcnt = cnt + stk.size()
  2. 回溯 + 剪枝。具体做法如下:
    1. 数据结构:栈
    2. 对于搜索到的字符s[i],有三种情况:
      1. 左括号。有两种选择:删或不删。
      2. 右括号。有两种选择,删或不删。
      3. 普通字符。直接添加。

使用上述方法得到的结果可能会有重复,因此可采用set去重,最后把set里的结果搬移到vector里。

306.累加数

回溯 + 大数加法

329.矩阵中的最长递增子路径

记忆化搜索


394.字符串解码


403.青蛙过河

想到了深度优先遍历,没有想到记忆化搜索。
想到了从深度优先遍历推导动态规划,然后什么也没想出来。

显而易见,这道题应该使用dfs来完成。但是题目所给的数据量比较大,因此使用dfs会超时。面对这种情况,一般而言有两种做法:

  1. 记忆化搜索
  2. dfs为动态规划

记忆化搜索

记忆化搜索的关键在于标识搜过的状态。这道题目中的状态是一个二维的状态:当前石子的索引,跳到当前石子位置时上一步的跳跃步数。

通常使用数组来作为存储中间结果的容器。下面就来确定数据范围:

题目规定第一次只能眺一步,后续每次眺的步数最多在上一步跳跃步数的基础上加1。考虑最坏的情况,也就是每次跳跃的步数加 1 1 1,青蛙最多跳 n n n步, n n n是石子的个数。

因此,记忆化容器可以使用数组int cache[n + 1][n + 1]

此外,还可以使用哈希表作为记忆化容器,我写的记忆化搜索的代码中就利用了哈希表作为记忆化容器。

动态规划

动态规划的关键在于状态表示状态计算

有了上面的记忆化搜索分析,状态表示就是记忆化搜索过程中标识的状态:当前石子的索引,跳到当前石子位置时上一步的跳跃步数。

状态计算:不难看出,能够跳到当前的石子位置取决于之前的石子位置,因此,动态规划方程如下:
d p [ i ] [ k ] = d p [ j ] [ k ] ∣ ∣ d p [ j ] [ k + 1 ] ∣ ∣ d p [ j ] [ k − 1 ] dp[i][k] = dp[j][k] || dp[j][k + 1] || dp[j][k - 1] dp[i][k]=dp[j][k]∣∣dp[j][k+1]∣∣dp[j][k1]
其中, k = s t o n e s [ i ] − s t o n e s [ j ] k = stones[i] - stones[j] k=stones[i]stones[j]


417.太平洋大西洋水流问题

注意点:从边缘节点开始搜索


419.甲板上的战舰

一道比较简单的搜索题,我指的是没加空间限制之前。


427.建立四叉树

这是一道简单的递归题目。

根据题目介绍的四叉树的相关信息,可以提出如下的建立四叉树的算法:

  1. 判断方格内所有元素是否相同。如果相同,说明是叶子节点,可以直接返回;否则进行下一步操作。
  2. 把方格分割为上左、上右,下左,下右四个正方形,依次递归每一个正方形

持续更新中…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值