CMD Guide
HomeDSAAdvanced Patterns

medium Range Frequency Queries

Problem Statement

You are given an integer array nums[] and a list of queries queries[], where each query is defined as queries[i] = [lefti, righti, valuei]. For each query, determine the frequency of valuei in the subarray of nums that starts at index lefti and ends at index righti for each query.

Return an array answer[] where answer[i] is the result for the ith query.

Examples

  1. Example 1:

    • Input: nums = [2, 1, 2, 3, 2, 1, 3], queries = [[0, 4, 2], [2, 6, 3], [1, 5, 1]]
    • Expected Output: [3, 2, 2]
    • Justification:
      • In the first query, 2 appears 3 times in the subarray [2, 1, 2, 3, 2].
      • In the second query, 3 appears 2 times in the subarray [2, 3, 2, 1, 3].
      • In the third query, 1 appears 2 time in the subarray [1, 2, 3, 2, 1].
  2. Example 2:

    • Input: nums = [4, 4, 4, 2, 4, 2, 2], queries = [[0, 2, 4], [3, 6, 2], [1, 4, 4]]
    • Expected Output: [3, 3, 3]
    • Justification:
      • In the first query, 4 appears 3 times in the subarray [4, 4, 4].
      • In the second query, 2 appears 3 times in the subarray [2, 4, 2, 2].
      • In the third query, 4 appears 3 times in the subarray [4, 4, 2, 4].
  3. Example 3:

    • Input: nums = [5, 3, 5, 5, 3, 3, 5, 3, 5], queries = [[0, 8, 5], [1, 5, 3], [2, 7, 5]]
    • Expected Output: [5, 3, 3]
    • Justification:
      • In the first query, 5 appears 5 times in the subarray [5, 3, 5, 5, 3, 3, 5, 3, 5].
      • In the second query, 3 appears 3 times in the subarray [3, 5, 5, 3, 3].
      • In the third query, 5 appears 3 times in the subarray [5, 5, 3, 3, 5, 3].

Constraints:

Try it yourself

Try solving this question here:

🎯 STRICT STANDOUT — Range Frequency Queries (problem)

0. Pattern family

Family: Range frequency of a value — prefix-of-positions or MO

Why this pattern: Count occurrences of a given value in [L,R]. This is simpler than distinct: it is a static range count, solvable by storing positions per value and binary searching.

1. Recognition · template (K12)

Best typical: map value → sorted indices; for query (L,R,v) answer upper_bound(R) - lower_bound(L) on that vector. MO alternative: freq[v] while sliding, answer freq[queryValue] — but online class design often wants the positions method.

2. Complexity derivation (K11)

Positions: build O(N); query O(log n_v) ≤ O(log N); total O(N + Q log N).
MO: O((N+Q)√N) worse when only this query type.
BIT/segtree: useful if updates; static positions win.

3. When-NOT (K12)

Do not reach MO first here. If the API is online RangeFreqQuery class with many queries and no updates, positions+binary search is cleaner. Updates of arr[i] → Fenwick per value or dynamic tree.

4. Edge / hand-run (K13)

nums=[2,1,2,3,2,1,3]; (0,4,2)→3; (2,6,3)→2; (1,5,1)→2.
Value absent → 0. L=R match → 1 or 0.

5. Interviewer follow-ups & drills

Q1. Why binary search on positions works?
Model answer: Indices are sorted; count in [L,R] is a range length.

Q2. MO still correct?
Model answer: Yes if offline batch; class interface is usually online.

Q3. Segment tree of what?
Model answer: Counts per segment for one value, or merge of freq maps (heavy).

✅ Solution Range Frequency Queries

Problem Statement

You are given an integer array nums[] and a list of queries queries[], where each query is defined as queries[i] = [lefti, righti, valuei]. For each query, determine the frequency of valuei in the subarray of nums that starts at index lefti and ends at index righti for each query.

Return an array answer[] where answer[i] is the result for the ith query.

