LogIn
I don't have account.
Sponsored -53%
Zebronics #1 Best Seller

ZEBRONICS Blanc Slim Wireless Mouse — Rechargeable, BT + 2.4GHz (Black)

Up to 1600 DPI · Silent clicks · 63g · Multicolor LED

★★★★★ 4.0 (9,512) 5K+ bought last month
₹376 M.R.P. ₹799Save ₹423
Buy Now
Adℹ

Bottom View of a Binary Tree: DFS vs BFS Approach

DevSniper
30 Views

#tree

#binary-tree

#b-tree

#treemap

#depth-first-search

#breadth-first-search

#tree-algorithms

Amazon Pay offer
Adℹ

You are given a binary tree. Imagine standing directly below it and looking straight up the bottom view is the set of nodes you'd actually be able to see.

More formally: for every possible horizontal position in the tree, the bottom view keeps only the deepest node sitting at that position. Nodes that are hidden behind a deeper node at the same horizontal position don't make the cut.

Given the root of a binary tree, return its bottom view as a list of values, ordered from left to right.

Consider this tree:


                20
              /    \
            8        22
          /   \         \
        5      3         25
              /  \
            10    14

The bottom view of this tree is:

5, 10, 3, 14, 25

Understanding the Problem First

The phrase "horizontal position" is doing all the work here, so let's make it concrete before touching any code.

Picture drawing a vertical line straight down through the root. That's position 0. Every time you move one step to the left child, you shift one position to the left (-1); every step to a right child shifts you one position to the right (+1). This value is usually called the horizontal distance (HD) of a node.

