Facebook Pixel

3488. Closest Equal Element Queries

Problem Description

You are given a circular array nums and an array queries. For each query i, you need to determine the following:

  • The minimum distance between the element at index queries[i] and any other index j in the circular array such that nums[j] == nums[queries[i]]. If no such index exists, the answer for that query should be -1.

Return an array answer of the same size as queries, where answer[i] represents the result for query i.

Intuition

The problem involves finding the minimal cyclic distance between elements equal to the one at a queried index in the circular array. Understanding the circular structure is key—a circular array "wraps around" such that any index after the last element leads back to the first. The solution requires efficiently calculating distances to both the near identical element before and after the queried position.

To tackle this in a circular manner, we:

  1. Treat the array as if it extends to twice its length. This allows straightforward wraparound computations without manually handling the circular logic.
  2. Use two hash tables to track indices of previously and next seen identical elements:
    • left: Tracks the most recent previous occurrence.
    • right: Identifies the next occurrence in the extended array.
  3. For any position in this extended array (representing our circular array twice), update the minimum distance to an identical element using these tables.
  4. After preprocessing, for each query, compute the minimum circular distance considering the two duplicates of the array.
  5. If this minimum distance exceeds or equals the size of the original array, it implies no valid identical element is found within the original circular structure, returning -1 for such cases.

This structured approach efficiently handles the circular nature and computes the required distances with appropriate use of hash tables and linear passes over the extended array.

Learn more about Binary Search patterns.

Solution Approach

To solve the problem efficiently given its circular nature, the solution employs a combination of extending the array and utilizing hash tables for tracking indices of identical elements.

Step-by-step Implementation

  1. Extend the Circular Array:

    • The original array nums of size n is conceptually duplicated to form an extended array of size 2n. This aids in automatic wraparound handling inherent in circular arrays.
  2. Hash Tables for Position Tracking:

    • Two dictionaries, left and right, are employed to maintain the indices of the previous and the next occurrence of each element, respectively. These will help in determining minimal distances from any position to previous and upcoming identical elements.
  3. Calculate Minimum Distances:

    • Forward Pass: Traverse the extended array from the beginning. For each element, update its minimum distance to a previously seen identical element using the left dictionary. Update the left dictionary with the current index of the element.
    • Backward Pass: A reverse traversal over the extended array updates the minimum distance to the next identical element using the right dictionary. Update the right dictionary with the current position.
  4. Query Evaluation:

    • For each query, compute the minimal valid distance utilizing both forward and backward results stored in d[i] and d[i+n]. If the calculated distance is less than n, it indicates a valid configuration within the circular array—record this distance. If not, assign -1.
  5. Edge Case Handling:

    • Ensure handling cases where no identical element exists within feasible bounds by checking if the minimum distance is greater than or equal to n.

This method, effectively using hash tables for immediate prior and next element tracking, ensures linear time complexity with respect to the size of the extended array, offering an efficient solution to the circular distance query problem.

The primary algorithmic components are:

  • Double the length of the array virtually by using modulo arithmetic.
  • Use dictionaries (left and right) for tracking last seen and next seen indices of elements.
  • Populate distance arrays d[i] representing the nearest same element for both normal and extended parts of the array.
  • Loop over the queries to determine results based on calculated distances and conditions checked for valid occurrences.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Suppose we have a circular array nums = [1, 2, 3, 1, 4] and a list of queries queries = [0, 2, 3]. We'll demonstrate how to find the minimum distance for each query using the provided solution approach.

Step-by-step Implementation

1. Extend the Circular Array:

First, extend nums by duplicating it: extended_nums = [1, 2, 3, 1, 4, 1, 2, 3, 1, 4]. This helps simulate the circular nature of the array.

2. Hash Tables for Position Tracking:

Initialize two dictionaries: left = {} and right = {}.

3. Calculate Minimum Distances:

  • Forward Pass:

    • Traverse from left to right through extended_nums:
      • For index 0, element 1: No previous occurrence, update left[1] = 0.
      • For index 1, element 2: No previous occurrence, update left[2] = 1.
      • For index 2, element 3: No previous occurrence, update left[3] = 2.
      • For index 3, element 1 (duplicate found): Calculate distance 3 - left[1] = 3, update left[1] = 3.
      • Continue similarly updating left for all elements.
  • Backward Pass:

    • Traverse from right to left through extended_nums:
      • For index 9, element 4: No next occurrence, update right[4] = 9.
      • For index 8, element 1: No next occurrence, update right[1] = 8.
      • For index 7, element 3: No next occurrence, update right[3] = 7.
      • For index 6, element 2: No next occurrence, update right[2] = 6.
      • For index 5, element 1 (duplicate found): Calculate distance right[1] - 5 = 3, update right[1] = 5.
      • Continue similarly updating right for all elements.

