← Home

767. Reorganize String

LeetCode article · C++ solution
Website made by wuisabel-gif · Original C++ code by keineahnung2345
heap / priority queueC++Markdown
767

I like to read this solution as a small machine: keep the useful information, throw away the noise. For 767. Reorganize String, the solution in this repository is mainly a heap / priority queue solution.

Guide

What?

The first job is to translate the English prompt into state, transition, and stopping conditions. Instead of trying to be clever immediately, read the code as a sequence of questions:

  • What state are we keeping?
  • How do we move from one state to the next?
  • When do we know the answer is already determined?

For this file, the main tools are: heap / priority queue, greedy.

The notes already sitting in the source point us in the right direction:

  • priority queue

Guide

When?

This is the kind of solution you want when the problem has structure hiding inside a messy-looking input. The accepted code reduces that pressure by storing exactly the information that remains useful later.

The important function names to track are reorganizeString, countLex.

Guide

Why?

The code is doing bookkeeping so your brain does not have to keep the entire search space open at once.

  • Sorting is used to make local choices comparable, so the later scan does not have to rediscover order.
  • A map keeps the lookup side cheap; the code pays a little memory to avoid repeated searching.
  • The queue gives the solution a level-by-level or frontier-style traversal.
  • The heap keeps the best candidate available without sorting the whole world every time.

Guide

How?

Walk through the solution in this order:

  1. Initialize the memory or helper structure.
  2. Process candidates in the order the invariant expects.
  3. Update the answer only when the current state is valid.
  4. Return the value that represents the fully processed input.

The most important competitive-programming habit here is to trust the invariant. Once the invariant is right, the loops become much less scary.

Guide

Complexity

  • Time: O(n) to O(n log n), depending on the dominant loop or data structure operation
  • Space: O(n) in the usual case for auxiliary containers or recursion

Guide

C++ Solution

Your submission

The accepted solution

solution.cpp
01//priority queue
02//Runtime: 12 ms, faster than 13.62% of C++ online submissions for Reorganize String.
03//Memory Usage: 6.4 MB, less than 100.00% of C++ online submissions for Reorganize String.
04class Solution {
05public:
06    string reorganizeString(string S) {
07        unordered_map<char, int> counter;
08        
09        for(char c : S){
10            counter[c]++;
11        }
12        
13        auto comp = [](const pair<char, int>& a, const pair<char, int>& b){
14            return (a.second == b.second) ? (a.first > b.first) : a.second < b.second;};
15        
16        //the larger count, the earlier be popped
17        //the smaller char order, the earlier be popped
18        priority_queue<pair<char, int>, vector<pair<char, int>>, decltype(comp)> pq(comp);
19        
20        for(auto it = counter.begin(); it != counter.end(); it++){
21            pq.push(make_pair(it->first, it->second));
22        }
23        
24        string ans = "";
25        
26        while(!pq.empty()){
27            //pop two elements at a time
28            pair<char, int> cur = pq.top(); pq.pop();
29            pair<char, int> next = make_pair(' ', 0);
30            
31            // cout << cur.first << " " << cur.second << endl;
32            
33            if(pq.size() > 0){
34                next = pq.top(); pq.pop();
35                cout << next.first << " " << next.second << endl;
36            }
37            
38            cur.second--;
39            if(ans.size() > 0 && cur.first == ans[ans.size()-1]) return "";
40            ans += cur.first;
41            
42            if(next.first != ' '){
43                next.second--;
44                if(ans.size() > 0 && next.first == ans[ans.size()-1]) return "";
45                ans += next.first;
46            }
47            
48            //push them back to the queue
49            if(cur.second > 0){
50                pq.push(cur);
51            }
52            
53            if(next.second > 0){
54                pq.push(next);
55            }
56        }
57        
58        return ans;
59    }
60};
61
62//Approach #1: Sort by Count
63//Runtime: 0 ms, faster than 100.00% of C++ online submissions for Reorganize String.
64//Memory Usage: 6.2 MB, less than 100.00% of C++ online submissions for Reorganize String.
65//A: size of the alphabet, N: ength of S
66//time: O(AN + AlogA), space: O(N)
67class Solution {
68public:
69    string reorganizeString(string S) {
70        int N = S.size();
71        //a hash calculated by count and its lexicographic order
72        vector<int> countLex(26, 0);
73        
74        //count part
75        for(char c: S){
76            countLex[c-'a'] += 26;
77        }
78        //lexicographic part
79        for(int i = 0; i < 26; i++){
80            countLex[i] += i;
81        }
82        
83        //we start from the char with smaller count
84        sort(countLex.begin(), countLex.end());
85        
86        vector<char> ans(N);
87        int cursor = 1;
88        for(int code : countLex){
89            int count = code/26;
90            char c = 'a' + (code%26);
91            /*
92            if S's length is 3, a character cannot appear more than 2 times
93            if S's length is 4, a character cannot appear more than 2 times
94            */
95            if(count > (N+1)/2) return "";
96            for(int i = 0; i < count; i++){
97                /*
98                the latter char(char with larger count) will start from 0
99                for example, in "abaca", 'a' is processed at last, 
100                and it will fill 0,2,4th position
101                */
102                if(cursor >= N) cursor = 0;
103                // cout << cursor << " " << c << endl;
104                ans[cursor] = c;
105                //jump to next next position
106                cursor += 2;
107            }
108        }
109        
110        return string(ans.begin(), ans.end());
111    }
112};
113
114//Approach #2: Greedy with Heap
115//priority queue, process two char at a time
116//Runtime: 4 ms, faster than 64.77% of C++ online submissions for Reorganize String.
117//Memory Usage: 6.6 MB, less than 100.00% of C++ online submissions for Reorganize String.
118//time: O(NlogA), space: O(A)
119class Solution {
120public:
121    string reorganizeString(string S) {
122        int N = S.size();
123        unordered_map<char, int> counter;
124        
125        for(char c : S){
126            counter[c]++;
127        }
128        
129        auto comp = [](const pair<char, int>& a, const pair<char, int>& b){
130            return (a.second == b.second) ? (a.first > b.first) : a.second < b.second;};
131        
132        //the larger count, the earlier be popped
133        //the smaller char order, the earlier be popped
134        priority_queue<pair<char, int>, vector<pair<char, int>>, decltype(comp)> pq(comp);
135        
136        for(auto it = counter.begin(); it != counter.end(); it++){
137            //early stopping
138            if(it->second > (N+1)/2) return "";
139            pq.push(make_pair(it->first, it->second));
140        }
141        
142        string ans = "";
143        
144        while(pq.size() >= 2){
145            //pop two elements at a time
146            pair<char, int> cur = pq.top(); pq.pop();
147            pair<char, int> next = pq.top(); pq.pop();
148            
149            //we don't need this check now, because we process two different char at one time 
150            // if(ans.size() > 0 && cur.first == ans[ans.size()-1]) return "";
151            ans += cur.first;
152            
153            // if(ans.size() > 0 && next.first == ans[ans.size()-1]) return "";
154            ans += next.first;
155            
156            //push them back to the queue
157            if(--cur.second > 0) pq.push(cur);
158            if(--next.second > 0) pq.push(next);
159        }
160        
161        //we jump out the loop when its size is 0 or 1
162        if(!pq.empty()) ans += pq.top().first;
163        
164        return ans;
165    }
166};

Cost

Complexity

Time
O(n) to O(n log n), depending on the dominant loop or data structure operation
Dominated by the main traversal, recursion, or data-structure operations in the code.
Space
O(n) in the usual case for auxiliary containers or recursion
Auxiliary state plus the answer structure where the problem requires one.