Problem overview
sortByLength receives a vector of words and returns it sorted from shortest to longest. Words of the same length go in alphabetical order. A word that appears twice stays in the result twice.
For ["banana", "fig", "apple", "kiwi"] the result is ["fig", "kiwi", "apple", "banana"]. For ["dog", "cat", "ant", "bee"] all lengths are 3, so the result is alphabetical: ["ant", "bee", "cat", "dog"]. For ["sun", "a", "moon", "sun"] it is ["a", "sun", "sun", "moon"].
Approach: tell sort how to compare two words
sort(words.begin(), words.end()) on its own orders strings alphabetically, so "banana" would come before "fig". To sort by length, give sort a third argument: a function that takes two elements and answers one question, "should a go before b?". A lambda written in place is the usual way to do that.
The question has two rules. First rule: if the lengths differ, the shorter word goes first. Second rule, used only when the lengths are equal: the alphabetically earlier word goes first. For two strings, a < b is true when a comes first from A to Z.
Step by step
- Call
sorton the whole vector, fromwords.begin()towords.end(), with a lambda taking twoconst string&,aandb. - Write rule one: if
a.size() != b.size(), returna.size() < b.size(). - Write rule two below it: return
a < b. - Return
words.sortrearranges the vector itself, and it never drops or adds elements, so duplicates stay.
The solution in C++
class Solution {
public:
vector<string> sortByLength(vector<string> &words) {
sort(words.begin(), words.end(), [](const string& a, const string& b) {
if (a.size() != b.size()) {
return a.size() < b.size();
}
return a < b;
});
return words;
}
};
Details that matter:
- The lambda answers "strictly before". It returns
trueonly whenamust go beforeb. For equal words it returnsfalseboth ways, which is whatsortrequires. sortchanges the vector in place. The parameter is a reference, so the caller's vector is sorted too, and the function returns a copy of it.- The lambda has two
returns of the same type,bool, which lets the compiler work out its return type. - The sort is not stable, but that doesn't matter here: equal words are identical strings, and different words with the same length are ordered by rule two.
Another way to write the rules
Two pairs compare their first values and then their second values, so the rules can also be written as one comparison of (length, word) pairs:
sort(words.begin(), words.end(), [](const string& a, const string& b) {
return make_pair(a.size(), a) < make_pair(b.size(), b);
});
On ["sun", "a", "moon", "sun"] it gives a sun sun moon, the same as the lesson's code. The two-rule version spells out each step, which is what the lesson practices.
Edge cases and common mistakes
- Forgetting the tie rule. With only rule one, words of equal length may come out in any order, so
["dog", "cat", "ant", "bee"]is not guaranteed to give["ant", "bee", "cat", "dog"]. - Returning
a.size() <= b.size(). A comparison must returnfalsefor equal elements. With<=it is invalid forsortand may misbehave or even crash on inputs with equal words, such as the duplicate"sun". - Wrong direction.
a.size() > b.size()puts the longest words first. - Sorting alphabetically only. The plain
sortwould give["apple", "banana", "fig", "kiwi"]for example 1. - Removing duplicates. Sorting never does; both
"sun"copies remain.
Why two rules, in this order
The order of the rules is part of the answer. Length is the main key, so it is tested first. Alphabetical order is only a tie-breaker, so it is tested second, and only runs when the lengths are equal. Swapping the rules would sort alphabetically and use length only to break ties, which gives ["a", "moon", "sun", "sun"] for the third example instead of the expected result.
Testing
["banana", "fig", "apple", "kiwi"]givesfig kiwi apple banana.["dog", "cat", "ant", "bee"]givesant bee cat dog.["sun", "a", "moon", "sun"]givesa sun sun moon.
