LeetCode 3160: Find Distinct Colors Among Balls

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 + 1 balls labeled from $0$ to limit. Initially, all balls are uncolored.
  • Each query is formatted as [ball, color], denoting that ball number ball is painted with color color.
  • If ball was previously uncolored, it now has color color.
  • If ball already held a color, its old color is overwritten by the new color.
  • Return an integer array result of length $N$, where result[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:

  1. ballToColor (Map<Integer, Integer>): Maps each active ball ID to its current color.
  2. colorFreq (Map<Integer, Integer>): Maps each active color to the number of balls currently painted that color.
Dual Hash Map State Machine Transition Incoming Query [ball=1, color=3] ballToColor Map Prev Color: 4 ➔ Decr Freq New Color: 3 ➔ Incr Freq colorFreq Map colorFreq.size() O(1) Distinct Count! When a color's count drops to 0, it is purged from colorFreq, ensuring map size exactly equals active distinct colors.

State Transition Mechanics

For each query [ball, newColor]:

  1. 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, remove prevColor from colorFreq completely.
  2. Assign New Color: Update ballToColor[ball] = newColor.
  3. Increment New Color Frequency: Increment colorFreq[newColor] by 1 (defaulting to 0 if not present).
  4. 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.