Find the Most Common Response
MedPrompt
You are given a 2D string array responses where each responses[i] is an array of strings representing survey responses from the ith day.
Return the most common response across all days after removing duplicate responses within each responses[i]. If there is a tie, return the lexicographically smallest response.
Example 1:
Input: responses = [["good","ok","good","ok"],["ok","bad","good","ok","ok"],["good"],["bad"]]
Output: "good"
Explanation:
- After removing duplicates within each list,
responses = [["good", "ok"], ["ok", "bad", "good"], ["good"], ["bad"]]. "good"appears 3 times,"ok"appears 2 times, and"bad"appears 2 times.- Return
"good"because it has the highest frequency.
Example 2:
Input: responses = [["good","ok","good"],["ok","bad"],["bad","notsure"],["great","good"]]
Output: "bad"
Explanation:
- After removing duplicates within each list we have
responses = [["good", "ok"], ["ok", "bad"], ["bad", "notsure"], ["great", "good"]]. "bad","good", and"ok"each occur 2 times.- The output is
"bad"because it is the lexicographically smallest amongst the words with the highest frequency.
Constraints:
1 <= responses.length <= 10001 <= responses[i].length <= 10001 <= responses[i][j].length <= 10responses[i][j]consists of only lowercase English letters
Approaches
2 approaches with complexity analysis and trade-offs.
This approach first identifies all unique responses that exist across all days. Then, for each of these unique responses, it iterates through every single day's list to count how many days it appears in. This involves a significant amount of repeated work, as each day's list is processed multiple times, once for every unique word in the entire dataset.
Algorithm
- Create a master
HashSetcalledallUniqueResponses. - Iterate through each day's
responses[i]and add all strings toallUniqueResponses. This gives us a set of every unique response that exists in the entire dataset. - Initialize a
HashMap<String, Integer> countsto store the frequency of each unique response. - Iterate through each
responseinallUniqueResponses. - For each
response, iterate through all the days fromi = 0toresponses.length - 1. - Inside the day loop, create a temporary
HashSetfromresponses[i]to handle duplicates for that specific day. - Check if the current
responseis present in the temporaryHashSet. If it is, increment its count in thecountsmap. - After populating the
countsmap, iterate through its entries to find the response with the highest frequency. - Maintain a
resultstring and amaxFreqinteger. Update them based on the frequency and lexicographical order for ties. - Return the final
result.
Walkthrough
The core idea is to first find the complete dictionary of possible responses and then count the occurrences for each.
-
Step 1: Collect All Unique Words: We iterate through the entire 2D array and add every single response to a
HashSet. This gives us a set of all unique words that appear anywhere in the survey. -
Step 2: Count Frequencies: We then iterate through our set of unique words. For each word, we start a counter at zero. We then loop through all the days again. For each day, we create another
HashSetof that day's responses (to handle duplicates within that day) and check if our target word is present. If it is, we increment our counter. After checking all days, we store the final count in aHashMap. -
Step 3: Find the Winner: After counting is done for all unique words, we iterate through our frequency map to find the word with the highest count, making sure to handle ties by choosing the lexicographically smallest word.
import java.util.*; class Solution { public String mostCommonWord(String[][] responses) { Set<String> allUniqueResponses = new HashSet<>(); for (String[] dayResponses : responses) { for (String response : dayResponses) { allUniqueResponses.add(response); } } Map<String, Integer> counts = new HashMap<>(); for (String uniqueResponse : allUniqueResponses) { int currentCount = 0; for (String[] dayResponses : responses) { Set<String> dailyUnique = new HashSet<>(Arrays.asList(dayResponses)); if (dailyUnique.contains(uniqueResponse)) { currentCount++; } } counts.put(uniqueResponse, currentCount); } String mostCommon = ""; int maxFreq = -1; for (Map.Entry<String, Integer> entry : counts.entrySet()) { String response = entry.getKey(); int freq = entry.getValue(); if (freq > maxFreq) { maxFreq = freq; mostCommon = response; } else if (freq == maxFreq) { if (mostCommon.isEmpty() || response.compareTo(mostCommon) < 0) { mostCommon = response; } } } return mostCommon; }}Complexity
Time
O(U * N * M * L), where `U` is the total number of unique strings, `N` is the number of days, `M` is the max responses per day, and `L` is the max string length. Since `U` can be up to `O(N * M)`, the complexity can approach `O(N^2 * M^2 * L)`, which is very slow.
Space
O(U * L), where `U` is the total number of unique strings across all days and `L` is the max string length. In the worst case, this is `O(N * M * L)`, where `N` is the number of days and `M` is the max responses per day. This space is used for the master set of unique responses and the frequency map.
Trade-offs
Pros
Conceptually straightforward to break down into distinct steps.
Cons
Extremely inefficient due to multiple nested loops.
The time complexity is very high, making it impractical for the given constraints.
Performs a lot of redundant work by repeatedly creating
HashSets for each day for every unique word.
This is the standard and efficient approach for this problem. It iterates through the responses day by day, removes duplicates for each day using a HashSet, and aggregates the counts of these unique daily responses into a global HashMap. By processing each day's unique responses only once and updating the potential answer on the fly, it avoids the redundant computations of the brute-force method.
Algorithm
- Initialize a
HashMap<String, Integer> countsto store frequencies. - Initialize
result = ""andmaxFreq = 0to track the winner in real-time. - Iterate through each day's responses
dayResponsesin the inputresponses. - For each
dayResponses, create aHashSet<String>to get the unique responses for that day. This efficiently handles the duplicate removal requirement for each day. - Iterate through the unique responses from the daily
HashSet. - For each unique
response, increment its count in the globalcountsmap and get itsnewFreq. - Compare
newFreqwithmaxFreq:- If
newFreq > maxFreq, a new most common word is found. UpdatemaxFreq = newFreqandresult = response. - If
newFreq == maxFreq, there is a tie. Check if the currentresponseis lexicographically smaller than the currentresult. If so, updateresult.
- If
- After iterating through all days,
resultwill hold the correct answer.
Walkthrough
This method processes the data in a single pass, which is much more efficient. Instead of finding all unique words first, we count them as we encounter them.
-
Step 1: Initialize Data Structures: We need a
HashMap<String, Integer>to store the frequency of each response. We also initialize variablesresultandmaxFreqto keep track of the most common response found so far. -
Step 2: Process Day by Day: We loop through each day's list of responses. For each day, we first put its responses into a
HashSet. This is a quick way to get only the unique responses for that day. -
Step 3: Update Counts and Track Winner: We then iterate through this set of unique daily responses. For each response, we update its count in our main
HashMap. Immediately after updating a count, we check if this response has become the new winner.- We compare its new frequency with
maxFreq. If it's greater, this response becomes the newresult. - If the frequency is equal to
maxFreq, we resolve the tie by comparing it lexicographically with the currentresultand updating if the new response is smaller.
- We compare its new frequency with
This way, when the loops are finished, we have our final answer without needing a separate step to find the winner.
import java.util.*; class Solution { public String mostCommonWord(String[][] responses) { Map<String, Integer> counts = new HashMap<>(); String result = ""; int maxFreq = 0; for (String[] dayResponses : responses) { Set<String> uniqueDailyResponses = new HashSet<>(Arrays.asList(dayResponses)); for (String response : uniqueDailyResponses) { int newFreq = counts.getOrDefault(response, 0) + 1; counts.put(response, newFreq); if (newFreq > maxFreq) { maxFreq = newFreq; result = response; } else if (newFreq == maxFreq) { if (response.compareTo(result) < 0) { result = response; } } } } return result; }}Complexity
Time
O(T * L), where `T` is the total number of responses across all days (sum of all `responses[i].length`), and `L` is the maximum length of a response string. This can also be expressed as `O(N * M * L)` where `N` is the number of days and `M` is the maximum responses per day. Each response string is processed a constant number of times.
Space
O(U * L), where `U` is the total number of unique strings across all days and `L` is the max string length. In the worst case, this is `O(N * M * L)`. This space is used for the frequency map and the temporary set for each day's responses.
Trade-offs
Pros
Highly efficient in time, processing the data in a single logical pass.
Well-suited for the given constraints.
The use of
HashMapandHashSetprovides fast average-case lookups and insertions.
Cons
Requires extra space for the
HashMapandHashSet, which could be significant if the number of unique responses is very large.
Solutions
Solution
class Solution {public String findCommonResponse(List<List<String>> responses) { Map<String, Integer> cnt = new HashMap<>(); for (var ws : responses) { Set<String> s = new HashSet<>(); for (var w : ws) { if (s.add(w)) { cnt.merge(w, 1, Integer : : sum); } } } String ans = responses.get(0).get(0); for (var e : cnt.entrySet()) { String w = e.getKey(); int v = e.getValue(); if (cnt.get(ans) < v || (cnt.get(ans) == v && w.compareTo(ans) < 0)) { ans = w; } } return ans; }}class Solution {public: string findCommonResponse(vector<vector<string>> &responses) { unordered_map<string, int> cnt; for (const auto &ws : responses) { unordered_set<string> s; for (const auto &w : ws) { if (s.insert(w).second) { ++cnt[w]; } } } string ans = responses[0][0]; for (const auto &e : cnt) { const string &w = e.first; int v = e.second; if (cnt[ans] < v || (cnt[ans] == v && w < ans)) { ans = w; } } return ans; }};class Solution: def findCommonResponse(self, responses: List[List[str]]) -> str: cnt = Counter() for ws in responses: for w in set(ws): cnt[w] += 1 ans = responses[0][0] for w, x in cnt . items(): if cnt[ans] < x or (cnt[ans] == x and w < ans): ans = w return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.