dfs算法模板:
1、下一层仅2个节点的dfs,也就是二叉树的dfs
先序遍历,迭代和递归写法都要熟悉:
def preoder_traversal(root):
if not root:
return
stack = [root]
while stack:
node = stack.pop()
do something with node
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return xxx
def preoder_traversal(root):
if not root:
return
do something with root
preoder_traversal(root.left)
preoder_traversal(root.right)
中序遍历,迭代和递归写法:
def inoder_traversal(root):
if not root:
return
stack = []
node = root
while stack or node:
if node:
stack.append(node)
node = node.left
else:
cur_node = stack.pop()
do something with cur_node
node = cur_node.right
return xxx
def inoder_traversal(root):
if not root:
return
inoder_traversal(root.left)
do something with root
inoder_traversal(root.right)
后序遍历,仅仅掌握递归写法:
def post_order_traversal(root):
if not root:
return
post_order_traversal(root.left)
post_oder_traversal(root.right)
do something with root
遍历过程中需要记住上次遍历节点才能得到结果的,模板(中序和后序仅仅换下if else代码位置):
last_node = None
def dfs(root):
if last_node is None:
last_node = root
else:
compare(last_node, root)....
last_node = root
dfs(root.left)
dfs(root.right)
补充下,二维矩阵类的dfs模板:
def solve(board):
m, n = len(board), len(board[0])
visited = [[False] * n for i in range(m)]
def dfs(i, j, index):
# 利用index, i, j,进行剪枝判断
do_sth_with_result() # 例如添加path结果等
# 只有i,j有意义,则设置访问visited,因为是dfs,所以一定会在一路遍历的过程中将所有经过的i,j标记为true
visited[i][j] = True
for dx, dy in [(1, 0), (-1, 0), (0, -1), (0, 1)]:
if (0 <= i + dx < m and 0 <= j + dy < n) and not visited[i + dx][j + dy]:
dfs(i + dx, j + dy, index + 1)
# 还原visited,因为在回溯的时候,还需再度使用
visited[i][j] = False
for i in range(m):
for j in range(n):
dfs(i, j, 0)
因为之前我对visited这个变量的设置不够优雅,所以针对性做了上述模板的补充。
经典题目:https://leetcode.cn/problems/word-search/ 单词搜索
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
m, n = len(board), len(board[0])
visted = [[False]*n for i in range(m)]
def dfs(i, j, index):
if index == len(word)-1:
return board[i][j] == word[index]
if board[i][j] != word[index]:
return False
visted[i][j] = True # **********当心************
for dx, dy in [(1, 0), (-1, 0), (0, -1), (0, 1)]:
if (0 <= i+dx < m and 0 <= j+dy < n) and not visted[i+dx][j+dy]:
if dfs(i+dx, j+dy, index+1):
return True
visted[i][j] = False # **********当心************
return False
for i in range(m):
for j in range(n):
if dfs(i, j, 0):
return True
return False
再看一个题目:挑战程序设计竞赛里的
第一个例题:湖泊计数
描述:由于最近的降雨,在农夫约翰田地的不同地方积聚了水,用N x M(1 <= N <= 100; 1 <= M <= 100)正方形的矩形表示。每个方格包含水(‘W’)或旱地(’。’)。农夫约翰想弄清楚他的田地里形成了多少个池塘。池塘是一组相连的正方形,里面有水,其中一个正方形被认为与八个池塘相邻。
给定农夫约翰的田野图,确定他有多少个池塘。
输入项:
*第1行:两个以空格分隔的整数:N和M
*第2…N + 1行:每行M个字符,代表Farmer John字段的一行。每个字符都是“ W”或“ . ”,字符之间没有空格。
输出量:
*第1行:农夫约翰田间的池塘数。
样本输入
8 12
. WWW . . . . . WWW
. . . . W W . . . WW .
. . . . WW . . . W . .
. . . . . W . . . W . .
. . . . . W . . . W . .
.WW . . . .WW. . .
WWW . . . . WW . . .
. . W . . . . . . .WW .
样本输出
3
输出详细信息:
有三个池塘:一个在左上角,一个在左下角,另一个在右侧
————————————————
void dfs(int x, int y)
{
map[x][y] = '.'; //看到了吗!!!都在dfs遍历i,j节点的时候设置visisted!
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
x += dx, y += dy;
if (x >= 1 && x <= M && y >= 1 && y <= N && map[x][y]=='W')
dfs(x,y);
}
}
return ;
}
int main()
{
int i,j,sum;
for (i = 1; i <= M; i++)
{
for (j = 1; j <= N; j++)
{
cin>>map[i][j];
}
}
for (i = 1; i <= M; i++)
{
for (j = 1; j <= N; j++)
{
if( map[i][j]=='W')
{ dfs(i,j);
sum++; }
}
}
cout<<sum<<endl;
return 0;
}
例子:验证二叉树
描述
给定一个二叉树,判断它是否是合法的二叉查找树(BST)
一棵BST定义为:
- 节点的左子树中的值要严格小于该节点的值。
- 节点的右子树中的值要严格大于该节点的值。
- 左右子树也必须是二叉查找树。
- 一个节点的树也是二叉查找树。
样例
样例 1:
输入:
tree = {-1}
输出:
true
解释:
二叉树如下(仅有一个节点):
-1
这是二叉查找树。
样例 2:
输入:
tree = {2,1,4,#,#,3,5}
输出:
true
解释:
二叉树如下:
2
/ \
1 4
/ \
3 5
这是二叉查找树。
class Solution:
"""
@param root: The root of binary tree.
@return: True if the binary tree is BST, or false
"""
def is_valid_b_s_t(self, root: TreeNode) -> bool:
# write your code here
self.last_node = None
return self.dfs(root)
def dfs(self, root):
if not root:
return True
if not self.dfs(root.left):
return False
if self.last_node and self.last_node.val >= root.val:
return False
self.last_node = root
return self.dfs(root.right)
类似的还有:
二叉树拆成链表 · Flatten Binary Tree to Linked List
描述
将一棵二叉树按照前序遍历拆解成为一个 假链表
。所谓的假链表是说,用二叉树的 right 指针,来表示链表中的 next 指针。
不要忘记将左儿子标记为 null,否则你可能会得到空间溢出或是时间溢出。
样例
样例 1:
输入:{1,2,5,3,4,#,6}
输出:{1,#,2,#,3,#,4,#,5,#,6}
解释:
1
/ \
2 5
/ \ \
3 4 6
1
\
2
\
3
\
4
\
5
\
6
样例 2:
输入:{1}
输出:{1}
解释:
1
1
挑战
不使用额外的空间耗费。
from lintcode import (
TreeNode,
)
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
"""
class Solution:
"""
@param root: a TreeNode, the root of the binary tree
@return: nothing
"""
def flatten(self, root: TreeNode):
# write your code here
self.last_node = None
self.dfs(root)
return root
def dfs(self, root):
if not root:
return None
l, r = root.left, root.right
if self.last_node:
# change self.last_node
self.last_node.left = None
self.last_node.right = root
self.last_node = root
self.dfs(l)
self.dfs(r)
注意,当心改写right,所以要先存下。
此外,还有使用全局变量的,在遍历每一个节点都和全局变量比较,例如下面的self.max_avg
描述 最大平均值子树 ==》所有树类相关求最值的几乎都要用全局变量思路!!!
给一棵二叉树,找到有最大平均值的子树。返回子树的根结点。
LintCode会打印出根结点为你返回节点的子树,保证有最大平均数子树只有一棵
样例
样例1
输入:
{1,-5,11,1,2,4,-2}
输出:11
说明:
这棵树如下所示:
1
/ \
-5 11
/ \ / \
1 2 4 -2
11子树的平均值是4.333,为最大的。
样例2
输入:
{1,-5,11}
输出:11
说明:
1
/ \
-5 11
1,-5,11 三棵子树的平均值分别是 2.333,-5,11。因此11才是最大的
from lintcode import (
TreeNode,
)
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
"""
class Solution:
"""
@param root: the root of binary tree
@return: the root of the maximum average of subtree
"""
def find_subtree2(self, root: TreeNode) -> TreeNode:
# write your code here
self.max_avg, self.ans = float('-inf'), None
sum_val, cnt = self.dfs(root)
return self.ans
def dfs(self, root):
if not root:
return 0, 0
s1, c1 = self.dfs(root.left)
s2, c2 = self.dfs(root.right)
s = s1 + s2 + root.val
c = c1 + c2 + 1
if s / c > self.max_avg:
self.max_avg = s / c
self.ans = root
return s, c
看一个需要使用迭代的,当然,也不是说完全不能用递归,得看面试官的脸!尤其是笔试的时候。。。
BST中第K小的元素 · Kth Smallest Element in a BST
描述
给一棵二叉搜索树,写一个 KthSmallest
函数来找到其中第 K 小的元素。
你可以假设 k 总是有效的, 1 ≤ k ≤ 树的总节点数
。
样例
样例 1:
输入:{1,#,2},2
输出:2
解释:
1
\
2
第二小的元素是2。
样例 2:
输入:{2,1,3},1
输出:1
解释:
2
/ \
1 3
第一小的元素是1。
挑战
如果这棵 BST 经常会被修改(插入/删除操作)并且你需要很快速的找到第 K 小的元素呢?你会如何优化这个 KthSmallest 函数?
递归解法:
from lintcode import (
TreeNode,
)
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
"""
class Solution:
"""
@param root: the given BST
@param k: the given k
@return: the kth smallest element in BST
"""
def kth_smallest(self, root: TreeNode, k: int) -> int:
# write your code here
self.ans = None
self.kth = 0
self.dfs(root, k)
return self.ans
def dfs(self, root, k):
if not root:
return
self.dfs(root.left, k)
self.kth += 1
if self.kth == k:
# print(self.kth, root.val, k)
self.ans = root.val
return
self.dfs(root.right, k)
非递归解法:
from lintcode import (
TreeNode,
)
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
"""
class Solution:
"""
@param root: the given BST
@param k: the given k
@return: the kth smallest element in BST
"""
def kth_smallest(self, root: TreeNode, k: int) -> int:
# write your code here
node = root
stack = []
kth = 0
while stack or node:
if node:
stack.append(node)
node = node.left
elif stack:
tmp = stack.pop()
node = tmp.right
kth += 1
if kth == k:
return tmp.val
return -1
86 · 二叉查找树迭代器
描述
设计实现一个带有下列属性的二叉查找树的迭代器:
next()返回BST中下一个最小的元素
- 元素按照递增的顺序被访问(比如中序遍历)
-
next()
和hasNext()
的询问操作要求均摊时间复杂度是&amp;amp;amp;amp;amp;lt;span class="katex"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="katex-mathml"&amp;amp;amp;amp;amp;gt;O(1)O(1)&amp;amp;amp;amp;amp;lt;span class="katex-html"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="base"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="strut" style="height: 1em; vertical-align: -0.25em;"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="mord mathnormal" style="margin-right: 0.02778em;"&amp;amp;amp;amp;amp;gt;O&amp;amp;amp;amp;amp;lt;span class="mopen"&amp;amp;amp;amp;amp;gt;(&amp;amp;amp;amp;amp;lt;span class="mord"&amp;amp;amp;amp;amp;gt;1&amp;amp;amp;amp;amp;lt;span class="mclose"&amp;amp;amp;amp;amp;gt;)
样例
样例 1:
输入:
tree = {10,1,11,#,6,#,12}
输出:
[1,6,10,11,12]
解释:
二叉查找树如下 :
10
/ \
1 11
\ \
6 12
可以返回二叉查找树的中序遍历 [1,6,10,11,12]
样例 2:
输入:
tree = {2,1,3}
输出:
[1,2,3]
解释:
二叉查找树如下 :
2
/ \
1 3
可以返回二叉查找树的中序遍历 [1,2,3]
挑战
额外空间复杂度是O(h),其中h是这棵树的高度
Super Star:使用O(1)的额外空间复杂度
我的写法:
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
Example of iterate a tree:
iterator = BSTIterator(root)
while iterator.hasNext():
node = iterator.next()
do something for node
"""
class BSTIterator:
"""
@param: root: The root of binary tree.
"""
def __init__(self, root):
# do intialization if necessary
self.node = root
self.stack = []
"""
@return: True if there has next node, or false
"""
def hasNext(self):
# write your code here
return self.node or self.stack
"""
@return: return next node
"""
def _next(self):
# write your code here
while self.node or self.stack:
if self.node:
self.stack.append(self.node)
self.node = self.node.left
elif self.stack:
tmp = self.stack.pop()
self.node = tmp.right
return tmp
其他写法:更优雅些
class BSTIterator:
#@param root: The root of binary tree.
def __init__(self, root):
self.stack = []
self.curt = root
#@return: True if there has next node, or false
def hasNext(self):
return self.curt is not None or len(self.stack) > 0
#@return: return next node
def _next(self):
while self.curt is not None:
self.stack.append(self.curt)
self.curt = self.curt.left
self.curt = self.stack.pop()
nxt = self.curt
self.curt = self.curt.right
return nxt
3. BST的搜索,比较简单直观,和二分类似:
def bst_search(root, target):
if not root:
return None
node = root
while node:
if node.val < target:
node = node.right
elif node.val > target:
node = node.left
else:
return node.val
return xxx
二叉搜索树中最接近的值 · Closest Binary Search Tree Value
描述
给一棵非空二叉搜索树以及一个target值,找到在BST中最接近给定值的节点值
- 给出的目标值为浮点数
- 我们可以保证只有唯一一个最接近给定值的节点
样例
样例1
输入: root = {5,4,9,2,#,8,10} and target = 6.124780
输出: 5
解释:
二叉树 {5,4,9,2,#,8,10},表示如下的树结构:
5
/ \
4 9
/ / \
2 8 10
样例2
输入: root = {3,2,4,1} and target = 4.142857
输出: 4
解释:
二叉树 {3,2,4,1},表示如下的树结构:
3
/ \
2 4
/
1
logn复杂度的:
非递归的版本 根据target的值与当前root.val的大小找到最接近target的两个值
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
"""
class Solution:
"""
@param root: the given BST
@param target: the given target
@return: the value in the BST that is closest to the target
"""
def closestValue(self, root, target):
upper = root
lower = root
while root:
if target > root.val:
lower = root
root = root.right
elif target < root.val:
upper = root
root = root.left
else:
return root.val
if abs(upper.val - target) <= abs(lower.val - target):
return upper.val
return lower.val
注意upper和lower的设置!!!和二分搜索模板异曲同工!!!
4、下一层是多节点(一定是for,而不能单纯像二叉树dfs root.left, dfs root.right那样)的dfs遍历 ==》排列组合就是典型这种!!!
dfs模板:
void dfs(input, path, result, cur) {
if (数据非法) return; // 终止条件
if (cur == input.size()) { // 收敛条件 or if (gap == 0) {
将path放入result
// result.append(list(path))
}
if (可以剪枝) return;
for(step in range(cur,end)) { // 执行所有可能的扩展动作
执行动作,修改path, 排列类的题目交换元素(重复元素加一个判断 if input[i] in input[cur:i]: continue)
dfs(input, path, step + 1 or cur + 1, result);//cur物理含义:树的当前遍历层级,见后说明
恢复path,排列类的题目恢复交换元素
}
}
--------------------------------
该模板的理解:
案例:单词分割,给一个不含空格的字符串,以及一个字典,找出所有可能的单词分割。例如:catsanddog ==》字典:{"a", "abnormal", "cat", "cats", "sand", "dog", "and"} == > 两种结果:["cats,and,dog"] ["cat","sand", "dog"]
过程分析:
cur =0
|
\|/
“catsanddog”
cur
/ \
cat cats
| |
sand and
| |
dog dog
套用下模板:
def dfs(input_str, cur, word_dict, path, result):
if cur == len(input_str):
result.append(",".join(path))
return
for i in range(cur, len(input_str)):
word = input_str[cur:i+1]
if word in word_dict:
path.append(word)
dfs(input_str, i+1, word_dict, path, result)
path.pop()
def main():
string, word_dict = "catsanddog", {"a", "abnormal", "cat", "cats", "sand", "dog", "and"}
result, path = [], []
cur = 0
dfs(string, cur, word_dict, path, result)
print('result', result)
main()
来一个类似的题目,
136 · 分割回文串
描述
给定字符串 s
, 需要将它分割成一些子串, 使得每个子串都是回文串.
返回所有可能的分割方案.
- 不同的方案之间的顺序可以是任意的.
- 每种分割方案中的每个子串都必须是
s
中连续的一段.
样例
样例 1:
"a"
解释: 字符串里只有一个字符, 也就只有一种分割方式 (就是它本身)
样例 2:
输入: "aab"
输出: [["aa", "b"], ["a", "a", "b"]]
解释: 有两种分割的方式.
1. 将 "aab" 分割成 "aa" 和 "b", 它们都是回文的.
2. 将 "aab" 分割成 "a", "a" 和 "b", 它们全都是回文的.
我自己中意的解法如下:注意dp数组的生成可以参考动态规划部分,为啥两个for是递增,也是最容易理解的版本!!!
class Solution:
"""
@param: s: A string
@return: A list of lists of string
"""
def partition(self, s):
# write your code here
dp = self.calc_dp(s)
self.ans = []
print(dp)
self.dfs(dp, s, layer=0, path=[])
return self.ans
def calc_dp(self, s):
n = len(s)
dp = [[False]*n for i in range(n)]
for j in range(n):
for i in range(0, j+1):
if i == j:
dp[i][j] = True
elif i == j - 1:
dp[i][j] = s[i] == s[j]
else:
if i < n - 1 and j > 0 and s[i] == s[j]:
dp[i][j] = dp[i+1][j-1]
# print(i, j, dp[i][j])
return dp
def dfs(self, dp, s, layer, path):
if layer == len(s):
self.ans.append(list(path))
return
for i in range(layer, len(s)):
if dp[layer][i]:
path.append(s[layer:(i+1)])
self.dfs(dp, s, i+1, path)
path.pop()
不含重复元素和含重复元素的全排列,讲解:
全排列:
举例说明,【1,2,3】:
root
/ | \
1 2 3
/ \ / \ / \
2 3 1 3 1 2
/ \ ... ....
3 1
也就是说:permutate([1,2,3]) = {[1]+permutate([2,3]), [2]+permutate([1,3]), [3]+permutate([1,2])}
使用交换思路后编码如下:
class Solution:
"""
@param: : A list of integers
@return: A list of unique permutations
"""
def permute(self, nums):
# write your code here
result = []
self.find_permutations(nums, 0, result)
return result
def find_permutations(self, nums, start_index, result):
if start_index >= len(nums):
result.append(list(nums))
return
for i in range(start_index, len(nums)):
nums[start_index], nums[i] = nums[i], nums[start_index]
self.find_permutations(nums, start_index + 1, result)
nums[start_index], nums[i] = nums[i], nums[start_index]
==》*****************start_index的物理含义,就是dfs的深度,也就是树的当前遍历层级,start_index+1意味着要到下一层layer去了!!!**********************
含有重复元素的全排列,举例说明,【1,1,2】:
root
/ | \
1 1(重复) 2
/ \ / \ / \
1 2 2 1 1 1(重复) ==》说明同一layer有重复的元素要pass掉
/ \ | | | |
2 1 1 2 1 1
代码如下:就是用比较无脑的交换做法,当前交换的元素nums[i], 只要在nums[start_index:i]有重复元素存在就说明之前的路径已经走过。
class Solution:
"""
@param: : A list of integers
@return: A list of unique permutations
"""
def permuteUnique(self, nums):
# write your code here
result = []
self.find_permutations(nums, 0, result)
return result
def find_permutations(self, nums, start_index, result):
if start_index >= len(nums):
result.append(list(nums))
return
for i in range(start_index, len(nums)):
if nums[i] in nums[start_index:i]: # 重复的排列就多了这个判断而已 *****************************同一layer有重复的元素就pass掉********************
continue
nums[start_index], nums[i] = nums[i], nums[start_index]
self.find_permutations(nums, start_index + 1, result)
nums[start_index], nums[i] = nums[i], nums[start_index]
例子:
接下来看看组合的算法:
不含重复元素的组合算法,无脑式的先排序:
class Solution:
"""
@param nums: A set of numbers
@return: A list of lists
"""
def subsets(self, nums):
# write your code here
nums = sorted(nums)
path, result = [], []
self.dfs(nums, 0, path, result)
return result
def dfs(self, nums, index, path, result):
result.append(list(path))
if index == len(nums):
return
for i in range(index, len(nums)):
path.append(nums[i])
self.dfs(nums, i+1, path, result)
path.pop()
含重复元素的组合算法,无脑式的先排序,加一个if去重:
class Solution:
"""
@param nums: A set of numbers.
@return: A list of lists. All valid subsets.
"""
def subsetsWithDup(self, nums):
# write your code here
nums = sorted(nums)
path, result = [], []
self.dfs(nums, 0, path, result)
return result
def dfs(self, nums, index, path, result):
result.append(list(path))
for i in range(index, len(nums)):
if i > 0 and nums[i] == nums[i-1] and i > index:
continue
path.append(nums[i])
self.dfs(nums, i+1, path, result)
path.pop()
案例参考:
分析下为何是上面的样子:
组合:给定一个含不同整数的集合,返回其所有的子集。
看下面的图,以【1,2,3】为例:结果就是
root (path=[])
|
------------------------------------------------------------------------
| | |
1 2 3
/ \ |
2 3 3
/
3
在遍历上述树的过程中将path全部记录下来
代码如下:
class Solution:
"""
@param nums: A set of numbers
@return: A list of lists
"""
def subsets(self, nums):
# write your code here
nums = sorted(nums)
path, result = [], []
self.dfs(nums, 0, path, result)
return result
def dfs(self, nums, start_index, path, result):
result.append(list(path))
if start_index == len(nums):
return
for i in range(start_index, len(nums)): ==》表示的物理含义:subsets(1,2,3)=subset(1开头)+subset(2开头)+subset(3开头)
path.append(nums[i])
self.dfs(nums, i+1, path, result)
path.pop()
含有重复元素的子集问题,例如【1,2,2,3】
root (path=[])
|
-------------------------------------------------------------------------
| | | |
1 2 2(重复) 3
/ \ / \ |
2 3 2 3 3
/ \ |
2 3 3
|
3
又例如【1,1,2,3】
root (path=[])
|
---------------------------------------------------------------------------
| 1(重复) | |
1 / \ 2 3
/ | \ 2 3 |
1 2 3 | 3
/ \ | 3
2 3 3
|
3
class Solution:
"""
@param nums: A set of numbers.
@return: A list of lists. All valid subsets.
"""
def subsetsWithDup(self, nums):
# write your code here
nums = sorted(nums)
path, result = [], []
self.dfs(nums, 0, path, result)
return result
def dfs(self, nums, index, path, result):
result.append(list(path))
for i in range(index, len(nums)):
if i > 0 and nums[i] == nums[i-1] and i > index: # 和上面代码相比,就多了这个判断而已
continue
path.append(nums[i])
self.dfs(nums, i+1, path, result)
path.pop()
135 · 数字组合
描述
给定一个数组 num
和一个整数 target
. 找到 num
中所有的数字之和为 target
的组合.
- 在同一个组合中,
num
中的每一个数字仅能被使用一次. - 所有数值 (包括
target
) 都是正整数. - 返回的每一个组合内的数字必须是非降序的.
- 返回的所有组合之间可以是任意顺序.
- 解集不能包含重复的组合.
样例
样例 1:
输入: num = [7,1,2,5,1,6,10], target = 8
输出: [[1,1,6],[1,2,5],[1,7],[2,6]]
样例 2:
输入: num = [1,1,1], target = 2
输出: [[1,1]]
解释: 解集不能包含重复的组合
class Solution9:
"""
@param candidates: A list of integers
@param target: An integer
@return: A list of lists of integers
we will sort your return value in output
"""
def combination_sum(self, candidates: List[int], target: int) -> List[List[int]]:
# write your code here
candidates.sort()
self.ans = []
self.dfs(candidates, target, path=[], layer=0)
return self.ans
def dfs(self, nums, target, path, layer):
if target == 0:
self.ans.append(list(path))
return
if layer == len(nums) or target < 0:
# not found
return
for i in range(layer, len(nums)):
if i > layer and nums[i] == nums[i-1]:
continue
path.append(nums[i])
self.dfs(nums, target-nums[i], path, i+1)
path.pop()
print(Solution9().combination_sum([6, 2, 7, 2, 3, 4, 2, 1], 7))
输出:
[[1, 2, 2, 2], [1, 2, 4], [1, 6], [2, 2, 3], [3, 4], [7]]
==》要是自己临场想不起来了,就自己画画组合的图吧!!!
如果允许数字使用多次呢???
135 · 数字组合
给定一个候选数字的集合 candidates
和一个目标值 target
。 找到 candidates
中所有的和为 target
的组合。
在同一个组合中, candidates
中的某个数字出现次数不限。
- 所有数值 (包括
target
) 都是正整数. - 返回的每一个组合内的数字必须是非降序的.
- 返回的所有组合之间可以是任意顺序.
- 解集不能包含重复的组合.
样例
样例 1:
输入: candidates = [2, 3, 6, 7], target = 7
输出: [[7], [2, 2, 3]]
样例 2:
输入: candidates = [1], target = 3
输出: [[1, 1, 1]]
先去重了下,再排序,保证结果是从小大的。
from typing import (
List,
)
class Solution:
"""
@param candidates: A list of integers
@param target: An integer
@return: A list of lists of integers
we will sort your return value in output
"""
def combination_sum(self, candidates: List[int], target: int) -> List[List[int]]:
# write your code here
candidates = list(set(candidates))
candidates.sort()
self.ans = []
self.dfs(candidates, target, path=[], layer=0)
return self.ans
def dfs(self, nums, target, path, layer):
if target == 0:
self.ans.append(list(path))
return
if target < 0:
# not found
return
for i in range(layer, len(nums)):
path.append(nums[i])
self.dfs(nums, target-nums[i], path, i)
path.pop()
这种题,自己画一个树就容易理解了,注意,layer是i,不是i+1.。。
再变化下:
90 · k数和(二)
描述
给定 n
个不同的正整数,整数 k
(&amp;amp;amp;amp;amp;lt;span class="katex"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="katex-mathml"&amp;amp;amp;amp;amp;gt;1&amp;amp;amp;amp;amp;amp;lt;=k&amp;amp;amp;amp;amp;amp;lt;=n1&amp;amp;amp;amp;amp;amp;lt;=k&amp;amp;amp;amp;amp;amp;lt;=n&amp;amp;amp;amp;amp;lt;span class="katex-html"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="base"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="strut" style="height: 0.68354em; vertical-align: -0.0391em;"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="mord"&amp;amp;amp;amp;amp;gt;1&amp;amp;amp;amp;amp;lt;span class="mspace" style="margin-right: 0.277778em;"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="mrel"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;=&amp;amp;amp;amp;amp;lt;span class="mspace" style="margin-right: 0.277778em;"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="base"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="strut" style="height: 0.73354em; vertical-align: -0.0391em;"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="mord mathnormal" style="margin-right: 0.03148em;"&amp;amp;amp;amp;amp;gt;k&amp;amp;amp;amp;amp;lt;span class="mspace" style="margin-right: 0.277778em;"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="mrel"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;=&amp;amp;amp;amp;amp;lt;span class="mspace" style="margin-right: 0.277778em;"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="base"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="strut" style="height: 0.43056em; vertical-align: 0em;"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;span class="mord mathnormal"&amp;amp;amp;amp;amp;gt;n)以及一个目标数字。
在这 n
个数里面找出 k
个不同的数,使得这 k
个数的和等于目标数字,你需要找出所有满足要求的方案(方案顺序不作要求)。
样例
样例 1:
输入:
数组 = [1,2,3,4]
k = 2
target = 5
输出:
[[1,4],[2,3]]
解释:
1+4=5,2+3=5
样例 2:
输入:
数组 = [1,3,4,6]
k = 3
target = 8
输出:
[[1,3,4]]
解释:
1+3+4=8
解法无非是限制了k和target而已!!!
from typing import (
List,
)
class Solution:
"""
@param a: an integer array
@param k: a postive integer <= length(A)
@param target: an integer
@return: A list of lists of integer
we will sort your return value in output
"""
def k_sum_i_i(self, a: List[int], k: int, target: int) -> List[List[int]]:
# write your code here
self.ans = []
self.dfs(a, k, target, layer=0, path=[])
return self.ans
def dfs(self, nums, k, target, layer, path):
if target < 0 or k < 0:
return
if target == 0 and k == 0:
self.ans.append(list(path))
return
for i in range(layer, len(nums)):
if i > layer and nums[i-1] == nums[i]:
continue
path.append(nums[i])
self.dfs(nums, k-1, target-nums[i], i+1, path)
path.pop()
425 · 电话号码的字母组合
描述
给一个不包含0
和1
的数字字符串,每个数字代表一个字母,请返回其所有可能的字母组合。
下图的手机按键图,就表示了每个数字可以代表的字母。
1 | 2 ABC | 3 DEF |
4 GHI | 5 JKL | 6 MNO |
7 PQRS | 8 TUV | 9 WXYZ |
以上的答案是按照词典编撰顺序进行输出的,不过,在做本题时,你也可以任意选择你喜欢的输出顺序。
样例
样例 1:
输入: "23"
输出: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]
解释:
'2' 可以是 'a', 'b' 或 'c'
'3' 可以是 'd', 'e' 或 'f'
样例 2:
输入: "5"
输出: ["j", "k", "l"]
from typing import (
List,
)
class Solution:
"""
@param digits: A digital string
@return: all possible letter combinations
we will sort your return value in output
"""
def letter_combinations(self, digits: str) -> List[str]:
# write your code here
self.key_map = {"1": "", "2": "ABC", "3": "DEF", "4": "GHI", "5": "JKL",
"6": "MNO", "7": "PQRS", "8": "TUV", "9": "WXYZ"}
self.ans = []
self.dfs(digits, layer=0, path=[])
return self.ans
def dfs(self, digits, layer, path):
while layer < len(digits) and digits[layer] == '1':
layer += 1
if layer == len(digits):
if path:
self.ans.append("".join(path))
return
chars = self.key_map[digits[layer]]
for c in chars:
path.append(c.lower())
self.dfs(digits, layer + 1, path)
path.pop()
---------------------------
DFS总结:
1、二叉树的遍历,,先序中序的递归和迭代写法必须掌握,像算法模板一样记住。后序遍历只掌握递归写法。
2、遍历过程中需要记住上次遍历节点才能得到结果的,记住模板。
last_node = None
def dfs (root):
if last_node is None:
last_node = root
else:
compare(last_node, root)....
last_node = root
dfs(root.left)
dfs(root.right)
4、BST的搜索代码要会,要记住。
5、排列组合类题目:
组合类算法,都使用分治+递归的思路去写,重复元素,先排序,无非多了一个判断。
排列类算法,用交换思路,都使用分治+递归的思路去写,重复元素,无非多了一个判断。
6、隐式图搜索:八皇后,正则表达式匹配,word拼图
i j
| |
abc ==> abc dfs(i+1, j+1)
a*bc ==> aaabc dfs(i+2, j) or dfs(i, j+1)
a.bc ==> adbc dfs(i+1, j+1)
a b c
g a n
a x x
i x x
n x x
dfs(左边走)
dfs(右边走)
dfs(上边走)
dfs(下边走)
走的过程中将路径记下来
7、常见问题:
超时的处理:剪枝(cache、trie去剪枝),修改算法bfs,用dp
测试用例过不完:自己debug,放到ide去调试
补充:超时使用DP的场景
192 · 通配符匹配预发布
描述:给定一个字符串 s
和一个字符模式 p
,实现一个支持 '?'
和 '*'
的通配符匹配。匹配规则如下:
-
'?'
可以匹配任何单个字符。 -
'*'
可以匹配任意字符串(包括空字符串)。
两个串完全匹配才算匹配成功。
0 <= |s|, |p| <= 1000
s仅包含小写英文字母
p包含小写英文字母,?
和 *
样例
样例1
输入:
"aa"
"a"
输出: false
输出2
输入:
"aa"
"aa"
输出: true
输出3
输入:
"aaa"
"aa"
输出: false
输出4
输入:
"aa"
"*"
输出: true
说明: '*' 可以替换任何字符串
输出5
输入:
"aa"
"a*"
输出: true
样例6
输入:
"ab"
"?*"
输出: true
说明: '?' -> 'a' '*' -> 'b'
样例7
输入:
"aab"
"c*a*b"
输出: false
先说下dp模板:
m, n = len(A), len(B)
dp = [[False]*(n+1) for i in range(m+1)]
for i in range(n+1):
for j in range(m+1):
if i == 0:
if j == 0:
# 处理dummy边界
else:
# j>0 处理边界
else:
if j > 0:
# 套用正常的dp公式
else:
#j ==0 的特殊情况考虑
retunr dp[m][n]
上述题目解法:尤其是*匹配0-1-2的情况要考虑清楚。
class Solution:
"""
@param s: A string
@param p: A string includes "?" and "*"
@return: is Match?
"""
def is_match(self, s: str, p: str) -> bool:
# write your code here
m, n = len(s), len(p)
dp = [[False]*(n + 1) for i in range(m + 1)]
for i in range(0, m + 1):
for j in range(0, n + 1):
if i == 0:
if j == 0:
dp[i][j] = True
else:
dp[i][j] = p[j-1] == '*' and dp[i][j-1]
else:
if j == 0:
dp[i][j] == False
else:
if p[j-1] != '*':
dp[i][j] = (p[j-1] == '?' or p[j-1] == s[i-1]) and dp[i-1][j-1]
else:
"""
dp[i][j] = dp[i][j-1] \ # *匹配0个,仅看s[i]和p[j-1]是否匹配
or dp[i-1][j-1] \ #*匹配1个,*匹配s[i]
or dp[i-1][j] #*匹配2个,*同时匹配s[i-1]和s[i]
"""
dp[i][j] = dp[i][j-1] \
or dp[i-1][j-1] \
or dp[i-1][j]
return dp[m][n]
标签:index,遍历,return,nums,self,二叉树,path,amp,root From: https://blog.51cto.com/u_11908275/6953199