599. Minimum Index Sum of Two Lists
Problem Description
You are given two arrays of strings list1
and list2
. Your task is to find all the common strings with the least index sum.
A common string is any string that appears in both list1
and list2
.
For each common string, calculate its index sum by adding its position in list1
(let's call it i
) and its position in list2
(let's call it j
). The index sum would be i + j
.
Among all common strings, you need to find those with the minimum index sum. If multiple common strings have the same minimum index sum, return all of them.
For example:
- If a string appears at
list1[2]
andlist2[3]
, its index sum is2 + 3 = 5
- If another string appears at
list1[1]
andlist2[2]
, its index sum is1 + 2 = 3
- The second string has a smaller index sum, so it would be preferred
Return all common strings that have the minimum index sum. The order of strings in your answer doesn't matter.
Intuition
To find common strings efficiently, we need a way to quickly check if a string from list1
exists in list2
and get its index. This naturally leads us to think about using a hash table (dictionary) to store all strings from list2
along with their indices. This gives us O(1) lookup time for each string.
Once we have this hash table set up, we can iterate through list1
. For each string in list1
, we can instantly check if it exists in list2
by looking it up in our hash table. If it exists, we can calculate the index sum i + j
where i
is the current position in list1
and j
is the position we stored in our hash table for list2
.
The key insight is that we need to track the minimum index sum found so far. As we iterate through list1
, we compare each new index sum with our current minimum:
- If we find a smaller index sum, we reset our answer list to contain only this new string and update our minimum
- If we find an equal index sum to our minimum, we add this string to our existing answer list
- If the index sum is larger, we simply ignore it
This single-pass approach with a hash table gives us an efficient O(n + m) solution where n and m are the lengths of the two lists, avoiding the need for nested loops which would be O(n × m).
Solution Approach
We implement the solution using a hash table to achieve optimal time complexity:
-
Build a hash table for list2: Create a dictionary
d
that maps each string inlist2
to its index. This is done with a dictionary comprehension:d = {s: i for i, s in enumerate(list2)}
. This allows O(1) lookup time for any string. -
Initialize tracking variables:
ans = []
: An empty list to store the result stringsmi = inf
: Initialize the minimum index sum to infinity so any valid sum will be smaller
-
Iterate through list1: For each string
s
at indexi
inlist1
:- Check if
s
exists in the hash tabled
(which means it's also inlist2
) - If it exists, retrieve its index
j
inlist2
usingj = d[s]
- Calculate the index sum:
i + j
- Check if
-
Update the result based on index sum:
- If
i + j < mi
: We found a new minimum index sum- Update
mi = i + j
- Reset
ans = [s]
to contain only this string
- Update
- If
i + j == mi
: We found another string with the same minimum index sum- Append this string to the answer:
ans.append(s)
- Append this string to the answer:
- If
i + j > mi
: Ignore this string as it doesn't have the minimum index sum
- If
-
Return the result: After processing all strings in
list1
, returnans
which contains all common strings with the minimum index sum.
The time complexity is O(n + m) where n and m are the lengths of list1
and list2
respectively, and the space complexity is O(m) for storing the hash table.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
list1 = ["happy", "sad", "good"]
list2 = ["sad", "happy", "good"]
Step 1: Build hash table for list2
We create a dictionary mapping each string in list2
to its index:
d = {"sad": 0, "happy": 1, "good": 2}
Step 2: Initialize tracking variables
ans = [] mi = infinity
Step 3: Iterate through list1
Iteration 1: i = 0
, s = "happy"
- Check if "happy" exists in
d
: Yes, it does! - Get its index in list2:
j = d["happy"] = 1
- Calculate index sum:
0 + 1 = 1
- Compare with
mi
(infinity):1 < infinity
- This is a new minimum! Update:
mi = 1
ans = ["happy"]
Iteration 2: i = 1
, s = "sad"
- Check if "sad" exists in
d
: Yes, it does! - Get its index in list2:
j = d["sad"] = 0
- Calculate index sum:
1 + 0 = 1
- Compare with
mi
(1):1 == 1
- Same as minimum! Add to answer:
ans = ["happy", "sad"]
Iteration 3: i = 2
, s = "good"
- Check if "good" exists in
d
: Yes, it does! - Get its index in list2:
j = d["good"] = 2
- Calculate index sum:
2 + 2 = 4
- Compare with
mi
(1):4 > 1
- Larger than minimum, ignore this string
Step 4: Return result
Return ans = ["happy", "sad"]
Both "happy" and "sad" have the minimum index sum of 1, so both are included in the final answer.
Solution Implementation
1class Solution:
2 def findRestaurant(self, list1: List[str], list2: List[str]) -> List[str]:
3 # Create a dictionary mapping restaurant names to their indices in list2
4 restaurant_to_index = {restaurant: index for index, restaurant in enumerate(list2)}
5
6 # Initialize result list to store restaurants with minimum index sum
7 result = []
8
9 # Initialize minimum index sum to infinity
10 min_index_sum = float('inf')
11
12 # Iterate through list1 with index
13 for index1, restaurant in enumerate(list1):
14 # Check if current restaurant exists in list2
15 if restaurant in restaurant_to_index:
16 # Get the index of restaurant in list2
17 index2 = restaurant_to_index[restaurant]
18
19 # Calculate the sum of indices
20 current_sum = index1 + index2
21
22 # If current sum is less than minimum, update minimum and reset result
23 if current_sum < min_index_sum:
24 min_index_sum = current_sum
25 result = [restaurant]
26 # If current sum equals minimum, add restaurant to result
27 elif current_sum == min_index_sum:
28 result.append(restaurant)
29
30 return result
31
1class Solution {
2 public String[] findRestaurant(String[] list1, String[] list2) {
3 // Create a map to store restaurants from list2 with their indices
4 Map<String, Integer> restaurantIndexMap = new HashMap<>();
5
6 // Populate the map with restaurants from list2 and their positions
7 for (int i = 0; i < list2.length; i++) {
8 restaurantIndexMap.put(list2[i], i);
9 }
10
11 // List to store restaurants with minimum index sum
12 List<String> result = new ArrayList<>();
13
14 // Initialize minimum index sum to a large value (2^30)
15 int minIndexSum = 1 << 30;
16
17 // Iterate through list1 to find common restaurants
18 for (int i = 0; i < list1.length; i++) {
19 // Check if current restaurant exists in list2
20 if (restaurantIndexMap.containsKey(list1[i])) {
21 // Get the index of this restaurant in list2
22 int list2Index = restaurantIndexMap.get(list1[i]);
23
24 // Calculate the sum of indices
25 int currentIndexSum = i + list2Index;
26
27 // If we found a smaller index sum, update minimum and reset result
28 if (currentIndexSum < minIndexSum) {
29 minIndexSum = currentIndexSum;
30 result.clear();
31 result.add(list1[i]);
32 }
33 // If index sum equals current minimum, add to result
34 else if (currentIndexSum == minIndexSum) {
35 result.add(list1[i]);
36 }
37 }
38 }
39
40 // Convert list to array and return
41 return result.toArray(new String[0]);
42 }
43}
44
1class Solution {
2public:
3 vector<string> findRestaurant(vector<string>& list1, vector<string>& list2) {
4 // Create a hash map to store restaurants from list2 with their indices
5 unordered_map<string, int> restaurantIndexMap;
6
7 // Populate the hash map with restaurants from list2
8 for (int i = 0; i < list2.size(); ++i) {
9 restaurantIndexMap[list2[i]] = i;
10 }
11
12 // Vector to store the result (restaurants with minimum index sum)
13 vector<string> result;
14 int minIndexSum = INT_MAX;
15
16 // Iterate through list1 to find common restaurants
17 for (int i = 0; i < list1.size(); ++i) {
18 // Check if current restaurant from list1 exists in list2
19 if (restaurantIndexMap.contains(list1[i])) {
20 // Get the index of this restaurant in list2
21 int j = restaurantIndexMap[list1[i]];
22 int currentIndexSum = i + j;
23
24 // If we found a smaller index sum, update minimum and reset result
25 if (currentIndexSum < minIndexSum) {
26 minIndexSum = currentIndexSum;
27 result.clear();
28 result.push_back(list1[i]);
29 }
30 // If index sum equals minimum, add to result
31 else if (currentIndexSum == minIndexSum) {
32 result.push_back(list1[i]);
33 }
34 }
35 }
36
37 return result;
38 }
39};
40
1/**
2 * Finds common restaurants between two lists with minimum index sum
3 * @param list1 - First array of restaurant names
4 * @param list2 - Second array of restaurant names
5 * @returns Array of restaurant names with minimum index sum
6 */
7function findRestaurant(list1: string[], list2: string[]): string[] {
8 // Create a map to store restaurant names from list2 with their indices
9 const restaurantIndexMap = new Map<string, number>(
10 list2.map((restaurant, index) => [restaurant, index])
11 );
12
13 // Track the minimum index sum found so far
14 let minimumIndexSum = Infinity;
15
16 // Store restaurants with minimum index sum
17 const result: string[] = [];
18
19 // Iterate through list1 to find common restaurants
20 list1.forEach((restaurant, indexInList1) => {
21 // Check if current restaurant exists in list2
22 if (restaurantIndexMap.has(restaurant)) {
23 // Get the index of restaurant in list2
24 const indexInList2 = restaurantIndexMap.get(restaurant)!;
25
26 // Calculate the sum of indices
27 const currentIndexSum = indexInList1 + indexInList2;
28
29 // If we found a smaller index sum, reset result and add current restaurant
30 if (currentIndexSum < minimumIndexSum) {
31 minimumIndexSum = currentIndexSum;
32 result.length = 0; // Clear the array
33 result.push(restaurant);
34 }
35 // If index sum equals minimum, add to result
36 else if (currentIndexSum === minimumIndexSum) {
37 result.push(restaurant);
38 }
39 }
40 });
41
42 return result;
43}
44
Time and Space Complexity
Time Complexity: O(n + m)
where n
is the length of list1
and m
is the length of list2
.
- Creating the dictionary
d
fromlist2
takesO(m)
time as we iterate through all elements inlist2
once. - The main loop iterates through all elements in
list1
, which takesO(n)
time. - Inside the loop, the dictionary lookup
s in d
and accessingd[s]
both takeO(1)
average time. - All other operations inside the loop (comparisons and list operations) take
O(1)
time. - Therefore, the total time complexity is
O(m) + O(n) = O(n + m)
.
Space Complexity: O(m)
where m
is the length of list2
.
- The dictionary
d
stores all elements fromlist2
as keys with their indices as values, requiringO(m)
space. - The answer list
ans
can contain at mostmin(n, m)
elements in the worst case (when all common restaurants have the same minimum index sum), which is bounded byO(m)
. - Other variables (
mi
,i
,j
,s
) useO(1)
space. - Therefore, the total space complexity is
O(m)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Duplicate Strings in the Same List
One common pitfall is not considering what happens when there are duplicate strings within the same list. While the problem doesn't explicitly state that strings are unique within each list, the solution assumes they are.
Issue Example:
list1 = ["happy", "sad", "happy"] list2 = ["happy", "good"]
If list1
contains duplicates, the dictionary comprehension for list2
would overwrite earlier occurrences, keeping only the last index. Similarly, when iterating through list1
, we might process the same string multiple times.
Solution:
The current implementation actually handles this correctly for list2
(keeping the last occurrence's index) and processes each position in list1
independently. However, to be explicit about handling duplicates:
def findRestaurant(self, list1: List[str], list2: List[str]) -> List[str]:
# Use the first occurrence in list2 for consistency
restaurant_to_index = {}
for index, restaurant in enumerate(list2):
if restaurant not in restaurant_to_index:
restaurant_to_index[restaurant] = index
result = []
min_index_sum = float('inf')
seen = set() # Track processed restaurants
for index1, restaurant in enumerate(list1):
if restaurant in restaurant_to_index and restaurant not in seen:
seen.add(restaurant)
index2 = restaurant_to_index[restaurant]
current_sum = index1 + index2
if current_sum < min_index_sum:
min_index_sum = current_sum
result = [restaurant]
elif current_sum == min_index_sum:
result.append(restaurant)
return result
2. Early Termination Optimization Missed
Another pitfall is not recognizing when we can stop searching early. Once we find a common string with index sum k
, we don't need to check elements in list1
at index i
where i > k
because even if they match the first element of list2
(index 0), the sum would be at least i + 0 > k
.
Optimized Solution:
def findRestaurant(self, list1: List[str], list2: List[str]) -> List[str]:
restaurant_to_index = {restaurant: index for index, restaurant in enumerate(list2)}
result = []
min_index_sum = float('inf')
for index1, restaurant in enumerate(list1):
# Early termination: if current index alone exceeds min_sum, stop
if index1 > min_index_sum:
break
if restaurant in restaurant_to_index:
index2 = restaurant_to_index[restaurant]
current_sum = index1 + index2
if current_sum < min_index_sum:
min_index_sum = current_sum
result = [restaurant]
elif current_sum == min_index_sum:
result.append(restaurant)
return result
This optimization can significantly improve performance when the minimum index sum is found early in the lists.
Which technique can we use to find the middle of a linked list?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!