****************************************************************************DFS算法****************************************************************************************************** 深度搜索 ———————————————————————————————————————————————————————————————————————————————————————————————— 二叉树的遍历:https://www.cnblogs.com/rnanprince/p/11595380.html 二叉树-前序&中序:https://www.cnblogs.com/rnanprince/p/11588434.html 二叉树的递归(DFS):https://www.cnblogs.com/rnanprince/p/11595176.html ———————————————————————————————————————————————————————————————————————————————————————————————— 给一棵二叉树, 找到和为最小的子树, 返回其根节点。 public class Solution { private TreeNode subtree = null; private int subtreeSum = Integer.MAX_VALUE; public TreeNode findSubtree(TreeNode root) { helper(root); return subtree; } private int helper(TreeNode root) { if (root == null) { return 0; } int sum = helper(root.left) + helper(root.right) + root.val; if (sum <= subtreeSum) { subtreeSum = sum; subtree = root; } return sum; } } ———————————————————————————————————————————————————————————————————————————————————————————————— 求从根(root)到叶(leaf)的所有路径 Input:{1,2,3,#,5} Output:["1->2->5","1->3"] Explanation: 1 / 2 3 5 public class Solution { public List<String> binaryTreePaths(TreeNode root) { List<String> paths = new ArrayList<>(); if (root == null) { return paths; } List<String> leftPaths = binaryTreePaths(root.left); List<String> rightPaths = binaryTreePaths(root.right); for (String path : leftPaths) { paths.add(root.val + "->" + path); } for (String path : rightPaths) { paths.add(root.val + "->" + path); } // root is a leaf if (paths.size() == 0) { paths.add("" + root.val); } return paths; } } ———————————————————————————————————————————————————————————————————————————————————————————————— 给定一棵二叉树,找到两个节点的最近公共父节点(LCA)。 最近公共祖先是两个节点的公共的祖先节点且具有最大深度。 public class Solution { // 在root为根的二叉树中找A,B的LCA: // 如果找到了就返回这个LCA // 如果只碰到A,就返回A // 如果只碰到B,就返回B // 如果都没有,就返回null public TreeNode lowestCommonAncestor(TreeNode root, TreeNode node1, TreeNode node2) { if (root == null || root == node1 || root == node2) { return root; } // Divide TreeNode left = lowestCommonAncestor(root.left, node1, node2); TreeNode right = lowestCommonAncestor(root.right, node1, node2); // Conquer if (left != null && right != null) { return root; } if (left != null) { return left; } if (right != null) { return right; } return null; } } 1 / 2 3 / / 9 5 8 7 / 6 0 4 参考:九章算法 https://www.jiuzhang.com/ https://www.jiuzhang.com/solutions/minimum-subtree/ https://www.jiuzhang.com/solutions/binary-tree-paths/
深度搜索 适用场景: 输入数据:如果是递归数据结构,如单链表,二叉树,集合,则百分之百可以用深搜;如果是非递归数据结构,如一维数组,二维数组,字符串,图,则概率小一些。 状态转换图:树或者DAG。 求解目标:多阶段存在性问题。必须要走到最深(例如对于树,必须要走到叶子节点)才能得到一个解,这种情况适合用深搜。 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); 恢复path,排列类的题目恢复交换元素 } } -------------------------------- 过程分析: cur =0 | |/ “catsanddog” {"a", "abnormal", "cat", "cats", "sand", "dog", "and"} == > ["cats,and,dog", "cat","sand", "dog"] cur / cat cats | | sand and | | dog dog "hello" ==> "hello" import sys import json def load_data(): data = json.loads(sys.stdin.read()) return data["string"], set(data["word_list"]) 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 = load_data() result, path = [], [] cur = 0 dfs(string, cur, word_dict, path, result) result.sort() print(json.dumps({'result': result}))
dfs先序遍历题目: (1)二叉树的所有路径:用到第一次说到的模板 (2)将二叉树 转为 链表 https://www.cnblogs.com/bonelee/p/11610194.html https://www.cnblogs.com/bonelee/p/11609991.html dfs中序遍历题目: 需要使用中序遍历迭代写法的: https://www.cnblogs.com/bonelee/p/11610292.html 后序遍历题目: https://www.cnblogs.com/bonelee/p/11609953.html 遍历过程中需要记住上次遍历节点才能得到结果的: https://www.cnblogs.com/bonelee/p/11610047.html 算法模板: class Solution(): last_node = None result = None def run(self, root): self.dfs(root) return self.result def dfs(self): # 放在最前面就是先序 if last_node is None: last_node = root else: do_sth(last_node, root) last_node = root dfs(root.left) dfs(root.right)
BST定义: https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91/7077855?fr=aladdin BST中搜索特定值: https://www.cnblogs.com/bonelee/p/11624355.html 搜索接近的值: https://www.cnblogs.com/bonelee/p/11632584.html 【1,3,5,8,88,101,234】, 89
基于排列的深度优先搜索 https://blog.csdn.net/qq_19446965/article/details/102463792 问题模型:求出所有满足条件的“排列”。 判断条件:组合中的元素是顺序“相关”的。 时间复杂度:与 n! 相关。 //全排列-A(n,m) def recursive_a(m, path_list, original_list, result_list): if m == 0: result_list.append(list(path_list)) return for i in range(len(original_list)): path_list.append(original_list[i]) temp_list = deepcopy(original_list) temp_list.pop(i) recursive_a(m - 1, path_list, temp_list, result_list) path_list.pop() 求A(4,3) original_list = [1, 2, 3, 4] result_list = [[1, 2, 3], [1, 2, 4], [1, 3, 2], [1, 3, 4], [1, 4, 2], [1, 4, 3], [2, 1, 3], [2, 1, 4], [2, 3, 1], [2, 3, 4], [2, 4, 1], [2, 4, 3], [3, 1, 2], [3, 1, 4], [3, 2, 1], [3, 2, 4], [3, 4, 1], [3, 4, 2], [4, 1, 2], [4, 1, 3], [4, 2, 1], [4, 2, 3], [4, 3, 1], [4, 3, 2]] 共24种 https://blog.csdn.net/qq_19446965/article/details/102463792 给定一个可包含重复数字的序列,返回所有不重复的全排列。 示例: 输入: [1,1,2] 输出: [ [1,1,2], [1,2,1], [2,1,1] ] 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/permutations-ii 先生成再去重 def helper(self, nums, res, path): if not nums and path not in res: res.append(path) else: for i in range(len(nums)): self.helper(nums[:i] + nums[i+1:], res, path + [nums[i]]) 耗时:1356 ms 这样的做法是在每个path生成之后才去做的判断,因此效率一点都不高。 深度拷贝: def helper(self, nums, res, path): if not nums and path not in res: res.append(list(path)) return for i in range(len(nums)): path.append(nums[i]) temp_list = deepcopy(nums) temp_list.pop(i) self.helper(temp_list, res, path) path.pop() 耗时:超时 回溯法 下面这个做法是标准的回溯法,需要用到visited来表示哪些位置已经被添加到path中了。 为什么有重复? 在这个例子中,我们在第一个1开始的排列中已经取了第二个1的情况;如果在第二个1开始的排列中仍然取第一个1,就有重复了。 如何去重呢? 1)排序 2)不是第一个数字,并且现在的数字和前面的数字相等,同时前面的数字还没有访问过,我们是不能搜索的,需要直接返回。 原因是,这种情况下,必须是由前面搜索到现在的这个位置,而不能是由现在的位置向前面搜索。 def helper(self, nums, res, path, visit): if len(path) == len(nums) : res.append(path) for i in range(len(nums)): if i > 0 and visit[i - 1] and nums[i - 1] == nums[i]: continue if not visit[i]: visit[i] = True self.helper(nums, res, path+[nums[i]], visit) visit[i] = False 耗时:56 ms 交换思想: def swap(nums, i, cur): nums[cur], nums[i] = nums[i], nums[cur] def helper_1(cur, nums, res): if cur == len(nums): res.append(list(nums)) for i in range(cur, len(nums)): if nums[i] not in nums[i+1:]: swap(nums, i, cur) helper(cur + 1, nums, res) swap(nums, i, cur) 耗时:44 ms 题目描述 给定一个"HH:MM"格式的时间,重复使用这些数字,返回下一个最近的时间。每个数字可以被重复使用任意次。 保证输入的时间都是有效的。例如,"01:34","12:09" 都是有效的,而"1:34","12:9"都不是有效的时间。