Fenwick Tree Fundamentals
What is a Fenwick Tree?
A Fenwick Tree (also called Binary Indexed Tree or BIT) is a data structure that efficiently supports:
- Point updates: Add a value to an element
- Prefix queries: Query sum from index 0 to i
Both operations in O(log n) time with O(n) space.
Key Insight: Binary Representation
The magic of Fenwick Tree is using the binary representation of indices:
Index: 1 2 3 4 5 6 7 8
Binary: 001 010 011 100 101 110 111 1000
BIT[1] covers: [1]
BIT[2] covers: [1,2]
BIT[3] covers: [3]
BIT[4] covers: [1,2,3,4]
BIT[5] covers: [5]
BIT[6] covers: [5,6]
BIT[7] covers: [7]
BIT[8] covers: [1,2,3,4,5,6,7,8]
Implementation
public class FenwickTree {
private int[] tree;
private int n;
public FenwickTree(int size) {
n = size;
tree = new int[n + 1]; // 1-indexed
}
public FenwickTree(int[] nums) {
n = nums.length;
tree = new int[n + 1];
for (int i = 0; i < n; i++) {
update(i, nums[i]);
}
}
// Add val to index i (0-indexed)
public void update(int i, int val) {
i++; // Convert to 1-indexed
while (i <= n) {
tree[i] += val;
i += i & (-i); // Add lowest set bit
}
}
// Query prefix sum from 0 to i (0-indexed)
public int query(int i) {
i++; // Convert to 1-indexed
int sum = 0;
while (i > 0) {
sum += tree[i];
i -= i & (-i); // Remove lowest set bit
}
return sum;
}
// Query range sum from left to right (0-indexed)
public int rangeQuery(int left, int right) {
return query(right) - query(left - 1);
}
}
The XOR Trick
i & (-i) gives the lowest set bit:
- If i = 6 (110), then -i = ...010, so i & (-i) = 2 (010)
- This tells us how far the current node reaches
Time & Space Complexity
- Build (from array): O(n log n) or O(n) with optimized build
- Update: O(log n)
- Query: O(log n)
- Space: O(n)
Advanced Fenwick Tree Applications
2D Fenwick Tree
For 2D range queries and updates:
public class FenwickTree2D {
private int[][] tree;
private int rows, cols;
public FenwickTree2D(int m, int n) {
rows = m;
cols = n;
tree = new int[m + 1][n + 1];
}
public void update(int row, int col, int val) {
for (int i = row + 1; i <= rows; i += i & (-i)) {
for (int j = col + 1; j <= cols; j += j & (-j)) {
tree[i][j] += val;
}
}
}
public int query(int row, int col) {
int sum = 0;
for (int i = row + 1; i > 0; i -= i & (-i)) {
for (int j = col + 1; j > 0; j -= j & (-j)) {
sum += tree[i][j];
}
}
return sum;
}
public int rangeQuery(int r1, int c1, int r2, int c2) {
return query(r2, c2) - query(r1 - 1, c2) - query(r2, c1 - 1) + query(r1 - 1, c1 - 1);
}
}
Counting Inversions with Fenwick Tree
public int countInversions(int[] nums) {
// Coordinate compression
int[] sorted = nums.clone();
Arrays.sort(sorted);
Map<Integer, Integer> map = new HashMap<>();
int rank = 1;
for (int num : sorted) {
map.putIfAbsent(num, rank++);
}
FenwickTree ft = new FenwickTree(rank);
int inversions = 0;
// Process from right to left
for (int i = nums.length - 1; i >= 0; i--) {
int r = map.get(nums[i]);
inversions += ft.query(r - 1); // Count smaller elements to the right
ft.update(r, 1);
}
return inversions;
}
Dynamic Frequency Counting
// Count numbers in range [left, right] that have been added so far
class FrequencyCounter {
private FenwickTree ft;
private int offset;
public FrequencyCounter(int maxValue) {
offset = maxValue; // Handle negative numbers
ft = new FenwickTree(2 * maxValue + 1);
}
public void add(int num) {
ft.update(num + offset, 1);
}
public void remove(int num) {
ft.update(num + offset, -1);
}
public int countInRange(int left, int right) {
return ft.rangeQuery(left + offset, right + offset);
}
}
Practice Problems
Given an integer array nums, return an integer array counts where counts[i] is the number of smaller elements to the right of nums[i].
Example:
Input: nums = [5,2,6,1]
Output: [2,1,1,0]
To the right of 5: [2,6,1] → 2 smaller (2,1). To the right of 2: [6,1] → 1 smaller (1). To the right of 6: [1] → 1 smaller (1). To the right of 1: [] → 0 smaller.
Solution
```java
public List<Integer> countSmaller(int[] nums) {
int max = Arrays.stream(nums).max().getAsInt();
int min = Arrays.stream(nums).min().getAsInt();
int offset = -min;
FenwickTree ft = new FenwickTree(max - min + 2);
List<Integer> result = new ArrayList<>();
for (int i = nums.length - 1; i >= 0; i--) {
int count = ft.query(nums[i] + offset - 1);
result.add(0, count);
ft.update(nums[i] + offset, 1);
}
return result;
}
```Edge Cases:
- All elements same
- Already sorted array
- Reverse sorted array
- Contains negative numbers
Quiz
1. What does `i & (-i)` compute in a Fenwick Tree?
2. Which operation is NOT efficiently supported by a standard Fenwick Tree?
3. What is the primary purpose of Fenwick Tree (Binary Indexed Tree)?
4. What is a common mistake when implementing Fenwick Tree (Binary Indexed Tree)?
Flashcards
Question
What is the time complexity for update and query in a Fenwick Tree?
Click to reveal answer
Answer
Both update and query are O(log n). The Fenwick Tree uses the binary representation of indices to traverse the tree in logarithmic steps.
Question
Why is a Fenwick Tree preferred over a Segment Tree for prefix sum problems?
Click to reveal answer
Answer
Fenwick Tree is simpler to implement, uses less space (O(n) vs O(4n)), and has smaller constants. It's ideal when you only need prefix sums and point updates.
Question
What is Fenwick Tree (Binary Indexed Tree)?
Click to reveal answer
Answer
Fenwick Tree (Binary Indexed Tree) is a key concept in software engineering.
Question
When to use Fenwick Tree (Binary Indexed Tree)?
Click to reveal answer
Answer
Use Fenwick Tree (Binary Indexed Tree) when building production systems that require reliability, scalability, and maintainability.
Question
Fenwick Tree (Binary Indexed Tree) best practices
Click to reveal answer
Answer
Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.
Revision Notes
Key Takeaways
- 1.Fenwick Tree provides O(log n) point updates and prefix queries
- 2.Uses binary representation of indices for efficient traversal
- 3.Simpler and more space-efficient than Segment Tree
- 4.Ideal for prefix sum and frequency counting problems
- 5.Can be extended to 2D for matrix range queries
Interview Tips
- •Fenwick Tree is great for counting problems involving order statistics
- •Use coordinate compression when dealing with large value ranges
- •For inversion count: process right-to-left, query sum of smaller elements
- •Remember: i & (-i) gives the lowest set bit - this is the key trick
- •Practice: Count of Smaller Numbers After Self, Range Sum Query - Mutable
Cheat Sheet
Fenwick Tree Cheat Sheet
Core Operations
// Add val at index i (0-indexed)
void update(int i, int val) {
i++;
while (i <= n) {
tree[i] += val;
i += i & (-i);
}
}
// Prefix sum [0..i] (0-indexed)
int query(int i) {
i++;
int sum = 0;
while (i > 0) {
sum += tree[i];
i -= i & (-i);
}
return sum;
}
// Range sum [l..r]
int rangeQuery(int l, int r) {
return query(r) - query(l - 1);
}
Key Insight
i & (-i) = lowest set bit
- 6 (110) & -6 (..010) = 2 (010)
- 8 (1000) & -8 (..1000) = 8 (1000)
Applications
- Prefix Sum: O(log n) query
- Count Inversions: Process right-to-left, query count of smaller
- 2D Range Queries: Use 2D BIT
- Frequency Counting: Count numbers in ranges
vs Segment Tree
| Feature | Fenwick Tree | Segment Tree |
|---|---|---|
| Implementation | Simpler | Complex |
| Space | O(n) | O(4n) |
| Range Min/Max | No | Yes |
| Range Updates | Limited | Full (lazy) |