Examples

  1. Example 1:

    • Input: nums = [2, 1, 2, 3, 2, 1, 3], queries = [[0, 4, 2], [2, 6, 3], [1, 5, 1]]
    • Expected Output: [3, 2, 2]
    • Justification:
      • In the first query, 2 appears 3 times in the subarray [2, 1, 2, 3, 2].
      • In the second query, 3 appears 2 times in the subarray [2, 3, 2, 1, 3].
      • In the third query, 1 appears 2 time in the subarray [1, 2, 3, 2, 1].
  2. Example 2:

    • Input: nums = [4, 4, 4, 2, 4, 2, 2], queries = [[0, 2, 4], [3, 6, 2], [1, 4, 4]]
    • Expected Output: [3, 3, 3]
    • Justification:
      • In the first query, 4 appears 3 times in the subarray [4, 4, 4].
      • In the second query, 2 appears 3 times in the subarray [2, 4, 2, 2].
      • In the third query, 4 appears 3 times in the subarray [4, 4, 2, 4].
  3. Example 3:

    • Input: nums = [5, 3, 5, 5, 3, 3, 5, 3, 5], queries = [[0, 8, 5], [1, 5, 3], [2, 7, 5]]
    • Expected Output: [5, 3, 3]
    • Justification:
      • In the first query, 5 appears 5 times in the subarray [5, 3, 5, 5, 3, 3, 5, 3, 5].
      • In the second query, 3 appears 3 times in the subarray [3, 5, 5, 3, 3].
      • In the third query, 5 appears 3 times in the subarray [5, 5, 3, 3, 5, 3].

Constraints:

  • 1 <= arr.length <= 105
  • 1 <= arr[i], value <= 104
  • 0 <= left <= right < arr.length

Solution

To solve this problem efficiently, we can use Mo's algorithm, which is well-suited for answering range queries on static arrays. Mo's algorithm sorts queries in a way that minimizes the movement of the left and right pointers, thereby reducing the number of operations needed to adjust the frequency counts. The algorithm processes the queries in a specific order, based on their left and right bounds, and maintains a frequency array that keeps track of the occurrence of each element in the current subarray.

This approach is effective because it reduces the complexity of each query from potentially linear time to logarithmic time. By processing the queries in this way, we minimize the number of elements that need to be added or removed from the subarray, making the solution both time and space-efficient.

Step-by-Step Algorithm

  1. Initialization:

    • Start by receiving the input array nums and the list of queries queries.
    • Determine the length of the nums array, denoted as n.
    • Initialize a frequency array frequency[] with a size 100001 to accommodate the range of values in nums.
  2. Query Preparation:

    • For each query in queries, create an object (or structure) that contains the left index left, the right index right, the value to search value, and the index i of the query in the original list.
    • Store all these query objects in an array queryObjects[].
  3. Sorting Queries:

    • Sort the queryObjects[] array using the following criteria:
      1. Primary Criterion: Sort by the block number of the left index. The block number is calculated by dividing the left index by the square root of n (the length of nums). This helps in minimizing the movement of the left pointer during query processing.
      2. Secondary Criterion: For queries that belong to the same block, sort by the right index. This ensures that the right pointer mostly moves in a single direction, further reducing the computational complexity.
  4. Query Processing:

    • Initialize two pointers currentLeft and currentRight, both set to 0. These pointers will represent the current subarray that is being processed.
    • Initialize an array result[] to store the result of each query, with the same length as queries.
  5. Processing Each Query:

    • For each query in the sorted queryObjects[]:
      1. Adjust Right Pointer:
        • Expansion: While currentRight is less than or equal to query.right, increase frequency[nums[currentRight]] by 1, and then increment currentRight. This step adds elements to the current subarray and updates their frequency.
        • Shrinkage: While currentRight is greater than query.right + 1, decrement currentRight and decrease frequency[nums[currentRight]] by 1. This step removes elements from the current subarray that are no longer part of the required range.
      2. Adjust Left Pointer:
        • Expansion: While currentLeft is less than query.left, decrease frequency[nums[currentLeft]] by 1, and then increment currentLeft. This step removes elements from the left side of the subarray that are no longer part of the required range.
        • Shrinkage: While currentLeft is greater than query.left, decrement currentLeft and increase frequency[nums[currentLeft]] by 1. This step adds elements to the left side of the subarray.
      3. Record the Result:
        • The frequency of query.value in the current subarray is now stored in frequency[query.value]. Assign this value to result[query.index].
  6. Return the Result:

    • After all queries have been processed, return the result[] array, which contains the frequency counts for each query.

