2445. Number of Nodes With Value One 🔒
Problem Description
You have a binary tree with n
nodes labeled from 1
to n
. The tree structure follows a specific pattern:
- Node
1
is the root - For any node with label
v
, its parent is the node with labelfloor(v / 2)
- This means node
2
and node3
are children of node1
, nodes4
and5
are children of node2
, and so on
Initially, all nodes have a value of 0
.
You're given an array queries
where each queries[i]
represents a node label. For each query, you need to flip the values in the entire subtree rooted at that node (including the node itself). Flipping means changing 0
to 1
and 1
to 0
.
After processing all queries in order, you need to return the total count of nodes that have value 1
.
Example of tree structure when n = 7
:
- Node
1
is the root - Node
3
has parentfloor(3/2) = 1
- Node
7
has parentfloor(7/2) = 3
The tree forms a complete binary tree pattern where:
- Node
i
has left child2*i
and right child2*i + 1
(if they exist within range1
ton
)
Key insight from the solution: If a node appears an even number of times in the queries array, the flips cancel out (flipping twice returns to original state). Only nodes that appear an odd number of times in queries need their subtrees flipped. The solution uses Counter
to count query frequencies and only processes nodes with odd counts, using DFS to flip entire subtrees efficiently.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The problem involves a tree structure with nodes and edges. Each node has a parent-child relationship defined by
parent = floor(node/2)
, forming a binary tree which is a special type of graph.
Is it a tree?
- Yes: The problem explicitly states we have an undirected connected tree with
n
nodes andn-1
edges. The structure follows a binary tree pattern where node 1 is the root and each node's children are2*node
and2*node + 1
.
DFS
- We arrive at DFS as the solution approach.
Conclusion: The flowchart correctly leads us to use DFS (Depth-First Search) for this problem. This makes perfect sense because:
- We need to traverse entire subtrees for each query operation
- When we flip a node's value, we must recursively flip all values in its subtree
- DFS is the natural choice for exploring all nodes in a subtree before moving to other parts of the tree
- The solution implements DFS through the recursive
dfs(i)
function that:- Flips the current node's value with
tree[i] ^= 1
- Recursively calls itself on the left child
dfs(i << 1)
- Recursively calls itself on the right child
dfs(i << 1 | 1)
- Flips the current node's value with
The DFS pattern perfectly fits this problem as we need to perform operations on entire subtrees, which requires visiting all descendants of a given node - a classic use case for depth-first traversal.
Intuition
The key insight starts with recognizing that flipping values in a subtree is a toggle operation - doing it twice returns to the original state. If we flip a subtree an even number of times, it's as if we never flipped it at all. Only odd numbers of flips actually change the final state.
This leads to our first optimization: instead of processing queries sequentially, we can count how many times each node appears in the queries array. If node i
appears 3 times, that's equivalent to flipping it once. If it appears 4 times, we don't need to flip it at all.
The second insight comes from the tree structure. The problem defines a complete binary tree where:
- Node
i
has left child2*i
(which isi << 1
in binary) - Node
i
has right child2*i + 1
(which isi << 1 | 1
in binary)
This binary relationship makes traversing the tree extremely efficient using bit operations.
When we need to flip a subtree, DFS is the natural choice because:
- We start at the query node and need to visit every descendant
- The recursive nature of DFS mirrors the recursive structure of the tree
- We can flip the current node, then recursively flip left and right subtrees
The final piece is realizing we can use an array to track node values since nodes are labeled from 1
to n
. This gives us O(1) access to any node's value.
Putting it all together: Count query frequencies → Process only nodes with odd counts → Use DFS to flip each subtree → Count the final number of 1
s. The XOR operation (^= 1
) elegantly handles the flip since 0 ^ 1 = 1
and 1 ^ 1 = 0
.
Learn more about Tree, Depth-First Search, Breadth-First Search and Binary Tree patterns.
Solution Approach
The implementation follows the optimization strategy mentioned in the reference: only process nodes that appear an odd number of times in the queries.
Step 1: Count Query Frequencies
cnt = Counter(queries)
We use Python's Counter
to count how many times each node appears in the queries array. This allows us to identify which nodes need to be processed.
Step 2: Initialize the Tree
tree = [0] * (n + 1)
Create an array of size n+1
to represent node values. Index i
corresponds to node i
(we ignore index 0 for simplicity). Initially, all values are 0
.
Step 3: Define the DFS Function
def dfs(i):
if i > n:
return
[tree](/problems/tree_intro)[i] ^= 1
dfs(i << 1)
dfs(i << 1 | 1)
The DFS function recursively flips values in a subtree:
- Base case: If node index exceeds
n
, we've gone beyond the tree bounds - Flip current node:
tree[i] ^= 1
toggles between 0 and 1 - Recursively process left child:
i << 1
(equivalent to2*i
) - Recursively process right child:
i << 1 | 1
(equivalent to2*i + 1
)
Step 4: Process Nodes with Odd Query Counts
for i, v in cnt.items(): if v & 1: dfs(i)
Iterate through the frequency counter. For each node i
that appears v
times:
- Check if
v
is odd using bitwise AND:v & 1
returns 1 for odd numbers, 0 for even - If odd, call
dfs(i)
to flip the entire subtree rooted at nodei
Step 5: Count Final Result
return sum([tree](/problems/tree_intro))
Sum all values in the tree array to get the total number of nodes with value 1
.
Time Complexity: O(n × q) in worst case where q is the number of unique nodes in queries, as each DFS traversal visits all nodes in a subtree.
Space Complexity: O(n) for the tree array and O(log n) for the recursive call stack depth.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with n = 7
and queries = [1, 3, 3]
.
Initial Setup: The tree structure looks like this:
1(0) / \ 2(0) 3(0) / \ / \ 4(0) 5(0) 6(0) 7(0)
Numbers in parentheses show node values (all initially 0).
Step 1: Count Query Frequencies
cnt = Counter([1, 3, 3]) # Result: {1: 1, 3: 2}
- Node 1 appears 1 time (odd)
- Node 3 appears 2 times (even)
Step 2: Process Only Odd-Count Nodes Since node 3 appears an even number of times, its flips cancel out. We only need to process node 1.
Step 3: DFS from Node 1 Starting DFS at node 1:
dfs(1): - Flip node 1: 0 → 1 - Call dfs(2) for left child - Call dfs(3) for right child dfs(2): - Flip node 2: 0 → 1 - Call dfs(4) for left child - Call dfs(5) for right child dfs(4): - Flip node 4: 0 → 1 - Call dfs(8) → returns (8 > 7) - Call dfs(9) → returns (9 > 7) dfs(5): - Flip node 5: 0 → 1 - Call dfs(10) → returns (10 > 7) - Call dfs(11) → returns (11 > 7) dfs(3): - Flip node 3: 0 → 1 - Call dfs(6) for left child - Call dfs(7) for right child dfs(6): - Flip node 6: 0 → 1 - Call dfs(12) → returns (12 > 7) - Call dfs(13) → returns (13 > 7) dfs(7): - Flip node 7: 0 → 1 - Call dfs(14) → returns (14 > 7) - Call dfs(15) → returns (15 > 7)
Final Tree State:
1(1) / \ 2(1) 3(1) / \ / \ 4(1) 5(1) 6(1) 7(1)
Step 4: Count Result
sum(tree) = 7 # All nodes have value 1
The key optimization: We avoided processing node 3's subtree twice (which would have flipped it back to 0) by recognizing it appears an even number of times in the queries.
Solution Implementation
1class Solution:
2 def numberOfNodes(self, n: int, queries: List[int]) -> int:
3 """
4 Count the number of nodes that have been toggled an odd number of times
5 after processing all queries on a complete binary tree.
6
7 Args:
8 n: The total number of nodes in the tree (1-indexed)
9 queries: List of node indices to toggle (including all descendants)
10
11 Returns:
12 The count of nodes that are in the "on" state (toggled odd times)
13 """
14
15 def toggle_subtree(node_index: int) -> None:
16 """
17 Toggle the state of a node and all its descendants recursively.
18
19 Args:
20 node_index: The index of the current node to process
21 """
22 # Base case: stop if node index exceeds the tree size
23 if node_index > n:
24 return
25
26 # Toggle current node's state (0 -> 1 or 1 -> 0)
27 node_states[node_index] ^= 1
28
29 # Recursively toggle left child and its subtree
30 toggle_subtree(node_index * 2)
31
32 # Recursively toggle right child and its subtree
33 toggle_subtree(node_index * 2 + 1)
34
35 # Initialize all nodes to "off" state (0)
36 # Index 0 is unused, nodes are 1-indexed
37 node_states = [0] * (n + 1)
38
39 # Count frequency of each node in queries
40 query_frequency = Counter(queries)
41
42 # Process each unique node that appears in queries
43 for node, frequency in query_frequency.items():
44 # Only toggle if the node appears an odd number of times
45 # (even number of toggles cancel out)
46 if frequency % 2 == 1:
47 toggle_subtree(node)
48
49 # Return the total number of nodes in "on" state
50 return sum(node_states)
51
1class Solution {
2 // Array to track the state of each node in the tree (0 or 1)
3 private int[] treeState;
4
5 /**
6 * Counts the number of nodes with value 1 after processing all queries.
7 * Each query toggles the state of a node and all its descendants in a binary tree.
8 *
9 * @param n The number of nodes in the tree (nodes are numbered from 1 to n)
10 * @param queries Array of node indices to toggle
11 * @return The total number of nodes with value 1 after all queries
12 */
13 public int numberOfNodes(int n, int[] queries) {
14 // Initialize tree state array with size n+1 (index 0 unused, nodes 1 to n)
15 treeState = new int[n + 1];
16
17 // Count the frequency of each node in queries
18 int[] queryCount = new int[n + 1];
19 for (int nodeIndex : queries) {
20 queryCount[nodeIndex]++;
21 }
22
23 // Process nodes that appear an odd number of times in queries
24 // (even number of toggles cancel out, so we only process odd counts)
25 for (int nodeIndex = 0; nodeIndex <= n; nodeIndex++) {
26 if (queryCount[nodeIndex] % 2 == 1) {
27 toggleSubtree(nodeIndex);
28 }
29 }
30
31 // Count the total number of nodes with value 1
32 int totalNodesWithOne = 0;
33 for (int nodeIndex = 0; nodeIndex <= n; nodeIndex++) {
34 totalNodesWithOne += treeState[nodeIndex];
35 }
36
37 return totalNodesWithOne;
38 }
39
40 /**
41 * Toggles the state of a node and all its descendants in the binary tree.
42 * Uses XOR operation to toggle: 0 becomes 1, 1 becomes 0.
43 *
44 * @param nodeIndex The index of the node to start toggling from
45 */
46 private void toggleSubtree(int nodeIndex) {
47 // Base case: if node index is out of bounds, return
48 if (nodeIndex >= treeState.length) {
49 return;
50 }
51
52 // Toggle current node's state using XOR
53 treeState[nodeIndex] ^= 1;
54
55 // Recursively toggle left child (2 * nodeIndex)
56 toggleSubtree(nodeIndex << 1);
57
58 // Recursively toggle right child (2 * nodeIndex + 1)
59 toggleSubtree((nodeIndex << 1) | 1);
60 }
61}
62
1class Solution {
2public:
3 int numberOfNodes(int n, vector<int>& queries) {
4 // Array to track the state of each node (0 or 1)
5 vector<int> nodeStates(n + 1);
6
7 // Count frequency of each node in queries
8 vector<int> queryFrequency(n + 1);
9 for (int nodeId : queries) {
10 ++queryFrequency[nodeId];
11 }
12
13 // DFS function to flip the state of a node and all its descendants
14 function<void(int)> flipSubtree = [&](int nodeId) {
15 // Base case: if node doesn't exist in the tree
16 if (nodeId > n) {
17 return;
18 }
19
20 // Flip the current node's state (XOR with 1)
21 nodeStates[nodeId] ^= 1;
22
23 // Recursively flip left child (2 * nodeId)
24 flipSubtree(nodeId << 1);
25
26 // Recursively flip right child (2 * nodeId + 1)
27 flipSubtree((nodeId << 1) | 1);
28 };
29
30 // Process each node that appears an odd number of times in queries
31 for (int nodeId = 0; nodeId <= n; ++nodeId) {
32 // If a node appears odd times, flip its subtree
33 if (queryFrequency[nodeId] & 1) {
34 flipSubtree(nodeId);
35 }
36 }
37
38 // Count and return the total number of nodes with state 1
39 return accumulate(nodeStates.begin(), nodeStates.end(), 0);
40 }
41};
42
1function numberOfNodes(n: number, queries: number[]): number {
2 // Array to track the state of each node (0 or 1)
3 const nodeStates: number[] = new Array(n + 1).fill(0);
4
5 // Count frequency of each node in queries
6 const queryFrequency: number[] = new Array(n + 1).fill(0);
7 for (const nodeId of queries) {
8 queryFrequency[nodeId]++;
9 }
10
11 // DFS function to flip the state of a node and all its descendants
12 const flipSubtree = (nodeId: number): void => {
13 // Base case: if node doesn't exist in the tree
14 if (nodeId > n) {
15 return;
16 }
17
18 // Flip the current node's state (XOR with 1)
19 nodeStates[nodeId] ^= 1;
20
21 // Recursively flip left child (2 * nodeId)
22 flipSubtree(nodeId << 1);
23
24 // Recursively flip right child (2 * nodeId + 1)
25 flipSubtree((nodeId << 1) | 1);
26 };
27
28 // Process each node that appears an odd number of times in queries
29 for (let nodeId = 0; nodeId <= n; nodeId++) {
30 // If a node appears odd times, flip its subtree
31 if (queryFrequency[nodeId] & 1) {
32 flipSubtree(nodeId);
33 }
34 }
35
36 // Count and return the total number of nodes with state 1
37 return nodeStates.reduce((sum, state) => sum + state, 0);
38}
39
Time and Space Complexity
Time Complexity: O(n × log n)
The algorithm iterates through unique queries in the Counter. For each query that appears an odd number of times, it calls the dfs
function starting from that node. In the worst case, the dfs
function traverses from a node down to all its descendants in the complete binary tree.
For a complete binary tree with n
nodes:
- The maximum depth is
O(log n)
- From any node
i
, the number of nodes in its subtree (including itself) is at mostn/i
- When we sum up all possible DFS traversals, considering that each node can be visited multiple times from different ancestors, the total operations across all queries is bounded by
O(n × log n)
This is because each node at depth d
can be reached from at most d
ancestors (nodes on the path from root to this node), and with maximum depth being O(log n)
, the total work done is O(n × log n)
.
Space Complexity: O(n)
The space complexity consists of:
- The
tree
array of sizen + 1
:O(n)
- The Counter object storing unique queries:
O(min(len(queries), n))
which is bounded byO(n)
- The recursive call stack for
dfs
:O(log n)
for the maximum depth of recursion
The dominant term is O(n)
from the tree
array, giving us an overall space complexity of O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling the Tree Index Bounds Correctly
A frequent mistake is forgetting that the tree is 1-indexed while also having a maximum of n
nodes. This can lead to two types of errors:
Pitfall Example:
# Wrong: Using 0-indexed array
tree = [0] * n # Missing one element!
# Wrong: Incorrect boundary check
def dfs(i):
if i >= n: # Should be i > n
return
tree[i] ^= 1
# ...
Solution:
# Correct: Account for 1-indexing
tree = [0] * (n + 1) # Extra element for 1-indexing
def dfs(i):
if i > n: # Correct boundary check
return
tree[i] ^= 1
# ...
2. Processing All Queries Instead of Optimizing
The naive approach of processing each query individually leads to unnecessary work when the same node appears multiple times:
Pitfall Example:
# Inefficient: Processing each query separately
for query in queries:
dfs(query)
return sum(tree)
This approach performs redundant flips when a node appears an even number of times (the flips cancel out).
Solution:
# Efficient: Only process nodes with odd occurrence count cnt = Counter(queries) for node, frequency in cnt.items(): if frequency % 2 == 1: # Only odd counts matter dfs(node)
3. Using Incorrect Child Node Calculation
In a complete binary tree, the child indices must be calculated precisely:
Pitfall Example:
def dfs(i):
if i > n:
return
tree[i] ^= 1
dfs(i * 2 - 1) # Wrong: This isn't the left child
dfs(i * 2) # Wrong: This would be left child, not right
Solution:
def dfs(i):
if i > n:
return
tree[i] ^= 1
dfs(i * 2) # Correct: Left child
dfs(i * 2 + 1) # Correct: Right child
# Or using bitwise operations:
# dfs(i << 1) # Left child
# dfs(i << 1 | 1) # Right child
4. Stack Overflow for Large Trees
For very large values of n
, the recursive DFS might cause stack overflow:
Solution using iterative approach:
def toggle_subtree_iterative(root):
stack = [root]
while stack:
node = stack.pop()
if node > n:
continue
tree[node] ^= 1
stack.append(node * 2)
stack.append(node * 2 + 1)
5. Misunderstanding the Flip Operation
Some might incorrectly implement the flip as setting to 1 rather than toggling:
Pitfall Example:
def dfs(i):
if i > n:
return
tree[i] = 1 # Wrong: This sets to 1, doesn't toggle!
# ...
Solution:
def dfs(i):
if i > n:
return
tree[i] ^= 1 # Correct: XOR toggles between 0 and 1
# Or alternatively:
# tree[i] = 1 - tree[i]
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!