3527. Find the Most Common Response
Problem Description
You are given a 2D string array responses
where each responses[i]
represents survey responses collected on day i
.
Your task is to find the most common response across all days. However, there's an important rule: if the same response appears multiple times on the same day, it should only be counted once for that day.
For example:
- If day 1 has responses
["apple", "banana", "apple"]
, the response "apple" only counts once for day 1 - If day 2 has responses
["apple", "orange"]
, the response "apple" counts once for day 2 - In total, "apple" would have a count of 2 across both days
After counting all responses with duplicates removed per day, you need to return the response that appears on the most days. If multiple responses have the same maximum count, return the one that comes first lexicographically (alphabetically).
The solution uses a hash table to track how many days each unique response appears on. For each day's responses, it first converts them to a set to remove duplicates, then increments the count for each unique response. Finally, it finds the response with the highest count, breaking ties by choosing the lexicographically smallest one.
Intuition
The key insight is recognizing that we need to count how many days each response appears on, not the total number of times it appears across all days. This distinction is crucial because a response appearing multiple times on the same day should only contribute 1 to its overall count.
Think of it like counting attendance - if a student raises their hand multiple times in one class, they're still only present for one class session. Similarly, if "apple" appears 5 times on day 1, it still only counts as appearing on 1 day.
This naturally leads us to a two-step counting process:
- For each day, identify the unique responses (removing duplicates within that day)
- Count how many days each unique response appears across all days
Why use a set for each day? Converting responses[i]
to a set automatically removes duplicates, giving us exactly what we need - the distinct responses for that day. Once we have the unique responses per day, we can simply increment a counter for each response.
The final step involves finding the maximum count. Since we need the lexicographically smallest response in case of ties, we compare both the count and the string value. If response A has the same count as response B, but A comes before B alphabetically (like "apple" before "banana"), we choose A.
This approach elegantly handles the duplicate removal requirement while maintaining an efficient way to track global frequency across days, making it a natural solution to the problem's constraints.
Solution Approach
The solution uses a hash table (Counter in Python) to efficiently track the occurrence count of each response across all days.
Step 1: Initialize the Counter
cnt = Counter()
We create an empty counter cnt
that will store each response as a key and the number of days it appears as the value.
Step 2: Process Each Day's Responses
for ws in responses:
for w in set(ws):
cnt[w] += 1
For each day's responses ws
:
- Convert the list to a
set(ws)
to automatically remove duplicates within that day - Iterate through each unique response
w
in the set - Increment the counter for that response by 1
This ensures that even if "apple" appears 3 times on day 1, it only adds 1 to cnt["apple"]
.
Step 3: Find the Most Common Response
ans = responses[0][0] for w, x in cnt.items(): if cnt[ans] < x or (cnt[ans] == x and w < ans): ans = w
Initialize ans
with any response (here, the first response of the first day). Then iterate through all responses in the counter:
w
is the response stringx
is its count (number of days it appears)
Update ans
to w
if:
cnt[ans] < x
: Responsew
appears on more days than the current answercnt[ans] == x and w < ans
: Responsew
appears on the same number of days but is lexicographically smaller
Time Complexity: O(n * m)
where n
is the number of days and m
is the average number of responses per day.
Space Complexity: O(k)
where k
is the total number of unique responses across all days.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Input: responses = [["coffee", "tea", "coffee"], ["tea", "water"], ["coffee", "water", "juice"]]
Step 1: Initialize Counter
We start with an empty counter: cnt = {}
Step 2: Process Each Day
Day 0: ["coffee", "tea", "coffee"]
- Convert to set:
{"coffee", "tea"}
(duplicate "coffee" removed) - Update counter:
cnt["coffee"] = 1
cnt["tea"] = 1
- Counter state:
{"coffee": 1, "tea": 1}
Day 1: ["tea", "water"]
- Convert to set:
{"tea", "water"}
(no duplicates) - Update counter:
cnt["tea"] = 2
(incremented from 1)cnt["water"] = 1
- Counter state:
{"coffee": 1, "tea": 2, "water": 1}
Day 2: ["coffee", "water", "juice"]
- Convert to set:
{"coffee", "water", "juice"}
(no duplicates) - Update counter:
cnt["coffee"] = 2
(incremented from 1)cnt["water"] = 2
(incremented from 1)cnt["juice"] = 1
- Final counter:
{"coffee": 2, "tea": 2, "water": 2, "juice": 1}
Step 3: Find Most Common Response
- Initialize:
ans = "coffee"
(first response of first day) - Check each response:
- "coffee": count = 2 (keep as current answer)
- "tea": count = 2, same as "coffee" but "tea" > "coffee" lexicographically, no update
- "water": count = 2, same as "coffee" but "water" > "coffee" lexicographically, no update
- "juice": count = 1, less than 2, no update
Result: "coffee" (appears on 2 days, ties with "tea" and "water" but comes first alphabetically)
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def findCommonResponse(self, responses: List[List[str]]) -> str:
6 # Counter to track frequency of each word across all responses
7 word_counter = Counter()
8
9 # Count occurrences of each unique word in each response
10 for response_words in responses:
11 # Use set to ensure each word is counted only once per response
12 for word in set(response_words):
13 word_counter[word] += 1
14
15 # Initialize result with the first word of the first response
16 result = responses[0][0]
17
18 # Find the word with highest frequency (ties broken by lexicographical order)
19 for word, frequency in word_counter.items():
20 # Update result if current word has higher frequency
21 # or same frequency but comes earlier lexicographically
22 if word_counter[result] < frequency or (word_counter[result] == frequency and word < result):
23 result = word
24
25 return result
26
1class Solution {
2 public String findCommonResponse(List<List<String>> responses) {
3 // Map to store the frequency of each word across all response lists
4 Map<String, Integer> wordFrequency = new HashMap<>();
5
6 // Iterate through each response list
7 for (List<String> wordList : responses) {
8 // Use a set to track unique words in current response list
9 Set<String> uniqueWords = new HashSet<>();
10
11 // Count each unique word only once per response list
12 for (String word : wordList) {
13 // If word is successfully added to set (not a duplicate in this list)
14 if (uniqueWords.add(word)) {
15 // Increment the word's frequency count
16 wordFrequency.merge(word, 1, Integer::sum);
17 }
18 }
19 }
20
21 // Initialize result with the first word from the first response list
22 String result = responses.get(0).get(0);
23
24 // Find the word with highest frequency (or lexicographically smallest if tied)
25 for (Map.Entry<String, Integer> entry : wordFrequency.entrySet()) {
26 String currentWord = entry.getKey();
27 int currentFrequency = entry.getValue();
28
29 // Update result if current word has higher frequency
30 // or same frequency but comes lexicographically before current result
31 if (wordFrequency.get(result) < currentFrequency ||
32 (wordFrequency.get(result) == currentFrequency && currentWord.compareTo(result) < 0)) {
33 result = currentWord;
34 }
35 }
36
37 return result;
38 }
39}
40
1class Solution {
2public:
3 string findCommonResponse(vector<vector<string>>& responses) {
4 // Map to store the frequency of each word across all response lists
5 unordered_map<string, int> wordFrequency;
6
7 // Iterate through each response list
8 for (const auto& responseList : responses) {
9 // Use a set to track unique words in current response list
10 unordered_set<string> uniqueWords;
11
12 // Process each word in the current response list
13 for (const auto& word : responseList) {
14 // If word is successfully inserted (not a duplicate in this list)
15 if (uniqueWords.insert(word).second) {
16 // Increment the global frequency count for this word
17 ++wordFrequency[word];
18 }
19 }
20 }
21
22 // Initialize result with the first word from the first response list
23 string result = responses[0][0];
24
25 // Find the word with highest frequency (with lexicographic tie-breaking)
26 for (const auto& entry : wordFrequency) {
27 const string& currentWord = entry.first;
28 int currentFrequency = entry.second;
29
30 // Update result if current word has higher frequency
31 // or same frequency but lexicographically smaller
32 if (wordFrequency[result] < currentFrequency ||
33 (wordFrequency[result] == currentFrequency && currentWord < result)) {
34 result = currentWord;
35 }
36 }
37
38 return result;
39 }
40};
41
1/**
2 * Finds the most common response across all response arrays.
3 * If multiple responses have the same frequency, returns the lexicographically smallest one.
4 *
5 * @param responses - Array of response arrays, where each inner array contains response strings
6 * @returns The most common response string
7 */
8function findCommonResponse(responses: string[][]): string {
9 // Map to store the frequency count of each unique response
10 const responseFrequencyMap = new Map<string, number>();
11
12 // Iterate through each response array
13 for (const responseArray of responses) {
14 // Use a Set to track unique responses within current array (avoid counting duplicates)
15 const uniqueResponsesInArray = new Set<string>();
16
17 // Process each response in the current array
18 for (const response of responseArray) {
19 // Only count each response once per array
20 if (!uniqueResponsesInArray.has(response)) {
21 uniqueResponsesInArray.add(response);
22 // Increment the frequency count for this response
23 responseFrequencyMap.set(response, (responseFrequencyMap.get(response) ?? 0) + 1);
24 }
25 }
26 }
27
28 // Initialize result with the first response from the first array
29 let mostCommonResponse = responses[0][0];
30
31 // Find the response with highest frequency (or lexicographically smallest if tied)
32 for (const [currentResponse, currentFrequency] of responseFrequencyMap) {
33 const bestFrequency = responseFrequencyMap.get(mostCommonResponse)!;
34
35 // Update if current response has higher frequency
36 // or same frequency but lexicographically smaller
37 if (bestFrequency < currentFrequency ||
38 (bestFrequency === currentFrequency && currentResponse < mostCommonResponse)) {
39 mostCommonResponse = currentResponse;
40 }
41 }
42
43 return mostCommonResponse;
44}
45
Time and Space Complexity
The time complexity is O(L)
, where L
is the total length of all responses (i.e., the sum of lengths of all individual response lists).
Breaking down the operations:
- The outer loop iterates through all response lists:
O(n)
wheren
is the number of response lists - For each response list, we convert it to a set and iterate through unique words:
O(m_i)
wherem_i
is the length of the i-th response list - The set conversion for each response list takes
O(m_i)
time - Adding to the counter takes
O(1)
per word - The second loop iterates through all unique words in the counter:
O(k)
wherek
is the total number of unique words across all responses - Since
k ≤ L
and the sum of allm_i
equalsL
, the overall time complexity isO(L)
The space complexity is O(L)
, where L
is the total length of all responses.
Space breakdown:
- The counter stores at most all unique words across all responses:
O(k)
wherek ≤ L
- Creating a set for each response list uses temporary space:
O(m_i)
for the i-th list, but only one set exists at a time - The maximum space used is dominated by the counter which could potentially store up to
L
words in the worst case (when all words are unique) - Therefore, the space complexity is
O(L)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Counting Duplicates Within the Same Day
The Problem:
A frequent mistake is counting duplicate responses multiple times within the same day. For instance, if day 1 has ["apple", "banana", "apple", "apple"]
, counting "apple" three times instead of once would give incorrect results.
Incorrect Approach:
cnt = Counter() for ws in responses: for w in ws: # Wrong: doesn't remove duplicates cnt[w] += 1
Correct Solution:
cnt = Counter()
for ws in responses:
for w in set(ws): # Correct: converts to set first
cnt[w] += 1
Pitfall 2: Incorrect Tie-Breaking Logic
The Problem: When multiple responses have the same maximum count, the lexicographically smallest one should be returned. A common error is using the wrong comparison operator or forgetting this requirement entirely.
Incorrect Approach:
# Wrong: uses > instead of < for lexicographic comparison if cnt[ans] < x or (cnt[ans] == x and w > ans): ans = w
Correct Solution:
# Correct: uses < for lexicographic comparison if cnt[ans] < x or (cnt[ans] == x and w < ans): ans = w
Pitfall 3: Edge Case - Empty Response Days
The Problem:
If any day has an empty response list []
, the code might crash when trying to access elements or create sets.
Incorrect Handling:
ans = responses[0][0] # Crashes if responses[0] is empty
Robust Solution:
# Handle empty responses gracefully
cnt = Counter()
for ws in responses:
if ws: # Check if the day has any responses
for w in set(ws):
cnt[w] += 1
# Initialize ans more carefully
if not cnt:
return "" # or handle as per requirements
ans = next(iter(cnt.keys())) # Get any key from counter
Pitfall 4: Using Built-in Functions Incorrectly
The Problem:
Using Counter.most_common()
without considering the lexicographic tie-breaking requirement.
Incorrect Approach:
# Wrong: most_common() doesn't guarantee lexicographic order for ties return cnt.most_common(1)[0][0]
Correct Solution: Either manually iterate and compare (as shown in the main solution) or sort appropriately:
# Sort by count (descending) then by word (ascending)
return max(cnt.items(), key=lambda x: (-x[1], x[0]))[0]
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
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!