做法:dfs + 剪枝。剪枝的做法是判断当前字符串中左右括号的个数
做法:dfs。对于dfs类型的题目:我们只需要把要处理的最小单元的处理逻辑写出来就好了,而不需要考虑下一个点是否符合我们继续dfs的条件
方法:回溯 + 剪枝。剪枝很重要!!!如果遇到与我们想得到的结果不符合的,我们在本次搜索中就砍掉它,是为剪枝!!!具体怎么回溯,自己想。
当然这道题目也可以使用动态规划,关于这个方法我理解得不深,需要加强理解。
方法1:动态规划。该问题实际上是一个01背包问题,可以使用01背包问题模板求解。
方法2:回溯+剪枝!!!这里的难点在于去重。可惜这种方法我不理解。
方法:dfs,然后就没什么好说的了,毕竟基础题目。
方法:dfs
注意事项:给定的序列中包含重复数字,因此我们要防止重复排列出现。关键点在于我们如何处理相同数字。一种有效的做法是保证相同数字的相对次序保持不变。具体而言,在使用当前数字时,我们要考虑在它之前的且与它相同的数字是否被使用过,如果否,跳过本次循环;如果之前与 它相同的数字被使用过,我们就使用该数字。
对全排列的代码进行了一丢丢改进。
观察组合[1,2][1,3][1,4][2,3][2,4]……
不难发现:
- 放置数字1后,就不再考虑数字1
- 放置数字2后,就不考虑数字1和数字2
- 放置数字3后,就不考虑数字1、数字2和数字3 ……
- 依次类推,放置数值k后,就不考虑包括k之前的全部数字。
因此,我们在进行dfs的时候,需要传入一个记录当前放置数字的变量num。
在dfs循环体内部,循环不是从1开始(与全排列的不同),而是从num + 1开始。对了,还有一点很重要:记得剪枝啊!!!剪枝可以提高时间复杂度和空间复杂度。
方法:和 组合 的方法完全一样。不过我认为官方题解的第二个解决方法特别巧妙。
方法:dfs。
看到搜索,就应该立马想到深度优先搜索(dfs)和广度优先搜索(bfs)。
广度优先搜索的不足: 最开始使用的是bfs,但是bfs有一个问题:bfs使用一个状态数组指示当前元素是否被访问过,由于不能重复修改状态数组st[i][j]
,因此它不能遍历所有可能的路径。
但是dfs就没有这个问题,因此它可以用来解决这个问题。
方法:这道题目的去重和组合总和II完全相同。经过这道题目,我理解了组合总和II的去重。
把相同的数字放在一起处理。比如数组[0,1,2,2,2,3,4,5]
,该数组中有3
个3
,那么我们在循环的时候考虑
- 不放3
- 放一个3
- 放两个3
- 放三个3
无论放几个3,下一步递归的数组下标总是
5
。
方法:每次递归有三种分隔方法:
- 分隔一个数字
- 分隔两个数字
- 分隔三个数字
当然我们必须判断
- 是否可以分隔两个数字和三个数字
- 分隔出来的数字是否有效
方法:自下而上回溯,类似于归并排序。
最开始我使用的是自上而下的回溯。在整个过程中,发现递归终止条件难以判断,一时间陷入思维困境中。后来看了题解,才知道这道题目自上而下回溯是行不通的。
方法:每次递归枚举回文串的长度:
- 假设我们搜索到字符串的第
i
个字符,且s[0 : i - 1]
的所有字符都已经分割成若干个回文串,并且分割结果放入答案数组ans
中。- 接下来我们从第
i
个字符字符开始,枚举回文串的长度,判断s.substr(i,len)
是否是回文串。- 如果是,递归
- 否则,递增回文串长度,重复上述步骤
优化:记忆化搜索或者动态规划预处理
相当nice的题目,值得一看
看到字符串的长度很小,而且要求所有可能的句子,我的第一反应的深度优先遍历dfs。具体步骤如下:
- 对字符串 s s s进行预处理。定义一个二维数组
st
,st[i][j]
表示字符串s[i : j]
是否是字符串列表中的单词。注意:与上一题不同,这里预处理是判断s[i : j]
是否是字符串列表中的单词,而不是判断s[i : j]
是否由字符串列表中的单词拼接而成。- dfs。
第一次做的时候开了一个状态数组,用它表示当前陆地是否访问过
第二次做的时候直接在原地修改,即若当前陆地访问过,则grid[x][y] = '0'
,极大地提高了程序的运行速度。
每一个数字都有使用或不使用两种选择。
方法:区间DP / DFS + 分治(类似于归并排序)
以表达式
a + b * c - d * f
为例,以任意一个运算符对其进行划分。对于第一个运算符+
,整体表达式的值等于+
左边表达式的值和右边表达式值的和。因此,只要递归计算每一个运算符左右两边式子的所有可能结果,即可得到整个表达式所有可能的结果。
若划分的区间里不存在运算符,则搜索结果是运算数本身。
题目分析
题目要求输出的字符串是有效字符串,且相比于原字符串,删除的括号个数最少。如果把删除的括号最少的个数预处理出来,那么问题转换为在给定删除括号个数的情况下,求解所有有效的且不重复的字符串。
思路和算法
- 预处理删除括号个数的最小值。用变量
cnt
统计删除括号个数的最小值,具体步骤如下:
- 数据结构:栈
- 遍历字符串,遇到左括号入栈;遇到右括号,如果栈不为空,则出栈,否则说明该右括号是无效括号,
cnt
加1
- 结束遍历后,如果栈不空,意味着栈中的左括号全是无效括号,更新
cnt
为cnt = cnt + stk.size()
- 回溯 + 剪枝。具体做法如下:
- 数据结构:栈
- 对于搜索到的字符
s[i]
,有三种情况:
- 左括号。有两种选择:删或不删。
- 右括号。有两种选择,删或不删。
- 普通字符。直接添加。
使用上述方法得到的结果可能会有重复,因此可采用
set
去重,最后把set
里的结果搬移到vector
里。
回溯 + 大数加法
记忆化搜索
想到了深度优先遍历,没有想到记忆化搜索。
想到了从深度优先遍历推导动态规划,然后什么也没想出来。
显而易见,这道题应该使用dfs
来完成。但是题目所给的数据量比较大,因此使用dfs
会超时。面对这种情况,一般而言有两种做法:
- 记忆化搜索
- 改
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][k−1]
其中,
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]
注意点:从边缘节点开始搜索
一道比较简单的搜索题,我指的是没加空间限制之前。
这是一道简单的递归题目。
根据题目介绍的四叉树的相关信息,可以提出如下的建立四叉树的算法:
- 判断方格内所有元素是否相同。如果相同,说明是叶子节点,可以直接返回;否则进行下一步操作。
- 把方格分割为上左、上右,下左,下右四个正方形,依次递归每一个正方形
持续更新中…