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 indexj
in the circular array such thatnums[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:
- Treat the array as if it extends to twice its length. This allows straightforward wraparound computations without manually handling the circular logic.
- 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.
- For any position in this extended array (representing our circular array twice), update the minimum distance to an identical element using these tables.
- After preprocessing, for each query, compute the minimum circular distance considering the two duplicates of the array.
- 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
-
Extend the Circular Array:
- The original array
nums
of sizen
is conceptually duplicated to form an extended array of size2n
. This aids in automatic wraparound handling inherent in circular arrays.
- The original array
-
Hash Tables for Position Tracking:
- Two dictionaries,
left
andright
, 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.
- Two dictionaries,
-
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 theleft
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 theright
dictionary with the current position.
- Forward Pass: Traverse the extended array from the beginning. For each element, update its minimum distance to a previously seen identical element using the
-
Query Evaluation:
- For each query, compute the minimal valid distance utilizing both forward and backward results stored in
d[i]
andd[i+n]
. If the calculated distance is less thann
, it indicates a valid configuration within the circular array—record this distance. If not, assign-1
.
- For each query, compute the minimal valid distance utilizing both forward and backward results stored in
-
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
.
- Ensure handling cases where no identical element exists within feasible bounds by checking if the minimum distance is greater than or equal to
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
andright
) 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 EvaluatorExample 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
, element1
: No previous occurrence, updateleft[1] = 0
. - For index
1
, element2
: No previous occurrence, updateleft[2] = 1
. - For index
2
, element3
: No previous occurrence, updateleft[3] = 2
. - For index
3
, element1
(duplicate found): Calculate distance3 - left[1] = 3
, updateleft[1] = 3
. - Continue similarly updating
left
for all elements.
- For index
- Traverse from left to right through
-
Backward Pass:
- Traverse from right to left through
extended_nums
:- For index
9
, element4
: No next occurrence, updateright[4] = 9
. - For index
8
, element1
: No next occurrence, updateright[1] = 8
. - For index
7
, element3
: No next occurrence, updateright[3] = 7
. - For index
6
, element2
: No next occurrence, updateright[2] = 6
. - For index
5
, element1
(duplicate found): Calculate distanceright[1] - 5 = 3
, updateright[1] = 5
. - Continue similarly updating
right
for all elements.
- For index
- Traverse from right to left through
4. Query Evaluation:
- For
queries[0] = 0
(element1
):- Calculate minimum distance using
d[0]
from forward pass andd[5]
from backward pass, both consideringextended_nums
. - Min distance is
1
via backward pass as another1
found at index3 (3 - 0)
and another at index8 (8 - 5)
.
- Calculate minimum distance using
- For
queries[1] = 2
(element3
):- From forward and backward distances: Nearest is
3
at index7 (7 - 2)
. - Min distance is
2
.
- From forward and backward distances: Nearest is
- For
queries[2] = 3
(element1
):- Distance of
1
from forward pass, as1
at index0 (3 - 0)
or at8 (8 - 5)
. - Min distance is
0
.
- Distance of
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.
Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!