4. Query Evaluation:

  • For queries[0] = 0 (element 1):
    • Calculate minimum distance using d[0] from forward pass and d[5] from backward pass, both considering extended_nums.
    • Min distance is 1 via backward pass as another 1 found at index 3 (3 - 0) and another at index 8 (8 - 5).
  • For queries[1] = 2 (element 3):
    • From forward and backward distances: Nearest is 3 at index 7 (7 - 2).
    • Min distance is 2.
  • For queries[2] = 3 (element 1):
    • Distance of 1 from forward pass, as 1 at index 0 (3 - 0) or at 8 (8 - 5).
    • Min distance is 0.

5. Edge Case Handling:

No such cases in this input, but if distances exceed the array's original length n = 5, the result would be -1.

Thus, the answer array for this example is [1, 2, 0].

Solution Implementation

1from typing import List
2
3class Solution:
4    def solveQueries(self, nums: List[int], queries: List[int]) -> List[int]:
5        n = len(nums)  # Length of the original array
6        m = n * 2  # Twice the length of the original array
7        min_distance = [m] * m  # Initialize with twice the length of nums
8        left_indices = {}  # Tracks the last occurrence of each number for the forward pass
9      
10        # Forward pass to compute minimum distances to previous occurrences
11        for i in range(m):
12            current_num = nums[i % n]  # Wrap around to handle circular nature
13            if current_num in left_indices:
14                min_distance[i] = min(min_distance[i], i - left_indices[current_num])
15            left_indices[current_num] = i
16      
17        right_indices = {}  # Tracks the last occurrence of each number for the backward pass
18      
19        # Backward pass to compute minimum distances to next occurrences
20        for i in range(m - 1, -1, -1):
21            current_num = nums[i % n]  # Wrap around to handle circular nature
22            if current_num in right_indices:
23                min_distance[i] = min(min_distance[i], right_indices[current_num] - i)
24            right_indices[current_num] = i
25      
26        # Reduce the distances considering only the first n elements
27        for i in range(n):
28            min_distance[i] = min(min_distance[i], min_distance[i + n])
29      
30        # Construct the results for each query
31        result = [-1 if min_distance[i] >= n else min_distance[i] for i in queries]
32        return result
33
1import java.util.List;
2import java.util.ArrayList;
3import java.util.Map;
4import java.util.HashMap;
5import java.util.Arrays;
6
7class Solution {
8    public List<Integer> solveQueries(int[] nums, int[] queries) {
9        int n = nums.length;
10        int doubleLength = n * 2;
11      
12        // Array to store the smallest distance between duplicate numbers
13        int[] distance = new int[doubleLength];
14        Arrays.fill(distance, doubleLength); // Fill with a large number initially
15
16        Map<Integer, Integer> leftMap = new HashMap<>();
17      
18        // Traverse from left to right
19        for (int i = 0; i < doubleLength; i++) {
20            int currentNumber = nums[i % n];
21            if (leftMap.containsKey(currentNumber)) {
22                // Update the minimum distance if current is smaller
23                distance[i] = Math.min(distance[i], i - leftMap.get(currentNumber));
24            }
25            // Update last seen index for the current number
26            leftMap.put(currentNumber, i);
27        }
28
29        Map<Integer, Integer> rightMap = new HashMap<>();
30      
31        // Traverse from right to left
32        for (int i = doubleLength - 1; i >= 0; i--) {
33            int currentNumber = nums[i % n];
34            if (rightMap.containsKey(currentNumber)) {
35                // Update the minimum distance if current is smaller
36                distance[i] = Math.min(distance[i], rightMap.get(currentNumber) - i);
37            }
38            // Update last seen index for the current number
39            rightMap.put(currentNumber, i);
40        }
41
42        // Update original array positions with the minimum distances
43        for (int i = 0; i < n; i++) {
44            distance[i] = Math.min(distance[i], distance[i + n]);
45        }
46
47        List<Integer> answer = new ArrayList<>();
48      
49        // Process each query to determine the shortest distance
50        for (int query : queries) {
51            // Add the distance if valid, otherwise -1
52            answer.add(distance[query] >= n ? -1 : distance[query]);
53        }
54        return answer;
55    }
56}
57
1#include <vector>
2#include <unordered_map>
3#include <algorithm>
4
5class Solution {
6public:
7    std::vector<int> solveQueries(std::vector<int>& nums, std::vector<int>& queries) {
8        int n = nums.size(); // Length of the input number array
9        int m = n * 2; // Length of the array when doubled
10        std::vector<int> distances(m, m); // Initialize distances with a large value (m)
11
12        std::unordered_map<int, int> leftMostIndex; // Map to store the most recent index of each number from the left
13        // Calculate minimum distance from the left
14        for (int i = 0; i < m; i++) {
15            int x = nums[i % n]; // Repeat the array to simulate circular behavior
16            if (leftMostIndex.count(x)) {
17                distances[i] = std::min(distances[i], i - leftMostIndex[x]);
18            }
19            leftMostIndex[x] = i; // Update the latest index of the current number
20        }
21
22        std::unordered_map<int, int> rightMostIndex; // Map to store the most recent index of each number from the right
23        // Calculate minimum distance from the right
24        for (int i = m - 1; i >= 0; i--) {
25            int x = nums[i % n]; // Repeat the array to simulate circular behavior
26            if (rightMostIndex.count(x)) {
27                distances[i] = std::min(distances[i], rightMostIndex[x] - i);
28            }
29            rightMostIndex[x] = i; // Update the latest index of the current number
30        }
31
32        // Determine the minimum distance for each element in the original array
33        for (int i = 0; i < n; i++) {
34            distances[i] = std::min(distances[i], distances[i + n]);
35        }
36
37        std::vector<int> result;
38        // Generate the result based on the queries
39        for (int query : queries) {
40            result.push_back(distances[query] >= n ? -1 : distances[query]);
41        }
42        return result;
43    }
44};
45
1// This function calculates the shortest distance to repeating numbers for multiple queries.
2function solveQueries(nums: number[], queries: number[]): number[] {
3    const numsLength = nums.length;
4    const doubledLength = numsLength * 2;
5    const distances: number[] = Array(doubledLength).fill(doubledLength);
6
7    // Maps to store the index of last occurrence of each number from nums.
8    const leftIndexMap = new Map<number, number>();
9    for (let i = 0; i < doubledLength; i++) {
10        const currentNumber = nums[i % numsLength];
11        if (leftIndexMap.has(currentNumber)) {
12            distances[i] = Math.min(distances[i], i - leftIndexMap.get(currentNumber)!);
13        }
14        leftIndexMap.set(currentNumber, i);
15    }
16
17    const rightIndexMap = new Map<number, number>();
18    for (let i = doubledLength - 1; i >= 0; i--) {
19        const currentNumber = nums[i % numsLength];
20        if (rightIndexMap.has(currentNumber)) {
21            distances[i] = Math.min(distances[i], rightIndexMap.get(currentNumber)! - i);
22        }
23        rightIndexMap.set(currentNumber, i);
24    }
25
26    // Minimize distances using values from the second half of the doubled array.
27    for (let i = 0; i < numsLength; i++) {
28        distances[i] = Math.min(distances[i], distances[i + numsLength]);
29    }
30
31    // Process queries to find out the shortest distance for each query index.
32    return queries.map(query => (distances[query] >= numsLength ? -1 : distances[query]));
33}
34

Time and Space Complexity

The algorithm iterates through the list nums twice while constructing the d array and again when finalizing the minimum distances. This results in an effective time complexity of O(n), as each operation inside the loops (x in left, i - left[x], min() and dictionary updates) takes constant time. Constructing the results for the queries also scales with the size of queries, but overall this influence is generally less significant. Thus, the total complexity remains O(n) in terms of the length of nums.

The space complexity is O(n) because the d array doubles the size of nums, resulting in a key structural requirement relative to n, and the dictionaries used (left and right) rely on the possible unique values in nums, but they are bounded by the input size. These factors combined underpin the O(n) space usage.

Learn more about how to find time and space complexity quickly.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More