For our tree:

  • 20 sits at HD 0 (it's the root).
  • 8 is 20's left child → HD -1.
  • 22 is 20's right child → HD +1.
  • 5 and 3 are 8's children → HD -2 and HD 0 respectively.
  • 25 is 22's right child → HD +2.
  • 10 and 14 are 3's children → HD -1 and HD +1 respectively.

Now group nodes by HD:

HD Nodes at this HD Depths
-2 5 2
-1 8, 10 1, 3
0 20, 3 0, 2
+1 22, 14 1, 3
+2 25 2

Notice that some HD values have more than one node that's exactly the situation the bottom view rule is meant to resolve. At HD -1, both 8 and 10 sit on the same vertical line, but 10 is further down, so it's the one that would actually be visible from below. Same story at HD 0 (3 wins over 20) and HD +1 (14 wins over 22).

So the real problem boils down to this: for every distinct HD, find the deepest node, then read those winning values off from the smallest HD to the largest.

That reframing is what makes the rest of this article click every approach below is really just a different strategy for visiting nodes and deciding, at each HD, whether the node in hand should replace whatever's currently recorded there.

Setting Up the Tree

We'll use the same tree throughout this article. Here's the plain Java representation:

class TreeNode {
    int val;
    TreeNode left, right;
    TreeNode(int val) {
        this.val = val;
    }
}
public class SampleTree {
    public static TreeNode build() {
        TreeNode n20 = new TreeNode(20);
        TreeNode n8  = new TreeNode(8);
        TreeNode n22 = new TreeNode(22);
        TreeNode n5  = new TreeNode(5);
        TreeNode n3  = new TreeNode(3);
        TreeNode n25 = new TreeNode(25);
        TreeNode n10 = new TreeNode(10);
        TreeNode n14 = new TreeNode(14);

        n20.left = n8;
        n20.right = n22;
        n8.left = n5;
        n8.right = n3;
        n22.right = n25;
        n3.left = n10;
        n3.right = n14;

        return n20;
    }
}

Approach 1: The Naive Attempt (and Why It Breaks)

The most instinctive first attempt looks something like this: walk the tree with a normal DFS, track each node's horizontal distance as you go and every time you land on a node, just write its value into a map keyed by HD overwriting whatever was there before. Whatever's left in the map once the traversal finishes should be the bottom view, right?

It's a reasonable guess and it even seems to work at first glance. The trouble is, it quietly assumes something that isn't actually true: that a node visited later in the traversal is always deeper in the tree. That assumption holds sometimes and fails other times and it's not obvious which case you're in until you trace through a real example.

Tracing Through It

A standard preorder DFS (root, then left subtree, then right subtree) visits our tree in this order:

20 → 8 → 5 → 3 → 10 → 14 → 22 → 25

Walking through this while overwriting the map at each HD:

Visit Node HD Map after this step
1 20 0 {0: 20}
2 8 -1 {0: 20, -1: 8}
3 5 -2 {0: 20, -1: 8, -2: 5}
4 3 0 {0: 3, -1: 8, -2: 5}
5 10 -1 {0: 3, -1: 10, -2: 5}
6 14 +1 {..., +1: 14}
7 22 +1 {..., +1: 22}
8 25 +2 {..., +2: 25}

Look closely at step 7. We had already correctly recorded 14 (depth 3) at HD +1, but then 22 (depth 1) comes along later in the traversal and overwrites it purely because DFS happened to visit 22 after 14, not because 22 is actually deeper.

The reason this happens: preorder DFS finishes exploring the entire left subtree before it ever touches the right subtree. So 22, which lives in the right subtree and is fairly shallow, ends up visited after 14, which lives deep inside the left subtree even though 14 is the one that should win.

The final (wrong) output from this approach is:

5, 10, 3, 22, 25

...when the correct answer is 5, 10, 3, 14, 25. The bug is subtle precisely because it doesn't show up everywhere only at the one HD where a shallow right-side node happens to share a column with a deep left-side node.

The Fix, in One Sentence

We need a way to tell the difference between "this node is new information" and "this node is old information arriving late." That means we can't just track values per HD we need to also track how deep each recorded node actually is.

Approach 2: DFS With Depth Tracking (Correct)

This approach keeps the same DFS traversal, but fixes the blind spot from Approach 1 by remembering, for each HD, not just the node's value but also its depth. When a new node arrives at an HD that's already been recorded, we only let it overwrite the existing entry if it's at the same depth or deeper. A shallower latecomer like 22 no longer gets to bulldoze a correct, deeper answer.

Tracing Through It

Same preorder traversal 20 → 8 → 5 → 3 → 10 → 14 → 22 → 25 but now every entry carries a depth and we check before overwriting:

Visit Node (depth) HD Existing entry at this HD Overwrite? Map after this step
1 20 (0) 0 none add {0: (20, 0)}
2 8 (1) -1 none add {..., -1: (8, 1)}
3 5 (2) -2 none add {..., -2: (5, 2)}
4 3 (2) 0 (20, 0) 2 ≥ 0 → yes {0: (3, 2), ...}
5 10 (3) -1 (8, 1) 3 ≥ 1 → yes {-1: (10, 3), ...}
6 14 (3) +1 none add {..., +1: (14, 3)}
7 22 (1) +1 (14, 3) 1 ≥ 3 → no unchanged 14 survives
8 25 (2) +2 none add {..., +2: (25, 2)}

This time, 22 shows up at step 7, checks the depth of the current occupant at HD +1, sees that 14 is deeper and politely backs off. The final map, read from the smallest HD to the largest, gives us:

5, 10, 3, 14, 25

which finally matches the correct answer.

// Java Code
import java.util.*;

public class BottomViewDFS {

    static class NodeInfo {
        int value;
        int depth;

        NodeInfo(int value, int depth) {
            this.value = value;
            this.depth = depth;
        }
    }

    public static List<Integer> bottomView(TreeNode root) {
        TreeMap<Integer, NodeInfo> hdMap = new TreeMap<>();
        fillMap(root, 0, 0, hdMap);
        List<Integer> result = new ArrayList<>();
        for (NodeInfo info : hdMap.values()) {
            result.add(info.value);
        }
        return result;
    }

    private static void fillMap(TreeNode node, int hd, int depth, TreeMap<Integer, NodeInfo> hdMap) {
        if (node == null) return;
        NodeInfo existing = hdMap.get(hd);
        // Only replace the current occupant if this node is at
        // an equal or deeper level  this is the fix from Approach 1
        if (existing == null || depth >= existing.depth) {
            hdMap.put(hd, new NodeInfo(node.val, depth));
        }
        fillMap(node.left, hd - 1, depth + 1, hdMap);
        fillMap(node.right, hd + 1, depth + 1, hdMap);
    }
    public static void main(String[] args) {
        TreeNode root = SampleTree.build();
        System.out.println(bottomView(root));
        // Output: [5, 10, 3, 14, 25]
    }
}

Complexity

Value
Time O(n log w) every one of the n nodes does a TreeMap lookup and possibly an insert, each costing O(log w), where w is the number of distinct horizontal distances (the tree's width)
Space O(h) for the recursion stack (h = tree height) plus O(w) for the map

This works correctly, but there's still something a little unsatisfying about it we had to bolt on extra bookkeeping (the depth field) just to patch a hole that DFS's traversal order created in the first place. That's usually a hint that a different traversal order might sidestep the problem entirely.

Approach 3: BFS / Level Order Traversal (Optimal)

Here's the observation that makes this approach click: the only reason Approach 1 broke was that DFS doesn't visit nodes in order of depth. A node from a shallow level can get visited after a node from a deep level, simply because of how DFS dives into subtrees.

BFS doesn't have that problem. By definition, a level-order traversal visits every node at depth 0, then every node at depth 1, then depth 2 and so on it is impossible for a shallower node to be visited after a deeper one. Which means: if we just overwrite the map unconditionally as we go, exactly like our buggy Approach 1 did, it will now work correctly because by the time a later node writes over an earlier one, it's guaranteed to be at an equal or greater depth. We get the correctness of Approach 2, without needing to track depth at all.

Tracing Through It

Level order traversal of our tree visits nodes level by level, left to right:

20 → 8 → 22 → 5 → 3 → 25 → 10 → 14

Overwriting the map unconditionally as each node is dequeued:

Visit Node HD Map after this step
1 20 0 {0: 20}
2 8 -1 {0: 20, -1: 8}
3 22 +1 {..., +1: 22}
4 5 -2 {..., -2: 5}
5 3 0 {0: 3, ...}
6 25 +2 {..., +2: 25}
7 10 -1 {-1: 10, ...}
8 14 +1 {+1: 14, ...}

At step 8, 14 overwrites 22 and this time it's guaranteed correct, since 14 belongs to a later (deeper) level than 22 and BFS never processes a deeper level before a shallower one. No depth field, no comparison logic, no risk of a shallow latecomer sneaking in.

Final result, read left to right by HD:

5, 10, 3, 14, 25
// Java Code
import java.util.*;

public class BottomViewBFS {

    static class QueueEntry {
        TreeNode node;
        int hd;
        QueueEntry(TreeNode node, int hd) {
            this.node = node;
            this.hd = hd;
        }
    }

    public static List<Integer> bottomView(TreeNode root) {
        TreeMap<Integer, Integer> hdMap = new TreeMap<>();
        if (root == null) return new ArrayList<>();
        Queue<QueueEntry> queue = new LinkedList<>();
        queue.add(new QueueEntry(root, 0));
        while (!queue.isEmpty()) {
            QueueEntry current = queue.poll();
            TreeNode node = current.node;
            int hd = current.hd;
            // Safe to overwrite unconditionally: BFS guarantees
            // we never see a shallower node after a deeper one
            hdMap.put(hd, node.val);
            if (node.left != null) {
                queue.add(new QueueEntry(node.left, hd - 1));
            }
            if (node.right != null) {
                queue.add(new QueueEntry(node.right, hd + 1));
            }
        }
        return new ArrayList<>(hdMap.values());
    }
    public static void main(String[] args) {
        TreeNode root = SampleTree.build();
        System.out.println(bottomView(root));
        // Output: [5, 10, 3, 14, 25]
    }
}
  • Why TreeMap is used instead of HashMap because the final output must be ordered by horizontal distance (hd), left to right and TreeMap keeps its keys sorted automatically, while HashMap gives no ordering guarantee at all.
  • If you did use a HashMap: you'd have to separately track the min and max hd values seen, then loop from min to max and look up each key manually to reconstruct order. That's more code to get the same result TreeMap gives you for free.

Complexity

Value
Time O(n log w) same asymptotic cost as Approach 2, since each of the n nodes still does one TreeMap operation, but the per-node logic is simpler (no depth comparison needed)
Space O(n) for the queue in the worst case (a very wide tree can have up to roughly n/2 nodes sitting in the queue at once), plus O(w) for the map

Comparing All Approaches

Approach Time Complexity Space Complexity Core Idea
Naive DFS (buggy) O(n log w) O(h) + O(w) Overwrite the map on every visit breaks because DFS can visit a shallow node after a deep one
DFS with Depth Tracking O(n log w) O(h) + O(w) Overwrite only when the new node is at an equal or greater depth
BFS / Level Order Optimal O(n log w) O(n) + O(w) Overwrite unconditionally, but rely on BFS visiting nodes strictly by increasing depth correctness comes for free

n = number of nodes, h = height of the tree, w = width of the tree (number of distinct horizontal distances).

Interestingly, the asymptotic time complexity doesn't actually improve from Approach 2 to Approach 3 both are O(n log w) because of the TreeMap operations. What improves is the simplicity and robustness of the logic: BFS removes an entire category of bugs by construction, rather than patching around them with extra state.

If you want to push the time complexity down to a strict O(n), you can replace the TreeMap with a plain array sized to the tree's width, using the horizontal distance (shifted by an offset so it's never negative) as a direct index. That trades the O(log w) map operations for O(1) array writes, at the cost of a small preprocessing step to figure out the tree's minimum and maximum horizontal distance up front.

Key Takeaways

  1. Don't trust traversal order to also mean "visited in the right priority." DFS visiting a node later doesn't mean that node is more important that assumption is exactly what broke Approach 1.
  2. When a simple approach almost works, ask what invariant it's silently relying on. Approach 1's real bug wasn't the overwrite logic it was assuming DFS order implies depth order, which is only sometimes true.
  3. Sometimes the cleanest fix isn't more logic, it's a different traversal. Approach 2 patches the problem with a depth check; Approach 3 removes the problem by choosing a traversal order (BFS) where that check becomes unnecessary.
  4. This HD-and-map pattern shows up everywhere in tree problems. The exact same horizontal-distance bookkeeping is the backbone of "Vertical Order Traversal" and "Top View of a Binary Tree" once you've internalized it here, those problems become variations on a theme rather than new problems entirely.
Responses (0)

Write a response

CommentHide Comments

No Comments yet.

"},"4":{"id":4,"name":"ZEBRONICS Blanc mouse ad","otherData":"{}","contentFormat":2,"content":"
Sponsored -53%
Zebronics #1 Best Seller

ZEBRONICS Blanc Slim Wireless Mouse — Rechargeable, BT + 2.4GHz (Black)

Up to 1600 DPI · Silent clicks · 63g · Multicolor LED

★★★★★ 4.0 (9,512) 5K+ bought last month
₹376 M.R.P. ₹799Save ₹423
Buy Now
"},"2":{"id":2,"name":"Crousal Ads","otherData":"{}","contentFormat":2,"content":"
Sponsored -34%
iQOO Amazon's Choice

iQOO Z10 Lite 5G — Cyber Green, 4GB RAM, 128GB Storage

Dimensity 6300 · 50MP Sony AI Camera · 6000 mAh · IP64

★★★★★ 4.0 (2,052) 500+ bought last month
₹18,997 M.R.P. ₹28,999Save ₹10,002
Buy Now
"},"1":{"id":1,"name":"Banner Ads","otherData":"{}","contentFormat":2,"content":"\n
\n \n \n \"Amazon\n \n
"}}},"blogDetails":{"isWriter":false,"likes":0,"isLikedByUser":false,"tagIds":[21,27,32,43,133,177,282,316,317,561],"tags":[{"id":21,"name":"Tree","maskingName":"tree","isFeatured":false},{"id":27,"name":"Binary Tree","maskingName":"binary-tree","isFeatured":false},{"id":32,"name":"B-Tree","maskingName":"b-tree","isFeatured":false},{"id":43,"name":"HashMap","maskingName":"hashmap","isFeatured":true},{"id":133,"name":"Coding Problems","maskingName":"coding-problems","isFeatured":true},{"id":177,"name":"DSA","maskingName":"dsa","isFeatured":true},{"id":282,"name":"TreeMap","maskingName":"treemap","isFeatured":false},{"id":316,"name":"DFS","maskingName":"depth-first-search","isFeatured":false},{"id":317,"name":"BFS","maskingName":"breadth-first-search","isFeatured":false},{"id":561,"name":"Tree Algorithms","maskingName":"tree-algorithms","isFeatured":false}],"comments":[],"bookmark":{"isBookmarked":false,"count":0},"author":{"userId":"MjlfNF84LVsyXTJuLXRDX0FOZ2VfX0l0","name":"DevSniper"},"viewCount":29,"showBannerImage":false,"seoTags":"bottom view of binary tree, bottom view of binary tree java, horizontal distance binary tree, vertical order traversal binary tree, top view vs bottom view binary tree, level order traversal java tree, TreeMap binary tree problem, binary tree DFS vs BFS, how to find bottom view of a binary tree, why does DFS fail for bottom view of binary tree, is BFS better than DFS for tree view problems","content":"

You are given a binary tree. Imagine standing directly below it and looking straight up the bottom view is the set of nodes you'd actually be able to see.

\n

More formally: for every possible horizontal position in the tree, the bottom view keeps only the deepest node sitting at that position. Nodes that are hidden behind a deeper node at the same horizontal position don't make the cut.

\n

Given the root of a binary tree, return its bottom view as a list of values, ordered from left to right.

\n

Consider this tree:

\n
\n                20\n              /    \\\n            8        22\n          /   \\         \\\n        5      3         25\n              /  \\\n            10    14\n
\n

The bottom view of this tree is:

\n
5, 10, 3, 14, 25\n
\n

Understanding the Problem First

\n

The phrase \"horizontal position\" is doing all the work here, so let's make it concrete before touching any code.

\n

Picture drawing a vertical line straight down through the root. That's position 0. Every time you move one step to the left child, you shift one position to the left (-1); every step to a right child shifts you one position to the right (+1). This value is usually called the horizontal distance (HD) of a node.

\n

For our tree:

\n\n

Now group nodes by HD:

\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
HDNodes at this HDDepths
-252
-18, 101, 3
020, 30, 2
+122, 141, 3
+2252
\n
\n

Notice that some HD values have more than one node that's exactly the situation the bottom view rule is meant to resolve. At HD -1, both 8 and 10 sit on the same vertical line, but 10 is further down, so it's the one that would actually be visible from below. Same story at HD 0 (3 wins over 20) and HD +1 (14 wins over 22).

\n

So the real problem boils down to this: for every distinct HD, find the deepest node, then read those winning values off from the smallest HD to the largest.

\n

That reframing is what makes the rest of this article click every approach below is really just a different strategy for visiting nodes and deciding, at each HD, whether the node in hand should replace whatever's currently recorded there.

\n

Setting Up the Tree

\n

We'll use the same tree throughout this article. Here's the plain Java representation:

\n
class TreeNode {\n    int val;\n    TreeNode left, right;\n    TreeNode(int val) {\n        this.val = val;\n    }\n}\n
\n
public class SampleTree {\n    public static TreeNode build() {\n        TreeNode n20 = new TreeNode(20);\n        TreeNode n8  = new TreeNode(8);\n        TreeNode n22 = new TreeNode(22);\n        TreeNode n5  = new TreeNode(5);\n        TreeNode n3  = new TreeNode(3);\n        TreeNode n25 = new TreeNode(25);\n        TreeNode n10 = new TreeNode(10);\n        TreeNode n14 = new TreeNode(14);\n\n        n20.left = n8;\n        n20.right = n22;\n        n8.left = n5;\n        n8.right = n3;\n        n22.right = n25;\n        n3.left = n10;\n        n3.right = n14;\n\n        return n20;\n    }\n}\n
\n

Approach 1: The Naive Attempt (and Why It Breaks)

\n

The most instinctive first attempt looks something like this: walk the tree with a normal DFS, track each node's horizontal distance as you go and every time you land on a node, just write its value into a map keyed by HD overwriting whatever was there before. Whatever's left in the map once the traversal finishes should be the bottom view, right?

\n

It's a reasonable guess and it even seems to work at first glance. The trouble is, it quietly assumes something that isn't actually true: that a node visited later in the traversal is always deeper in the tree. That assumption holds sometimes and fails other times and it's not obvious which case you're in until you trace through a real example.

\n

Tracing Through It

\n

A standard preorder DFS (root, then left subtree, then right subtree) visits our tree in this order:

\n
20 → 8 → 5 → 3 → 10 → 14 → 22 → 25\n
\n

Walking through this while overwriting the map at each HD:

\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
VisitNodeHDMap after this step
1200{0: 20}
28-1{0: 20, -1: 8}
35-2{0: 20, -1: 8, -2: 5}
430{0: 3, -1: 8, -2: 5}
510-1{0: 3, -1: 10, -2: 5}
614+1{..., +1: 14}
722+1{..., +1: 22}
825+2{..., +2: 25}
\n
\n

Look closely at step 7. We had already correctly recorded 14 (depth 3) at HD +1, but then 22 (depth 1) comes along later in the traversal and overwrites it purely because DFS happened to visit 22 after 14, not because 22 is actually deeper.

\n

The reason this happens: preorder DFS finishes exploring the entire left subtree before it ever touches the right subtree. So 22, which lives in the right subtree and is fairly shallow, ends up visited after 14, which lives deep inside the left subtree even though 14 is the one that should win.

\n

The final (wrong) output from this approach is:

\n
5, 10, 3, 22, 25\n
\n

...when the correct answer is 5, 10, 3, 14, 25. The bug is subtle precisely because it doesn't show up everywhere only at the one HD where a shallow right-side node happens to share a column with a deep left-side node.

\n

The Fix, in One Sentence

\n

We need a way to tell the difference between \"this node is new information\" and \"this node is old information arriving late.\" That means we can't just track values per HD we need to also track how deep each recorded node actually is.

\n

Approach 2: DFS With Depth Tracking (Correct)

\n

This approach keeps the same DFS traversal, but fixes the blind spot from Approach 1 by remembering, for each HD, not just the node's value but also its depth. When a new node arrives at an HD that's already been recorded, we only let it overwrite the existing entry if it's at the same depth or deeper. A shallower latecomer like 22 no longer gets to bulldoze a correct, deeper answer.

\n

Tracing Through It

\n

Same preorder traversal 20 → 8 → 5 → 3 → 10 → 14 → 22 → 25 but now every entry carries a depth and we check before overwriting:

\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
VisitNode (depth)HDExisting entry at this HDOverwrite?Map after this step
120 (0)0noneadd{0: (20, 0)}
28 (1)-1noneadd{..., -1: (8, 1)}
35 (2)-2noneadd{..., -2: (5, 2)}
43 (2)0(20, 0)2 ≥ 0 → yes{0: (3, 2), ...}
510 (3)-1(8, 1)3 ≥ 1 → yes{-1: (10, 3), ...}
614 (3)+1noneadd{..., +1: (14, 3)}
722 (1)+1(14, 3)1 ≥ 3 → nounchanged 14 survives
825 (2)+2noneadd{..., +2: (25, 2)}
\n
\n

This time, 22 shows up at step 7, checks the depth of the current occupant at HD +1, sees that 14 is deeper and politely backs off. The final map, read from the smallest HD to the largest, gives us:

\n
5, 10, 3, 14, 25\n
\n

which finally matches the correct answer.

\n
// Java Code\nimport java.util.*;\n\npublic class BottomViewDFS {\n\n    static class NodeInfo {\n        int value;\n        int depth;\n\n        NodeInfo(int value, int depth) {\n            this.value = value;\n            this.depth = depth;\n        }\n    }\n\n    public static List<Integer> bottomView(TreeNode root) {\n        TreeMap<Integer, NodeInfo> hdMap = new TreeMap<>();\n        fillMap(root, 0, 0, hdMap);\n        List<Integer> result = new ArrayList<>();\n        for (NodeInfo info : hdMap.values()) {\n            result.add(info.value);\n        }\n        return result;\n    }\n\n    private static void fillMap(TreeNode node, int hd, int depth, TreeMap<Integer, NodeInfo> hdMap) {\n        if (node == null) return;\n        NodeInfo existing = hdMap.get(hd);\n        // Only replace the current occupant if this node is at\n        // an equal or deeper level  this is the fix from Approach 1\n        if (existing == null || depth >= existing.depth) {\n            hdMap.put(hd, new NodeInfo(node.val, depth));\n        }\n        fillMap(node.left, hd - 1, depth + 1, hdMap);\n        fillMap(node.right, hd + 1, depth + 1, hdMap);\n    }\n    public static void main(String[] args) {\n        TreeNode root = SampleTree.build();\n        System.out.println(bottomView(root));\n        // Output: [5, 10, 3, 14, 25]\n    }\n}\n
\n

Complexity

\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Value
TimeO(n log w) every one of the n nodes does a TreeMap lookup and possibly an insert, each costing O(log w), where w is the number of distinct horizontal distances (the tree's width)
SpaceO(h) for the recursion stack (h = tree height) plus O(w) for the map
\n
\n

This works correctly, but there's still something a little unsatisfying about it we had to bolt on extra bookkeeping (the depth field) just to patch a hole that DFS's traversal order created in the first place. That's usually a hint that a different traversal order might sidestep the problem entirely.

\n

Approach 3: BFS / Level Order Traversal (Optimal)

\n

Here's the observation that makes this approach click: the only reason Approach 1 broke was that DFS doesn't visit nodes in order of depth. A node from a shallow level can get visited after a node from a deep level, simply because of how DFS dives into subtrees.

\n

BFS doesn't have that problem. By definition, a level-order traversal visits every node at depth 0, then every node at depth 1, then depth 2 and so on it is impossible for a shallower node to be visited after a deeper one. Which means: if we just overwrite the map unconditionally as we go, exactly like our buggy Approach 1 did, it will now work correctly because by the time a later node writes over an earlier one, it's guaranteed to be at an equal or greater depth. We get the correctness of Approach 2, without needing to track depth at all.

\n

Tracing Through It

\n

Level order traversal of our tree visits nodes level by level, left to right:

\n
20 → 8 → 22 → 5 → 3 → 25 → 10 → 14\n
\n

Overwriting the map unconditionally as each node is dequeued:

\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
VisitNodeHDMap after this step
1200{0: 20}
28-1{0: 20, -1: 8}
322+1{..., +1: 22}
45-2{..., -2: 5}
530{0: 3, ...}
625+2{..., +2: 25}
710-1{-1: 10, ...}
814+1{+1: 14, ...}
\n
\n

At step 8, 14 overwrites 22 and this time it's guaranteed correct, since 14 belongs to a later (deeper) level than 22 and BFS never processes a deeper level before a shallower one. No depth field, no comparison logic, no risk of a shallow latecomer sneaking in.

\n

Final result, read left to right by HD:

\n
5, 10, 3, 14, 25\n
\n
// Java Code\nimport java.util.*;\n\npublic class BottomViewBFS {\n\n    static class QueueEntry {\n        TreeNode node;\n        int hd;\n        QueueEntry(TreeNode node, int hd) {\n            this.node = node;\n            this.hd = hd;\n        }\n    }\n\n    public static List<Integer> bottomView(TreeNode root) {\n        TreeMap<Integer, Integer> hdMap = new TreeMap<>();\n        if (root == null) return new ArrayList<>();\n        Queue<QueueEntry> queue = new LinkedList<>();\n        queue.add(new QueueEntry(root, 0));\n        while (!queue.isEmpty()) {\n            QueueEntry current = queue.poll();\n            TreeNode node = current.node;\n            int hd = current.hd;\n            // Safe to overwrite unconditionally: BFS guarantees\n            // we never see a shallower node after a deeper one\n            hdMap.put(hd, node.val);\n            if (node.left != null) {\n                queue.add(new QueueEntry(node.left, hd - 1));\n            }\n            if (node.right != null) {\n                queue.add(new QueueEntry(node.right, hd + 1));\n            }\n        }\n        return new ArrayList<>(hdMap.values());\n    }\n    public static void main(String[] args) {\n        TreeNode root = SampleTree.build();\n        System.out.println(bottomView(root));\n        // Output: [5, 10, 3, 14, 25]\n    }\n}\n
\n\n

Complexity

\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Value
TimeO(n log w) same asymptotic cost as Approach 2, since each of the n nodes still does one TreeMap operation, but the per-node logic is simpler (no depth comparison needed)
SpaceO(n) for the queue in the worst case (a very wide tree can have up to roughly n/2 nodes sitting in the queue at once), plus O(w) for the map
\n
\n

Comparing All Approaches

\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ApproachTime ComplexitySpace ComplexityCore Idea
Naive DFS (buggy)O(n log w)O(h) + O(w)Overwrite the map on every visit breaks because DFS can visit a shallow node after a deep one
DFS with Depth TrackingO(n log w)O(h) + O(w)Overwrite only when the new node is at an equal or greater depth
BFS / Level Order OptimalO(n log w)O(n) + O(w)Overwrite unconditionally, but rely on BFS visiting nodes strictly by increasing depth correctness comes for free
\n
\n

n = number of nodes, h = height of the tree, w = width of the tree (number of distinct horizontal distances).

\n

Interestingly, the asymptotic time complexity doesn't actually improve from Approach 2 to Approach 3 both are O(n log w) because of the TreeMap operations. What improves is the simplicity and robustness of the logic: BFS removes an entire category of bugs by construction, rather than patching around them with extra state.

\n

If you want to push the time complexity down to a strict O(n), you can replace the TreeMap with a plain array sized to the tree's width, using the horizontal distance (shifted by an offset so it's never negative) as a direct index. That trades the O(log w) map operations for O(1) array writes, at the cost of a small preprocessing step to figure out the tree's minimum and maximum horizontal distance up front.

\n

Key Takeaways

\n
    \n
  1. Don't trust traversal order to also mean \"visited in the right priority.\" DFS visiting a node later doesn't mean that node is more important that assumption is exactly what broke Approach 1.
  2. \n
  3. When a simple approach almost works, ask what invariant it's silently relying on. Approach 1's real bug wasn't the overwrite logic it was assuming DFS order implies depth order, which is only sometimes true.
  4. \n
  5. Sometimes the cleanest fix isn't more logic, it's a different traversal. Approach 2 patches the problem with a depth check; Approach 3 removes the problem by choosing a traversal order (BFS) where that check becomes unnecessary.
  6. \n
  7. This HD-and-map pattern shows up everywhere in tree problems. The exact same horizontal-distance bookkeeping is the backbone of \"Vertical Order Traversal\" and \"Top View of a Binary Tree\" once you've internalized it here, those problems become variations on a theme rather than new problems entirely.
  8. \n
","categoryId":177,"subCategoryId":177,"contentFormat":5,"blogId":201,"userId":"MjlfNF84LVsyXTJuLXRDX0FOZ2VfX0l0","title":"Bottom View of a Binary Tree: DFS vs BFS Approach","url":"bottom-view-of-a-binary-tree-dfs-vs-bfs-approach","bannerImage":"","seoDescription":"Learn to find the bottom view of a binary tree using horizontal distance. Compares a buggy DFS attempt, a fixed DFS and the optimal BFS solution in Java.","generatedOn":"2026-08-26T06:06:48","updatedOn":"2026-08-26T06:06:48"},"popularContents":[{"id":171,"title":"Meesho SDE-1 Backend Interview Experience (Remote | 3 Round) - Rejected","url":"meesho-sde-1-backend-interview-experience-remote-3-round-rejected","description":null,"seoDescription":"Read a real Meesho SDE-1 Backend interview experience covering the online assessment, DSA interview, machine coding round, LLD questions and preparation tips.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4714228Z","viewCount":0},{"id":35,"title":"Cursor vs Copilot : Which AI Coding Assistant Wins in 2025?","url":"cursor-vs-copilot-which-ai-coding-assistant-truly-wins-in-2025","description":null,"seoDescription":"Compare Cursor vs GitHub Copilot on speed, context awareness, multi-file support, pricing and code quality to choose the best AI coding assistant for developers","contentType":5,"generatedOn":"2026-10-14T22:54:09.4714167Z","viewCount":0},{"id":153,"title":"Visa Software Engineer 1 (SDE-1) Interview Experience | CodeSignal OA + Graph-Based Technical Rounds","url":"visa-software-engineer-1-sde-1-interview-experience-codesignal-oa-graph-based-technical-rounds","description":null,"seoDescription":" Ace your Visa SDE-1 interview with this complete interview experience covering CodeSignal OA, graph coding rounds, hiring manager questions and tips.","contentType":5,"generatedOn":"2026-10-14T22:54:09.47142Z","viewCount":0},{"id":168,"title":"Amazon SDE-2 Interview Experience (3 Years Experience)","url":"amazon-sde-2-interview-experience-3-years-experience","description":null,"seoDescription":" Read a real Amazon SDE-2 interview experience covering the online assessment, coding rounds, object-oriented design, leadership principles and system design.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4714212Z","viewCount":0},{"id":83,"title":"Uber SDE 2 Interview Experience (5 Rounds, Selected) - Complete DSA Questions with solution, System Design & Managerial Round","url":"uber-sde-2-interview-experience","description":null,"seoDescription":"Uber SDE 2 interview experience with 5 rounds, real DSA questions, system design, coding round, and preparation tips to crack Uber interviews.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4714185Z","viewCount":0},{"id":34,"title":"System Design Deep Dive: 25 Essential Interview Questions","url":"system-design-deep-dive-25-essential-interview-questions","description":null,"seoDescription":"","contentType":5,"generatedOn":"2026-10-14T22:54:09.4714132Z","viewCount":0},{"id":197,"title":"My Adobe SDE Interview Experience (Aug 2026) 5 Rounds, Selected","url":"my-adobe-sde-interview-experience-aug-2026-5-rounds-selected","description":null,"seoDescription":"Real Adobe SDE interview: 5 rounds, 4 DSA problems (Rotate Array, BSTs, Kth Smallest Matrix), code review & behavioral round. Full solutions + tips.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4714299Z","viewCount":0},{"id":173,"title":"Uber SDE-2 Interview Experience : LLD, HLD, DSA & Hiring Manager Round","url":"uber-sde-2-interview-experience-lld-hld-dsa-hiring-manager-round","description":null,"seoDescription":"Read a real Uber SDE-2 interview experience covering DSA, low-level design, high-level system design, behavioral rounds, hiring freeze and key takeaways.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4714255Z","viewCount":0},{"id":172,"title":"Google L4 Software Engineer III interview experience | Selected","url":"google-l4-software-engineer-iii-interview-experience-selected","description":null,"seoDescription":"Google L4 Software Engineer III interview experience with API design, Googlyness, route matching, graph coding rounds, follow-up questions and tips.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4714241Z","viewCount":0},{"id":163,"title":"Top 50 SQL Interview Questions and Answers (Beginner to Intermediate)","url":"top-sql-interview-questions-and-answers-beginner-to-intermediate","description":null,"seoDescription":"Top 50 SQL Interview Questions and Answers (Beginner to Intermediate). Top SQL Interview Questions for fresher. Frequently asked SQL interview questions with answers","contentType":2,"generatedOn":"2026-10-14T22:54:09.4709357Z","viewCount":0}],"latestContents":[{"id":219,"title":"6 Tools That Made My Life Easier as a Software Engineer","url":"6-tools-that-made-my-life-easier-as-a-software-engineer","description":null,"seoDescription":"The 6 developer tools I use daily as a software engineer: Git, VS Code, Docker, Postman, Chrome DevTools, and AI assistants, with real tips, mistakes, and trade","contentType":5,"generatedOn":"2026-10-14T22:54:09.4709691Z","viewCount":0},{"id":218,"title":"When Should a Business Use Multiple AI Agents Instead of One?","url":"when-should-a-business-use-multiple-ai-agents-instead-of-one","description":null,"seoDescription":"Single agent or multi-agent AI? Learn the 4 signals that justify splitting agents by knowledge, permissions, and risk, plus routing and handoff tips.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4709719Z","viewCount":0},{"id":216,"title":"AI in Clinical Trials Market Estimated to Experience a Hike in Growth by 2035","url":"ai-in-clinical-trials-market-estimated-to-experience-a-hike-in-growth-by-2035","description":null,"seoDescription":"The exclusive information about market dynamics serves as a valuable guide to predict economic scenarios and initiatives taken to enhance future growth. Our mar","contentType":5,"generatedOn":"2026-10-14T22:54:09.4709922Z","viewCount":0},{"id":217,"title":"AI in Clinical Trials Market Estimated to Experience a Hike in Growth by 2035","url":"ai-in-clinical-trials-market-estimated-to-experience-a-hike-in-growth-by-2035","description":null,"seoDescription":"The exclusive information about market dynamics serves as a valuable guide to predict economic scenarios and initiatives taken to enhance future growth. Our mar","contentType":5,"generatedOn":"2026-10-14T22:54:09.470995Z","viewCount":0},{"id":215,"title":"What Happens When an AI Agent Doesn't Know the Answer?","url":"what-happens-when-an-ai-agent-doesnt-know-the-answer","description":null,"seoDescription":"Most AI agents fail quietly. They guess instead of saying \"I don't know.\" Here's how to design better fallback and human-handoff behavior.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4709964Z","viewCount":0},{"id":214,"title":"Greedy Algorithms Explained: How They Work, When They Fail and How to Use Them","url":"greedy-algorithms-explained-how-they-work-when-they-fail-and-how-to-use-them","description":null,"seoDescription":"Learn greedy algorithms step by step with C# code Dijkstra, Huffman coding, knapsack and MSTs plus when greedy fails and how to prove it's correct","contentType":5,"generatedOn":"2026-10-14T22:54:09.4709979Z","viewCount":0},{"id":213,"title":"Amazon SDE II Interview Experience (Bangalore, Selected) ~ Aug 2026","url":"amazon-sde-ii-interview-experience-bangalore-selected-aug-2026","description":null,"seoDescription":"Amazon SDE II interview experience from Bangalore covering 4 rounds, DSA, LLD, system design, Leadership Principles, coding questions and selection.","contentType":5,"generatedOn":"2026-10-14T22:54:09.471002Z","viewCount":0},{"id":212,"title":"How to Get 10x Better AI Answers Without Writing Better Prompts : 10 Proven Techniques","url":"how-to-get-10x-better-ai-answers-without-writing-better-prompts-10-proven-techniques","description":null,"seoDescription":"Get better AI answers without complex prompts. Learn 10 practical techniques using context, examples, tools, feedback, and verification.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4710033Z","viewCount":0},{"id":211,"title":"I Interviewed for a Microsoft SDE I Role. Here's Everything That Happened.","url":"i-interviewed-for-a-microsoft-sde-i-role-heres-everything-that-happened","description":null,"seoDescription":"My real Microsoft SDE I interview experience, all 4 rounds, the exact DSA problems with fully tested solutions, the WhatsApp system design round","contentType":5,"generatedOn":"2026-10-14T22:54:09.4710046Z","viewCount":0},{"id":140,"title":"PolicyBazaar Software Development Engineer (SDE) Interview Experience | DSA, Core CS & System Design","url":"policybazaar-software-development-engineer-sde-interview-experience-dsa-core-cs-system-design","description":null,"seoDescription":"Detailed PolicyBazaar SDE interview experience covering 3 rounds: DSA coding, core CS assessment and system design with key learnings and insights.","contentType":5,"generatedOn":"2026-10-14T22:54:09.471006Z","viewCount":0}],"relatedContents":[{"id":207,"title":"How to Find the Right View of a Binary Tree","url":"right-view-of-a-binary-tree","description":null,"seoDescription":"Learn how to find the right view of a binary tree using DFS and BFS, with examples, code, complexity analysis and coding interview tips.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4772405Z","viewCount":0},{"id":206,"title":"Left View of a Binary Tree: DFS and BFS Approaches Explained","url":"left-view-of-a-binary-tree","description":null,"seoDescription":"Learn how to find the left view of a binary tree using DFS and BFS, with examples, code, complexity analysis, common mistakes and interview tips.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4772437Z","viewCount":0},{"id":205,"title":"Top View of a Binary Tree Using BFS and DFS | Complete Guide","url":"top-view-of-a-binary-tree","description":null,"seoDescription":"Learn how to find the top view of a binary tree using DFS and BFS with step-by-step explanations, examples, code, complexity analysis and interview insights.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4772454Z","viewCount":0},{"id":200,"title":" Longest Playlist With No Repeated Songs : Brute Force to Optimal","url":"longest-playlist-with-no-repeated-songs-brute-force-to-optimal","description":null,"seoDescription":"Find the longest playlist with no repeated songs using brute force, HashSet, and HashMap sliding window techniques, with Java code and dry runs.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4772483Z","viewCount":0},{"id":90,"title":"Contain Virus : Deep, Intuitive & Fully Explained Guide","url":"contain-virus-deep-intuitive-fully-explained-guide","description":null,"seoDescription":"Contain Virus problem explained with DFS + greedy approach, step-by-step simulation, examples and optimized solutions for coding interviews.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4772498Z","viewCount":0},{"id":37,"title":"Find the repeating and missing numbers","url":"find-the-repeating-and-missing-numbers","description":null,"seoDescription":"Find the repeating and missing numbers in an array from 1..n using brute force, hashmap, math and XOR, step-by-step explained with real-world use cases.","contentType":5,"generatedOn":"2026-10-14T22:54:09.4772511Z","viewCount":0},{"id":27,"title":"4 Sum | Find Quads that add up to a target value","url":"4-sum-find-quads-that-add-up-to-a-target-value","description":null,"seoDescription":"","contentType":5,"generatedOn":"2026-10-14T22:54:09.4772523Z","viewCount":0},{"id":26,"title":"Top 50 Most Frequently Asked String Coding Problems (Interview Preparation )","url":"top-50-most-frequently-asked-string-coding-problems-interview-preparation","description":null,"seoDescription":"","contentType":5,"generatedOn":"2026-10-14T22:54:09.4772535Z","viewCount":0},{"id":25,"title":"Intersection of Two Sorted Arrays","url":"intersection-of-two-sorted-arrays","description":null,"seoDescription":"","contentType":5,"generatedOn":"2026-10-14T22:54:09.4772548Z","viewCount":0}]}}},"source":{"isMobile":false}}; window.__CLIENT_RENDER__ = false;