Algorithm Walkthrough

Given:

  • nums = [2, 1, 2, 3, 2, 1, 3]
  • queries = [[0, 4, 2], [2, 6, 3], [1, 5, 1]]

Step 1: Initialization

  • n = 7 (length of nums)
  • frequency[] = [0] * 100001

Step 2: Query Preparation

  • Create query objects:
    • query1: left=0, right=4, value=2, index=0
    • query2: left=2, right=6, value=3, index=1
    • query3: left=1, right=5, value=1, index=2
  • Store them in queryObjects[].

Step 3: Sorting Queries

  • Block size sqrt(n) = sqrt(7) ≈ 2.65. We round down to 2 for block calculations.
  • Sort queries:
    • All queries fall in block 0 or block 1.
    • After sorting by the block and right index, the order is:
      • query1 (block 0)
      • query3 (block 0)
      • query2 (block 1)

Step 4: Query Processing Initialization

  • currentLeft = 0
  • currentRight = 0
  • result[] = [0, 0, 0]

Step 5: Processing Each Query

Processing query1: left=0, right=4, value=2

  • Adjust Right Pointer:
    • Expand currentRight:
      • frequency[2] = 1 at currentRight=0
      • frequency[1] = 1 at currentRight=1
      • frequency[2] = 2 at currentRight=2
      • frequency[3] = 1 at currentRight=3
      • frequency[2] = 3 at currentRight=4
  • The subarray is [2, 1, 2, 3, 2].
  • Adjust Left Pointer:
    • No adjustments needed, currentLeft is already at query.left.
  • Store the Result:
    • result[0] = frequency[2] = 3

Processing query3: left=1, right=5, value=1

  • Adjust Right Pointer:
    • Expand currentRight:
      • frequency[1] = 2 at currentRight=5
  • The subarray is [1, 2, 3, 2, 1].
  • Adjust Left Pointer:
    • Expand currentLeft:
      • Decrease frequency[2] to 2. currentLeft=1
  • The subarray is now [1, 2, 3, 2, 1] from currentLeft=1 to currentRight=5.
  • Store the Result:
    • result[2] = frequency[1] = 2

Processing query2: left=2, right=6, value=3

  • Adjust Right Pointer:
    • Expand currentRight:
      • frequency[3] = 2 at currentRight=6
  • The subarray is [2, 3, 2, 1, 3].
  • Adjust Left Pointer:
    • Expand currentLeft:
      • Decrease frequency[1] to 1. currentLeft=2
  • The subarray is [2, 3, 2, 1, 3] from currentLeft=2 to currentRight=6.
  • Store the Result:
    • result[1] = frequency[3] = 2

Step 6: Return the Result

  • The final result[] = [3, 2, 2].

Code

java
import java.util.Arrays;

class Solution {
    private int[] nums;
    private int[] frequency;

    public int[] rangeFrequencyQueries(int[] nums, int[][] queries) {
        this.nums = nums;
        int n = nums.length;
        frequency = new int[100001]; // assuming nums[i] is <= 100000
        Query[] queryObjects = new Query[queries.length];

        // Prepare the queries for Mo's algorithm
        for (int i = 0; i < queries.length; i++) {
            queryObjects[i] = new Query(queries[i][0], queries[i][1], queries[i][2], i);
        }

        // Sort the queries
        Arrays.sort(queryObjects, (q1, q2) -> {
            int block1 = q1.left / (int) Math.sqrt(n);
            int block2 = q2.left / (int) Math.sqrt(n);
            if (block1 != block2) {
                return block1 - block2;
            }
            return q1.right - q2.right;
        });

        int[] result = new int[queries.length];
        int currentLeft = 0, currentRight = 0;
        for (Query query : queryObjects) {
            // Adjust right boundary
            while (currentRight <= query.right) frequency[nums[currentRight++]]++;
            while (currentRight > query.right + 1) frequency[nums[--currentRight]]--;

            // Adjust left boundary
            while (currentLeft < query.left) frequency[nums[currentLeft++]]--;
            while (currentLeft > query.left) frequency[nums[--currentLeft]]++;

            // Store the result for this query
            result[query.index] = frequency[query.value];
        }

        return result;
    }

