问题是找到二叉树的最小深度,以便在以下树上运行:
* :Level 1
/ \
* * :Level 2
/ \
* NULL :Level 3
将最小深度返回2。
根据leetcode,我能够获得一个优于其他解决方案的100%的递归解决方案,这对我来说没有意义,因为如果必须访问每个节点的每个子节点,怎么可能这么快(DFS的实现) )。
相反,我决定以BFS方式而不是DFS方式进行操作,并检查每个级别中是否有没有子级的节点,并且该节点是最小深度的。
这是我的递归解决方案:
public int minDepth(TreeNode root) {
if (root == null)
return 0;
else if (root.left == null)
return minDepth(root.right) + 1;
else if (root.right == null)
return minDepth(root.left) + 1;
else
return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
}
这是我的关卡解决方案:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int minDepth(TreeNode root) {
ArrayList<TreeNode> q = new ArrayList<TreeNode>(); // We will store each new node here
q.add(root); // add the root to our queue
return root != null ? minDepthHelper(q, 0) : 0; // If the root node is null, dont bother and return 0
}
private int minDepthHelper(ArrayList<TreeNode> q, int depth){
if(q.size() == 0) // Empty queue means nothing to do, so return
return depth;
int size = q.size(); // How many we will need to pop (the parents)
for(int i = 0; i < size; i++){
TreeNode curr = q.get(0); // FIFO!
if(curr.left == null && curr.right == null){
return depth +1; // If you have no children, then you are a leaf so return your depth.
// nodes 0 through size are on the same level, so any of them , if they have
// no children, will return the same value which will be min depth.
}else{
// Add only non-null children!
if(curr.left != null)
q.add(curr.left);
if(curr.right != null)
q.add(curr.right);
}
q.remove(0);
}
// Will only reach here if no nodes in level depth have no right and no left
return minDepthHelper(q, depth+1);
}
}
有人可以解释一下为什么第二个解决方案比较慢的原因,尽管它应该进行较少的比较?
答案 0 :(得分:1)
我不会对LeetCode百分比施加太大的负担。当我提交您的递归解决方案时,它显示胜过34%。 LeetCode还显示100%和34%段的完全相同的示例代码。只能猜测他们的测试用例到底是什么。我提交的所有实现都在1毫秒内运行,因此,他们的所有41个测试用例都是很小的树,因此性能差异可以忽略不计。您也不知道哪种树结构在示例情况中占主导地位-它们可能或多或少都是最差情况下的时间复杂度,在这种情况下,BFS与DFS相比几乎没有优势。
考虑到这一点,让我们在大型测试用例上对代码进行基准测试,看看是否可以获得对LeetCode提供的黑盒测试环境无法获得的了解。
在开始之前,让我们检查一下您的BFS解决方案,该解决方案使用递归并像对待队列一样操作ArrayList。确实,将ArrayList移位操作摊销为O(1),但是使用ArrayDeque是Java排队操作中更快,更语义上合适的数据结构。
此外,通过在BFS实现中使用递归,您将否定BFS的主要好处之一,那就是它是迭代的。不必操纵调用堆栈将减少很多开销。
综合考虑,我将编写BFS函数,如:
public int minDepth(TreeNode root) {
ArrayDeque<Pair<TreeNode, Integer>> q = new ArrayDeque<>();
q.offer(new Pair(root, 1));
while (!q.isEmpty()) {
Pair<TreeNode, Integer> curr = q.poll();
if (curr.first != null) {
if (curr.first.left == null && curr.first.right == null) {
return curr.second;
}
q.offer(new Pair(curr.first.left, curr.second + 1));
q.offer(new Pair(curr.first.right, curr.second + 1));
}
}
return 0;
}
现在,快速进行基准测试。在这里,BFS2
是您的BFS实现,BFS
是我的:
long BFSTotal = 0;
long BFS2Total = 0;
long DFSTotal = 0;
for (int i = 0; i < 10000; i++) {
TreeNode root = randomTree(10000);
long start = System.currentTimeMillis();
minDepthDFS(root);
DFSTotal += System.currentTimeMillis() - start;
start = System.currentTimeMillis();
minDepthBFS(root);
BFSTotal += System.currentTimeMillis() - start;
start = System.currentTimeMillis();
minDepthBFS2(root);
BFS2Total += System.currentTimeMillis() - start;
}
System.out.println("BFS: " + BFSTotal);
System.out.println("BFS2: " + BFS2Total);
System.out.println("DFS: " + DFSTotal);
此代码创建10000个不同的树,每个树有10000个节点,这些节点使用抛硬币算法创建,并在每棵树上运行算法。这是几次运行的结果:
BFS: 1906
BFS2: 5484
DFS: 3351
BFS: 1709
BFS2: 6101
DFS: 3773
BFS: 1527
BFS2: 5567
DFS: 3856
继续,run the code yourself,然后尝试一下。我不认为这些结果是绝对结论性的,但它们确实加强了我的基本前提:BFS击败DFS,因为其开销较小且有可能提早保释(最坏的情况是时间复杂度相同),以及使用递归的非递归BFS实现高效的数据结构优于使用低效的数据结构的递归BFS实现。
这也表明您的BFS实现速度大约是DFS实现速度的两倍,这可以解释您的LeetCode结果,但是同样地,考虑到它们的小巧之处,我还是犹豫不决。测试用例似乎是这样。