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

 

看一个需要使用迭代的,当然,也不是说完全不能用递归,得看面试官的脸!尤其是笔试的时候。。。

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;amp;lt;span class="katex"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="katex-mathml"&amp;amp;amp;amp;amp;amp;gt;O(1)O(1)&amp;amp;amp;amp;amp;amp;lt;span class="katex-html"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="base"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="strut" style="height: 1em; vertical-align: -0.25em;"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="mord mathnormal" style="margin-right: 0.02778em;"&amp;amp;amp;amp;amp;amp;gt;O&amp;amp;amp;amp;amp;amp;lt;span class="mopen"&amp;amp;amp;amp;amp;amp;gt;(&amp;amp;amp;amp;amp;amp;lt;span class="mord"&amp;amp;amp;amp;amp;amp;gt;1&amp;amp;amp;amp;amp;amp;lt;span class="mclose"&amp;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, 需要将它分割成一些子串, 使得每个子串都是回文串.

返回所有可能的分割方案.

 
  1. 不同的方案之间的顺序可以是任意的.
  2. 每种分割方案中的每个子串都必须是 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()

 

 

 

 

不含重复元素和含重复元素的全排列,讲解:

 


全排列:
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 的组合.

 
  1. 在同一个组合中, num 中的每一个数字仅能被使用一次.
  2. 所有数值 (包括 target ) 都是正整数.
  3. 返回的每一个组合内的数字必须是非降序的.
  4. 返回的所有组合之间可以是任意顺序.
  5. 解集不能包含重复的组合.
样例

样例 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 中的某个数字出现次数不限。

  1. 所有数值 (包括 target ) 都是正整数.
  2. 返回的每一个组合内的数字必须是非降序的.
  3. 返回的所有组合之间可以是任意顺序.
  4. 解集不能包含重复的组合.
样例

样例 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;amp;lt;span class="katex"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="katex-mathml"&amp;amp;amp;amp;amp;amp;gt;1&amp;amp;amp;amp;amp;amp;amp;lt;=k&amp;amp;amp;amp;amp;amp;amp;lt;=n1&amp;amp;amp;amp;amp;amp;amp;lt;=k&amp;amp;amp;amp;amp;amp;amp;lt;=n&amp;amp;amp;amp;amp;amp;lt;span class="katex-html"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="base"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="strut" style="height: 0.68354em; vertical-align: -0.0391em;"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="mord"&amp;amp;amp;amp;amp;amp;gt;1&amp;amp;amp;amp;amp;amp;lt;span class="mspace" style="margin-right: 0.277778em;"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="mrel"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;lt;=&amp;amp;amp;amp;amp;amp;lt;span class="mspace" style="margin-right: 0.277778em;"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="base"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="strut" style="height: 0.73354em; vertical-align: -0.0391em;"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="mord mathnormal" style="margin-right: 0.03148em;"&amp;amp;amp;amp;amp;amp;gt;k&amp;amp;amp;amp;amp;amp;lt;span class="mspace" style="margin-right: 0.277778em;"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="mrel"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;lt;=&amp;amp;amp;amp;amp;amp;lt;span class="mspace" style="margin-right: 0.277778em;"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="base"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="strut" style="height: 0.43056em; vertical-align: 0em;"&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;span class="mord mathnormal"&amp;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 · 电话号码的字母组合
描述

给一个不包含01的数字字符串,每个数字代表一个字母,请返回其所有可能的字母组合。

下图的手机按键图,就表示了每个数字可以代表的字母。

12
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的场景
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]

  





posted @ 2019-10-26 10:12  bonelee  阅读(1389)  评论(0编辑  收藏  举报