A clear explanation of rearranging a string so that selected characters follow a custom order.
Problem Restatement
We are given two strings:
order
sAll characters in order are unique.
The string order defines a custom ordering of characters. We need to rearrange the characters of s so that characters appearing in order follow that same relative order.
Characters in s that do not appear in order may be placed anywhere in the result.
Return any valid permutation of s.
Input and Output
| Item | Meaning |
|---|---|
| Input | Strings order and s |
| Output | Any permutation of s that respects order |
| Custom rule | If x appears before y in order, then all xs must appear before all ys in the result |
| Extra characters | Characters not in order can go anywhere |
| Characters | Lowercase English letters |
Function shape:
class Solution:
def customSortString(self, order: str, s: str) -> str:
...Examples
Example 1:
order = "cba"
s = "abcd"The custom order says:
c before b before aThe character d does not appear in order, so it can be placed anywhere.
One valid answer is:
"cbad"Other valid answers include:
"dcba"
"cdba"
"cbda"Example 2:
order = "bcafg"
s = "abcd"The characters from s that appear in order are:
b, c, aThey must appear in that order.
The character d is not in order, so it can be placed anywhere.
One valid answer is:
"bcad"First Thought: Sort With a Custom Rank
One direct solution is to assign every character in order a rank.
For example:
order = "cba"gives:
| Character | Rank |
|---|---|
c | 0 |
b | 1 |
a | 2 |
Then we sort the characters of s by this rank.
Characters not in order can receive any default rank, because their position is flexible.
This works, but sorting costs O(n log n).
Since the alphabet is small and lowercase, we can do better with counting.
Key Insight
We do not need comparison sorting.
We only need to place characters from s in the order given by order.
So we can:
- Count how many times each character appears in
s. - Append characters that appear in
order, followingorder. - Append remaining characters that were not in
order.
This is a counting-sort style solution.
Algorithm
- Count all characters in
s. - Create an empty list
result. - For each character
chinorder:- Append
chexactlycount[ch]times. - Remove it from the count map.
- Append
- Append all remaining characters from the count map.
- Join the list into a string.
Correctness
The algorithm first counts every character in s, so it knows exactly how many copies of each character must appear in the output.
Then it processes characters in the same order as order. For every character ch in order, the algorithm appends all copies of ch before moving to the next character. Therefore, if character x appears before character y in order, all copies of x are appended before all copies of y.
After that, the algorithm appends characters not present in order. The problem allows these characters to appear anywhere, so placing them at the end is valid.
Every character from s is appended exactly as many times as it appears in s, and no extra characters are added. Therefore, the result is a valid permutation of s that respects the custom order.
Complexity
Let n = len(s) and m = len(order).
| Metric | Value | Why |
|---|---|---|
| Time | O(n + m) | Count s, process order, then append leftovers |
| Space | O(n) | The output list stores the result |
The count map stores at most 26 lowercase letters, so auxiliary counting space is O(1) with respect to input size.
Implementation
from collections import Counter
class Solution:
def customSortString(self, order: str, s: str) -> str:
count = Counter(s)
result = []
for ch in order:
if ch in count:
result.append(ch * count[ch])
del count[ch]
for ch, freq in count.items():
result.append(ch * freq)
return "".join(result)Code Explanation
We count every character in s:
count = Counter(s)The result is built as a list of string pieces:
result = []Then we process order from left to right:
for ch in order:If ch appears in s, append all its copies:
result.append(ch * count[ch])Then remove it from the counter so it will not be appended again later:
del count[ch]Finally, append characters not mentioned in order:
for ch, freq in count.items():
result.append(ch * freq)The problem allows these remaining characters to go anywhere, so appending them at the end is valid.
Return the joined string:
return "".join(result)Alternative: Custom Sorting
A shorter solution uses a rank map and Python sorting.
class Solution:
def customSortString(self, order: str, s: str) -> str:
rank = {ch: i for i, ch in enumerate(order)}
return "".join(sorted(s, key=lambda ch: rank.get(ch, len(order))))This is concise, but it costs O(n log n) time because it sorts the characters.
Testing
def is_valid(order: str, s: str, result: str) -> bool:
if sorted(s) != sorted(result):
return False
position = {ch: i for i, ch in enumerate(order)}
filtered = [ch for ch in result if ch in position]
for i in range(1, len(filtered)):
if position[filtered[i - 1]] > position[filtered[i]]:
return False
return True
def run_tests():
sol = Solution()
result = sol.customSortString("cba", "abcd")
assert is_valid("cba", "abcd", result)
result = sol.customSortString("bcafg", "abcd")
assert is_valid("bcafg", "abcd", result)
result = sol.customSortString("kqep", "pekeq")
assert is_valid("kqep", "pekeq", result)
result = sol.customSortString("abc", "xyz")
assert is_valid("abc", "xyz", result)
result = sol.customSortString("abc", "aaabbbccc")
assert is_valid("abc", "aaabbbccc", result)
print("all tests passed")
run_tests()Test coverage:
| Test | Why |
|---|---|
"cba", "abcd" | Standard custom order case |
"bcafg", "abcd" | Extra characters not in order |
"kqep", "pekeq" | Duplicate characters in s |
"abc", "xyz" | No characters from s appear in order |
"abc", "aaabbbccc" | Many repeated ordered characters |