Heap · 17 of 18
Rearrange characters in a string such that no two adjacent are same
Line up letters so no two equal ones stand together, by always using the most common letter that did not just go.
- Simple way
- time O(n * k)
room O(k) - Clever way
- time O(n log k)
room O(k)
The problem, simply
You get a string. Rearrange its characters so that no two neighbors are equal. If that is impossible, return an empty string. Here n is the length and k is the number of different characters.
For "aaabc" one answer is "acaba". For "aaab" there is no answer.
A story to remember it
Picture it
'aaabc' has three a's, one b and one c. The a's need two other letters to keep them apart, and b and c are exactly enough: a ? a ? a.
- checking
Examples
Just enough
- In
s = 'aaabc'- Out
'acaba'
The a's take spots 0, 2 and 4. 'abaca' is also correct.
More letters
- In
s = 'aaabbc'- Out
'ababac'
Any arrangement with no equal neighbors is accepted.
Impossible
- In
s = 'aaab'- Out
''
Three a's need at least two other letters between them, but there is only one b.
All the same
- In
s = 'bb'- Out
''
Two equal letters always touch. A single 'b' would be fine.
Way 1: the simple way
Do the teacher's plan without a heap. At each step, look at every letter, and pick the one with the most copies left that is not the letter you just placed.
- Count each letter.
- For each of the
nspots, scan allkletters for the biggest count that is not the previous letter. - If no letter can be picked, return
"".
class Solution {
public String rearrange(String s) {
int[] count = new int[26];
for (char c : s.toCharArray()) count[c - 'a']++;
StringBuilder out = new StringBuilder();
int prev = -1;
for (int step = 0; step < s.length(); step++) {
int pick = -1;
for (int c = 0; c < 26; c++) { // scan every letter
if (c != prev && count[c] > 0 && (pick == -1 || count[c] > count[pick])) pick = c;
}
if (pick == -1) return ""; // only the previous letter is left
out.append((char) ('a' + pick));
count[pick]--;
prev = pick;
}
return out.toString();
}
}How long it takes: n steps, each scanning k letters, O(n * k). With 26 letters that is fine, but with a big alphabet (all of Unicode, or numbers as "letters") the scan gets slow. Extra room: the counts, O(k).
Way 2: the clever way
Let a max heap find the biggest count, and keep the letter that just went on a bench for one turn.
- Count each letter and put
(count, letter)pairs in a max heap. - While the heap is not empty:
- Poll the top pair and write its letter.
- If the benched pair still has copies, put it back into the heap.
- Bench the pair you just used, with its count lowered by one.
- If the answer has all
nletters, return it. If not, a letter got stuck on the bench, so return"".
Rearrange 'aaabc' with a max heap and a bench
01 / 06
Count the letters and put them in a max heap. a:3 is on top. The bench is empty.
- looking now
- checking
- answer
- just added
When it fails, it fails like this:
'aaab' after three steps: the answer is 'aba', the heap is empty, and a:1 is still on the bench. Nothing can go next, so return an empty string.
- rejected
import java.util.PriorityQueue;
class Solution {
public String rearrange(String s) {
int[] count = new int[26];
for (char c : s.toCharArray()) count[c - 'a']++;
PriorityQueue<int[]> heap = new PriorityQueue<>((x, y) -> Integer.compare(y[0], x[0])); // max heap
for (int c = 0; c < 26; c++) {
if (count[c] > 0) heap.offer(new int[] { count[c], c });
}
StringBuilder out = new StringBuilder();
int[] bench = null; // the letter that just went
while (!heap.isEmpty()) {
int[] top = heap.poll();
out.append((char) ('a' + top[1]));
if (bench != null && bench[0] > 0) heap.offer(bench); // back from the bench
top[0]--;
bench = top; // sits out one turn
}
return out.length() == s.length() ? out.toString() : "";
}
}How long it takes: each of the n letters costs one poll and at most one offer, O(n log k). Extra room: the heap, O(k).
Watch out for
- Do not forget the final check. When a letter is stuck on the bench, the loop ends with a short answer. Compare the length with
n. - You can also check up front: if the biggest count is more than
(n + 1) / 2, return""at once. - A string of length 0 or 1 is already fine.
- Uppercase, digits or any characters? Use a
HashMap<Character, Integer>for counts instead of a 26 slot array.
What interviewers ask next
- Equal characters must be at least
dapart? Make the bench a waiting line that holds the lastd - 1used letters. A letter comes back to the heap only when it leaves the front of the line. This is the same code with a queue instead of one slot. - Can it be
O(n)? Yes. Put the most common letter on spots 0, 2, 4 and so on, then fill the rest of the even spots and then the odd spots with the other letters. - How many arrangements exist? That is a counting problem with inclusion and exclusion, much harder. Interviewers usually stop at "find one".
- Task scheduler with idle slots? The same greedy, but instead of failing when everything is on the bench, you add an idle step.
Remember this
Grown-up words: the classes are character frequencies, the line of biggest classes is a max heap (priority queue), the bench is a cooldown slot (a queue when the gap is bigger than one), and always choosing the biggest class is a greedy choice.