Given a string s
consisting only of lowercase English letters, you are to reorder the string using the following algorithm:
s
and append it to the result.s
that is greater than the last appended character and append it to the result.s
and append it to the result.s
that is smaller than the last appended character and append it to the result.s
have been used.
You must not reuse any character from s
. The final result will be a permutation of the original string. There is always exactly one valid answer for each input.
At first glance, the problem seems to require repeatedly finding the next smallest or largest unused character in the string, alternating between ascending and descending order. A brute-force approach would be to scan the string for the next smallest or largest character each time, but this would be inefficient.
To optimize, we can observe that all characters are lowercase English letters, so there are only 26 possible characters. Instead of searching the string repeatedly, we can count the frequency of each character and use this information to efficiently pick the next available character in either ascending or descending order.
This approach avoids unnecessary scans and leverages the limited alphabet size, leading to a much faster solution. The problem becomes one of managing character counts and iterating in both directions over the alphabet.
Here is a step-by-step breakdown of the optimized algorithm:
s
.This approach is efficient because each character is processed exactly as many times as it appears in the input, and all operations on the count array are O(1).
Let's walk through an example with s = "aaaabbbbcccc"
:
abccbaabccba
Brute-Force Approach:
This problem can be solved efficiently by leveraging the limited size of the lowercase English alphabet. By counting character frequencies and iterating in both ascending and descending order, we avoid unnecessary scans and achieve an O(N) solution. The key insight is to use a counting array for constant-time access, making the implementation both simple and optimal.
class Solution:
def sortString(self, s: str) -> str:
counts = [0] * 26
for c in s:
counts[ord(c) - ord('a')] += 1
result = []
n = len(s)
while len(result) < n:
# Ascending pass
for i in range(26):
if counts[i]:
result.append(chr(i + ord('a')))
counts[i] -= 1
# Descending pass
for i in range(25, -1, -1):
if counts[i]:
result.append(chr(i + ord('a')))
counts[i] -= 1
return ''.join(result)
class Solution {
public:
string sortString(string s) {
vector<int> counts(26, 0);
for (char c : s) {
counts[c - 'a']++;
}
string result;
int n = s.size();
while (result.size() < n) {
// Ascending pass
for (int i = 0; i < 26; ++i) {
if (counts[i]) {
result += (char)('a' + i);
counts[i]--;
}
}
// Descending pass
for (int i = 25; i >= 0; --i) {
if (counts[i]) {
result += (char)('a' + i);
counts[i]--;
}
}
}
return result;
}
};
class Solution {
public String sortString(String s) {
int[] counts = new int[26];
for (char c : s.toCharArray()) {
counts[c - 'a']++;
}
StringBuilder result = new StringBuilder();
int n = s.length();
while (result.length() < n) {
// Ascending pass
for (int i = 0; i < 26; i++) {
if (counts[i] > 0) {
result.append((char) (i + 'a'));
counts[i]--;
}
}
// Descending pass
for (int i = 25; i >= 0; i--) {
if (counts[i] > 0) {
result.append((char) (i + 'a'));
counts[i]--;
}
}
}
return result.toString();
}
}
var sortString = function(s) {
let counts = new Array(26).fill(0);
for (let c of s) {
counts[c.charCodeAt(0) - 'a'.charCodeAt(0)]++;
}
let result = [];
let n = s.length;
while (result.length < n) {
// Ascending pass
for (let i = 0; i < 26; i++) {
if (counts[i] > 0) {
result.push(String.fromCharCode(i + 'a'.charCodeAt(0)));
counts[i]--;
}
}
// Descending pass
for (let i = 25; i >= 0; i--) {
if (counts[i] > 0) {
result.push(String.fromCharCode(i + 'a'.charCodeAt(0)));
counts[i]--;
}
}
}
return result.join('');
};