    private class Query {
        int left, right, value, index;
        Query(int left, int right, int value, int index) {
            this.left = left;
            this.right = right;
            this.value = value;
            this.index = index;
        }
    }

    // Main method for testing the examples
    public static void main(String[] args) {
        Solution solution = new Solution();

        // Example 1
        int[] nums1 = {2, 1, 2, 3, 2, 1, 3};
        int[][] queries1 = {{0, 4, 2}, {2, 6, 3}, {1, 5, 1}};
        int[] result1 = solution.rangeFrequencyQueries(nums1, queries1);
        System.out.println(Arrays.toString(result1)); // Output: [3, 2, 2]

        // Example 2
        int[] nums2 = {4, 4, 4, 2, 4, 2, 2};
        int[][] queries2 = {{0, 2, 4}, {3, 6, 2}, {1, 4, 4}};
        int[] result2 = solution.rangeFrequencyQueries(nums2, queries2);
        System.out.println(Arrays.toString(result2)); // Output: [3, 3, 3]

        // Example 3
        int[] nums3 = {5, 3, 5, 5, 3, 3, 5, 3, 5};
        int[][] queries3 = {{0, 8, 5}, {1, 5, 3}, {2, 7, 5}};
        int[] result3 = solution.rangeFrequencyQueries(nums3, queries3);
        System.out.println(Arrays.toString(result3)); // Output: [5, 3, 3]
    }
}

Complexity Analysis

Time Complexity

The overall time complexity is , according to Mo's algorithm.

Space Complexity

  • The primary space usage is the freq array, which is of size , i.e., in this case. Additionally, we use space for storing the results (O(q)), and the queries themselves, which is .

Thus, the space complexity is , which simplifies to assuming a constant maximum element value.

🎯 STRICT STANDOUT — Solution Range Frequency Queries

0. Pattern family

Family: Range count — positions BS vs MO teaching

Why this pattern: Even if the page shows MO, standout treatment must name the O(N + Q log N) positions method as preferred for static frequency-of-value.

1. Recognition · template (K12)

Preferred: vector<int> pos[V]; for i,x in nums: pos[x].push(i); query: upper(pos[v],R)-lower(pos[v],L). MO shell: maintain freq while sliding offline queries of form (L,R,v).

2. Complexity derivation (K11)

Positions: Θ(N) build + Θ(Q log N).
MO: Θ((N+Q)√N).
Hand-run positions for v=2 in [2,1,2,3,2,1,3]: indices [0,2,4]; count in [0,4]: 3 indices ≤4 and ≥0 → 3.

3. When-NOT (K12)

MO if you must share one shell with many aggregate types offline. BIT: point add +1 at index when value matches — good with updates. Prefix of counts per value: O(N·U) memory if U large — avoid.

4. Edge / hand-run (K13)

v never appears → empty vector → 0.
All elements v → R-L+1.
Off-by-one: inclusive R → upper_bound(R) not R+1 if stored carefully.

5. Interviewer follow-ups & drills

Q1. Implement lower/upper carefully.
Model answer: Need first index ≥ L and first > R; difference is count.

Q2. Why not 2D prefix always?
Model answer: Memory U·N blows up; positions sparse-friendly.

Q3. Interview follow-up: update nums[i]?
Model answer: Positions freeze — switch to Fenwick/segtree.

🧩 Pattern · Advanced

Recognize it: A specialized structure — segment tree / Fenwick (BIT) / sqrt-decomposition — for fast range queries or offline processing. Reach for the right tool.

▶ Visualize this problem (step it, predict each fork)
⛶ Open this problem debugger in explore mode
🤖 Don't fully get this? Learn it with Claude

Stuck on Range Frequency Queries? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🪜 Hint ladder (no spoilers)

Progressively stronger hints — you still solve it.

I'm working on the problem **Range Frequency Queries** (DSA). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🎨 Explain the approach visually

See the technique, not just code.

Explain the optimal approach to **Range Frequency Queries** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔍 Review my solution

Catch bugs, edge cases, sub-optimality.

I'll paste my solution to **Range Frequency Queries**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔁 Drill the pattern

Lock in recognition with look-alikes.

Give me 2 problems that use the SAME underlying pattern as **Range Frequency Queries**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes