DFS 算法模板——二叉树的遍历非递归写法要会,排列组合的一定要自己画一颗树,变量i和当前遍历层数计数的start_index要注意区分
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
看一个需要使用迭代的,当然,也不是说完全不能用递归,得看面试官的脸!尤其是笔试的时候。。。
描述
给一棵二叉搜索树,写一个 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
描述
给一棵非空二叉搜索树以及一个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:
解释: 字符串里只有一个字符, 也就只有一种分割方式 (就是它本身)
样例 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()
不含重复元素和含重复元素的全排列,讲解:
全排列: https://www.cnblogs.com/bonelee/p/11668685.html 举例说明,【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] 例子: https://www.cnblogs.com/bonelee/p/11668915.html
接下来看看组合的算法:
不含重复元素的组合算法,无脑式的先排序:
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()
案例参考:
https://www.cnblogs.com/bonelee/p/11668685.html
https://www.cnblogs.com/bonelee/p/11667428.html
分析下为何是上面的样子:
组合:给定一个含不同整数的集合,返回其所有的子集。
https://www.cnblogs.com/bonelee/p/11667428.html 看下面的图,以【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()
给一个不包含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、二叉树的遍历,https://www.cnblogs.com/rnanprince/p/11595380.html,先序中序的递归和迭代写法必须掌握,像算法模板一样记住。后序遍历只掌握递归写法。 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的场景
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]