In algorithmic problem solving, tracking dynamic state under streaming operations requires carefully selecting data structures to maintain $O(1)$ query responses. LeetCode 3160: Find the Number of Distinct Colors Among the Balls is a prime example of real-time state synchronization. Given a stream of queries where each operation assigns a color to a numbered ball, our task is to report the exact count of unique colors currently present across all colored balls immediately after each query. While a brute-force approach scanning all balls after each operation suffers from catastrophic $O(N \cdot K)$ time complexity, maintaining a Dual Hash Map State Machine achieves optimal $O(1)$ amortized time per query and $O(N)$ overall runtime. Here is an architectural and algorithmic breakdown of the optimal solution in Java, C++20, and TypeScript.
Problem Formulation & Constraints
You are given an integer limit and a 2D integer array queries of size $N \times 2$:
- There are
limit + 1balls labeled from $0$ tolimit. Initially, all balls are uncolored. - Each query is formatted as
[ball, color], denoting that ball numberballis painted with colorcolor. - If
ballwas previously uncolored, it now has colorcolor. - If
ballalready held a color, its old color is overwritten by the newcolor. - Return an integer array
resultof length $N$, whereresult[i]denotes the count of distinct colors active across all balls after query $i$.
Key Constraints: $1 \le limit \le 10^9$, $1 \le queries.length \le 10^5$. Because limit can reach $10^9$, allocating an array of size limit + 1 causes an immediate Out of Memory (OOM) error. The solution must rely strictly on sparse hash maps bounded by the number of queries ($N$).
Dual Hash Map Architecture & State Transitions
To evaluate the number of distinct colors in $O(1)$ time per query without rescanning, we maintain two complementary hash maps:
ballToColor (Map<Integer, Integer>): Maps each active ball ID to its current color.colorFreq (Map<Integer, Integer>): Maps each active color to the number of balls currently painted that color.
State Transition Mechanics
For each query [ball, newColor]:
- Check for Existing Color: If
ballToColor.containsKey(ball):- Retrieve
prevColor = ballToColor.get(ball). - If
prevColor == newColor, no state transition occurs. The count remains unchanged. - Otherwise, decrement
colorFreq[prevColor]by 1. - Critical Cleanup Step: If
colorFreq[prevColor] == 0, removeprevColorfromcolorFreqcompletely.
- Retrieve
- Assign New Color: Update
ballToColor[ball] = newColor. - Increment New Color Frequency: Increment
colorFreq[newColor]by 1 (defaulting to 0 if not present). - Record Distinct Count: The number of unique active colors is simply
colorFreq.size().
Multi-Language Implementations
1. Java Solution (Original Authentic Formulation)
import java.util.HashMap;
class Solution {
public int[] queryResults(int limit, int[][] queries) {
int n = queries.length;
int[] result = new int[n];
HashMap<Integer, Integer> ballMap = new HashMap<>();
HashMap<Integer, Integer> colorMap = new HashMap<>();
for (int i = 0; i < n; i++) {
int ball = queries[i][0];
int color = queries[i][1];
if (ballMap.containsKey(ball)) {
int prevColor = ballMap.get(ball);
colorMap.put(prevColor, colorMap.get(prevColor) - 1);
if (colorMap.get(prevColor) == 0) {
colorMap.remove(prevColor);
}
}
ballMap.put(ball, color);
colorMap.put(color, colorMap.getOrDefault(color, 0) + 1);
result[i] = colorMap.size();
}
return result;
}
}
2. C++20 Optimal Solution
#include <vector>
#include <unordered_map>
class Solution {
public:
std::vector<int> queryResults(int limit, std::vector<std::vector<int>>& queries) {
int n = queries.size();
std::vector<int> result(n);
std::unordered_map<int, int> ballToColor;
std::unordered_map<int, int> colorFreq;
// Reserve bucket space to eliminate rehashing during high-volume query streams
ballToColor.reserve(n);
colorFreq.reserve(n);
for (int i = 0; i < n; ++i) {
int ball = queries[i][0];
int color = queries[i][1];
auto it = ballToColor.find(ball);
if (it != ballToColor.end()) {
int prevColor = it->second;
if (--colorFreq[prevColor] == 0) {
colorFreq.erase(prevColor);
}
}
ballToColor[ball] = color;
colorFreq[color]++;
result[i] = colorFreq.size();
}
return result;
}
};
3. TypeScript Solution
function queryResults(limit: number, queries: number[][]): number[] {
const n = queries.length;
const result = new Int32Array(n);
const ballToColor = new Map<number, number>();
const colorFreq = new Map<number, number>();
for (let i = 0; i < n; i++) {
const [ball, color] = queries[i];
if (ballToColor.has(ball)) {
const prevColor = ballToColor.get(ball)!;
const currentCount = colorFreq.get(prevColor)!;
if (currentCount === 1) {
colorFreq.delete(prevColor);
} else {
colorFreq.set(prevColor, currentCount - 1);
}
}
ballToColor.set(ball, color);
colorFreq.set(color, (colorFreq.get(color) ?? 0) + 1);
result[i] = colorFreq.size;
}
return Array.from(result);
}
Complexity Analysis
| Metric | Complexity | Mathematical Derivation |
|---|---|---|
| Time Complexity | $O(N)$ | Each of the $N$ queries executes $O(1)$ amortized hash map lookups, insertions, and deletions. |
| Space Complexity | $O(N)$ | At most $N$ distinct balls and $N$ distinct colors are stored, completely independent of the $10^9$ limit. |
