Problem overview
The function longest_common_prefix takes two strings, s1 and s2, and returns the longest string that both of them start with. For "hello" and "help" the answer is "hel": the first three characters match, and then l meets p. For "Andy" and "Mircea" the first characters already differ, so the answer is the empty string. For "AlgoCademy" and "Algo" the answer is "Algo", because the shorter string is itself a prefix of the longer one.
Approach
Compare the strings position by position from the start. While the characters at the same position are equal, they belong to the prefix, so we add them to the result. The first mismatch ends the prefix, since a common prefix must be unbroken from the start. Later positions might match again, for example in "abxd" and "abyd" the last characters are equal, but they are not part of a prefix.
There is also a limit on how far to look: the prefix cannot be longer than the shorter string. Comparing only up to the shorter length means every index is valid in both strings.
The brute-force alternative, trying every prefix length and checking it with starts_with, repeats comparisons. The single pass compares each position at most once.
Step by step
- Collect the characters of both strings into vectors. Rust does not let you write
s1[i]on aString. Text is stored as UTF-8 bytes, and a character can use more than one byte, so a position in the text is ambiguous. Collectingchars()into aVec<char>gives plain indexing by character. - Prepare the result and the limit.
resultstarts as an emptyStringand must bemut.min_lenis the smaller of the two lengths, computed withmin. - Loop over the indices
0..min_len. The range excludesmin_len, so each index exists in both vectors. - Compare the characters. If
chars1[i]equalschars2[i], append that character toresultwithpush, which adds onechar. - Stop at the first difference. In the
elsebranch,breakleaves the loop. - Return
result. That line runs after abreakand also when the loop finishes allmin_lenpositions, which is the case when the shorter string is a prefix of the other.
The solution in Rust
impl Solution {
fn longest_common_prefix(s1: String, s2: String) -> String {
let chars1: Vec<char> = s1.chars().collect();
let chars2: Vec<char> = s2.chars().collect();
let mut result = String::new();
let min_len = chars1.len().min(chars2.len());
for i in 0..min_len {
if chars1[i] == chars2[i] {
result.push(chars1[i]);
} else {
break;
}
}
return result;
}
}
Language details:
Stringversus&str. The parameters are ownedStringvalues, so the function takes ownership of its arguments. We only read from them, which is why we can callchars()on them directly.- Type annotations on
collect.collect()can build many collections, so the compiler needs to know which one. The annotationVec<char>on the variable tells it. charis a Unicode scalar value, 4 bytes wide, not a byte. The vector holds one entry per character, sochars1.len()counts characters, whiles1.len()would count bytes. For"héllo"and"hélp", the function returns"hél", three characters. A byte-based comparison would work differently, becauseétakes two bytes.minon integers.chars1.len().min(chars2.len())returns the smallerusize. The indices areusizetoo, which is what indexing requires.return result;is legal at the end of a function, though idiomatic Rust often leaves outreturnand the semicolon and ends with the bare expressionresult.pushversuspush_str.pushappends onechar, andpush_strappends a string slice.
Tracing an example
With s1 = "hello" and s2 = "help", chars1 has 5 entries and chars2 has 4, so min_len is 4. At i = 0 both characters are h, so result becomes "h". Then e matches e and l matches l, which gives "hel". At i = 3 the characters are l and p, which differ, so the loop breaks and the function returns "hel". The final o of "hello" is never looked at.
Complexity
Collecting the characters takes O(n + m) time for strings of lengths n and m. The loop does at most min(n, m) iterations with constant work each. The total time is O(n + m). The two vectors and the result take O(n + m) extra space. This could be reduced to O(1) beyond the result by walking both chars() iterators together with zip, but the vectors keep the lesson's code simple and indexable.
Edge cases and common mistakes
- An empty string.
min_lenis 0, the loop body never runs, and the result is the empty string. - No common prefix. The first comparison fails and
breakleavesresultempty. - Identical strings. The loop matches every position, and the result equals the string.
- One string is a prefix of the other. The loop finishes without a
break, and the shorter string comes back whole. - Looping to the longer length. Indexing past the end of the shorter vector panics, because out-of-bounds indexing is checked in Rust. Always bound the loop by
min_len. - Forgetting
break. Without it, matching characters after a mismatch would be added to the result, and"abxd"with"abyd"would give"abd"instead of"ab". - Case. The comparison is case-sensitive, so
"Algo"and"algo"have an empty common prefix.
Testing
"hello","help"gives"hel"."Andy","Mircea"gives""."AlgoCademy","Algo"gives"Algo"."","x"gives"".
