Matrix · 10 of 10
Common elements in all rows of a given matrix
Find the numbers that show up in every row of a grid, in one pass, with a notebook that counts how many rows in a row each number has survived.
- Simple way
- time O(m*n^2)
room O(n) - Clever way
- time O(m*n)
room O(n)
The problem, simply
You get a grid with m rows and n columns. The rows are not sorted, and a number can appear more than once in the same row.
Give back every number that appears in all of the rows. Each such number should appear only once in the answer, in any order.
A story to remember it
Picture it
Four rows, not sorted. Only 1 and 8 show up in every row. 7 is in three rows but not in row 0, and 2 is in rows 0 and 3 only.
- answer
Examples
Unsorted rows with repeats
- In
[[1,2,1,4,8],[3,7,8,5,1],[8,7,8,3,1],[8,1,2,7,9]]- Out
[8, 1]
1 and 8 are in all four rows. 7 is missing from row 0, and 2 is missing from rows 1 and 2. The repeated 1 in row 0 and the repeated 8 in row 2 must not be counted twice.
Sorted rows
- In
[[1,2,3,4,5],[2,4,5,8,10],[3,5,7,9,11],[1,3,5,7,9]]- Out
[5]
Sorting does not change the answer, and the same code works.
Nothing in common
- In
[[1,2],[3,4]]- Out
[]
No number is in both rows.
All the same
- In
[[5,5],[5,5],[5,5]]- Out
[5]
5 is in every row, but it goes into the answer once, not six times.
One row
- In
[[4,4,6]]- Out
[4, 6]
With one row, every different number in it is in all the rows.
Way 1: the simple way
Take each different number from the first row and look for it in every other row.
- Put the numbers of row 0 into a set, so repeats are checked once.
- For each number in the set, scan rows 1 to
m - 1. If some row does not have it, drop it. - The numbers that survive every row are the answer.
import java.util.*;
class Solution {
public List<Integer> commonElements(int[][] mat) {
List<Integer> result = new ArrayList<>();
if (mat.length == 0) return result;
Set<Integer> candidates = new LinkedHashSet<>();
for (int v : mat[0]) candidates.add(v); // each different number of row 0, once
for (int v : candidates) {
boolean everywhere = true;
for (int i = 1; i < mat.length && everywhere; i++) {
boolean found = false;
for (int x : mat[i]) if (x == v) { found = true; break; } // scan the whole row
everywhere = found;
}
if (everywhere) result.add(v);
}
return result;
}
}How long it takes: up to n candidates, each scanned against m - 1 rows of n numbers, O(m*n^2). Extra room: the set of candidates, O(n).
Way 2: the clever way
Read the grid once, row by row, keeping a notebook (a hash map) from a number to how many rows, starting at row 0, it has been seen in without a gap.
- For row 0, set
count[v] = 1for every numberv. - For row
ifrom 1 tom - 1, for every numbervin the row:- If
count[v] == i, the number was in all rows before this one. Setcount[v] = i + 1. - If this is the last row, that bump means
vis in every row. Add it to the answer. - Otherwise do nothing: the number is missing from some earlier row, is not in the notebook at all, or was already counted in this row.
- If
The check count[v] == i does three jobs at once: it ignores strangers, it ignores numbers that fell behind, and it ignores a second copy in the same row.
One pass with a notebook of counts
01 / 16
Row 0: write each different number in the notebook with a count of 1. The second 1 in row 0 is the same number, so it does not get a second line.
- looking now
- checking
- seen
- answer
- rejected
- just added
- ignored
import java.util.*;
class Solution {
public List<Integer> commonElements(int[][] mat) {
List<Integer> result = new ArrayList<>();
int m = mat.length;
if (m == 0) return result;
Map<Integer, Integer> count = new HashMap<>(); // number -> rows survived so far
for (int v : mat[0]) count.put(v, 1);
if (m == 1) { // one row: every different number counts
result.addAll(count.keySet());
return result;
}
for (int i = 1; i < m; i++) {
for (int v : mat[i]) {
Integer c = count.get(v);
if (c != null && c == i) { // in every row before this one, first time in this row
count.put(v, i + 1);
if (i == m - 1) result.add(v); // survived the last row too
}
}
}
return result;
}
}How long it takes: every cell is read once with an O(1) map lookup, O(m*n). Extra room: the notebook only ever holds numbers from row 0, so O(n).
Watch out for
- Adding new numbers to the map after row 0. They can never be common, and they make the map grow to
O(m*n). - Forgetting the one row grid. The loop over rows 1 and up never runs, so handle
m == 1on its own. - Returning a number twice. The
count == icheck makes each number join the answer once. - Assuming the rows are sorted. The grid in this problem is not sorted. If it were, the pointer method below would also work.
What interviewers ask next
- What if every row is sorted? Keep one pointer per row, all starting at the right end. Find the smallest of the numbers under the pointers. Every row whose number is bigger than that smallest one moves its pointer left until its number is no longer bigger. If all pointers then show the same number, record it and move every pointer left past all copies of it. Stop when any pointer falls off.
O(m*n)time andO(m)room, and no hash map. - Can you do it without a hash map and unsorted rows? Sort each row first,
O(m*n*log(n)), then use the pointer method. - Common numbers of only two arrays? Put one in a set and scan the other. This problem is that idea repeated across many rows.
- Return how many rows each number appears in, not only the common ones. Then you do need a map from number to a set of rows, or count with a "last row seen" value per number.
- Huge grid that does not fit in memory? The method reads rows one at a time and keeps only row 0's numbers, so it works as a stream.
Remember this
Grown-up words: the teacher's notebook is a hash map, the tally that must match the row number is a "last seen row" check that removes duplicates, and the whole thing is the intersection of m sets done in one pass.