161. One Edit Distance π
Problem Description
This problem asks you to determine if two strings are exactly one edit distance apart. Two strings are considered one edit distance apart if you can transform one string into the other by performing exactly one of these operations:
-
Insert one character: Add exactly one character at any position in string
s
to make it equal to stringt
. For example,"ab"
β"adb"
(insert'd'
). -
Delete one character: Remove exactly one character from string
s
to make it equal to stringt
. For example,"abc"
β"ac"
(delete'b'
). -
Replace one character: Change exactly one character in string
s
to a different character to make it equal to stringt
. For example,"abc"
β"adc"
(replace'b'
with'd'
).
The key requirements are:
- Exactly one edit operation must be performed (not zero, not more than one)
- For replacement, the character must be changed to a different character (not the same)
- Return
true
if the strings are one edit distance apart,false
otherwise
Examples of strings that are one edit distance apart:
"ab"
and"acb"
(insert'c'
)"cab"
and"ab"
(delete'c'
)"1203"
and"1213"
(replace'0'
with'1'
)
Examples of strings that are NOT one edit distance apart:
"ab"
and"ab"
(zero edits needed - they're already equal)"ab"
and"adb"
(this would be one edit, so it IS one edit distance apart)"ab"
and"adcb"
(requires two insertions)
Intuition
The key insight is that when two strings differ by exactly one edit, there must be exactly one position where they first differ. Once we find this position, we can determine what type of edit operation would make them equal.
Let's think about what happens when we compare two strings character by character:
-
If the lengths differ by more than 1, it's impossible to make them equal with just one edit. We'd need multiple insertions or deletions.
-
If the lengths are equal, the only possible operation is a replacement. When we find the first mismatched character, we need to check if the remaining parts after this position are identical. If they are, then replacing this one character would make the strings equal.
-
If one string is longer by exactly 1, we could either delete from the longer string or insert into the shorter one (these are equivalent operations from different perspectives). When we find the first mismatch, we can "skip" the character in the longer string and check if the remaining parts match.
To simplify our logic, we can always ensure that s
is the longer (or equal length) string by swapping if necessary. This way, we only need to handle the delete operation from s
's perspective, rather than both insert and delete cases.
The elegance of this approach is that we don't need to actually perform any edit operations. We just need to:
- Find where the strings first differ
- Check if skipping or replacing that character would make the rest of the strings match
- If we never find a difference, the strings are identical except possibly for one extra character at the end of the longer string
This gives us a clean O(n)
solution where we only need one pass through the strings.
Solution Approach
The implementation follows a systematic approach to handle all possible cases:
Step 1: Normalize the input
if len(s) < len(t):
return self.isOneEditDistance(t, s)
We ensure that s
is always the longer (or equal length) string by recursively calling the function with swapped parameters if needed. This simplifies our logic since we only need to handle deletion from s
rather than both insertion and deletion cases.
Step 2: Check length difference
m, n = len(s), len(t)
if m - n > 1:
return False
If the length difference is greater than 1, it's impossible to make them equal with exactly one edit, so we return False
immediately.
Step 3: Find the first difference
for i, c in enumerate(t):
if c != s[i]:
We iterate through the shorter string t
and compare each character with the corresponding character in s
. When we find the first mismatch at position i
, we need to determine what type of edit would work:
Case 3a: Equal length strings (m == n
)
return s[i + 1:] == t[i + 1:]
If the strings have equal length, the only option is replacement. We check if the remaining parts after position i
are identical. If they are, replacing s[i]
with t[i]
would make the strings equal.
Case 3b: Different length strings (m == n + 1
)
return s[i + 1:] == t[i:]
If s
is longer by 1, we can delete the character at position i
from s
. We check if skipping s[i]
and comparing the rest of s
with the rest of t
results in equal strings.
Step 4: Handle identical prefixes
return m == n + 1
If the loop completes without finding any difference, it means all characters in t
match the corresponding characters in s
. The strings are one edit apart only if s
has exactly one extra character at the end (which can be deleted to make them equal).
The time complexity is O(min(m, n))
where m
and n
are the lengths of the strings, as we iterate through at most the shorter string. The space complexity is O(1)
if we don't count the string slicing operations (which create new strings in Python), or O(m + n)
if we do count them.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the example where s = "cab"
and t = "ab"
.
Step 1: Check if we need to swap
len(s) = 3
,len(t) = 2
- Since
len(s) >= len(t)
, no swap needed s = "cab"
,t = "ab"
Step 2: Check length difference
m = 3
,n = 2
m - n = 1
, which is not greater than 1, so we continue
Step 3: Find the first difference
- Compare characters one by one:
i = 0
:t[0] = 'a'
vss[0] = 'c'
β Mismatch found!
Step 4: Determine the edit type
- Since
m = 3
andn = 2
, we havem == n + 1
(different lengths) - We check if deleting
s[0]
would make the strings equal:s[i + 1:] = s[1:] = "ab"
t[i:] = t[0:] = "ab"
"ab" == "ab"
β True!
The function returns True
because deleting the character 'c' at position 0 from "cab" gives us "ab", which equals t
.
Let's trace through another example where s = "abc"
and t = "adc"
:
Step 1: Check if we need to swap
- Both strings have length 3, no swap needed
Step 2: Check length difference
m = 3
,n = 3
m - n = 0
, so we continue
Step 3: Find the first difference
- Compare characters:
i = 0
:t[0] = 'a'
vss[0] = 'a'
β Matchi = 1
:t[1] = 'd'
vss[1] = 'b'
β Mismatch found!
Step 4: Determine the edit type
- Since
m == n
(equal lengths), we check if replacement would work:s[i + 1:] = s[2:] = "c"
t[i + 1:] = t[2:] = "c"
"c" == "c"
β True!
The function returns True
because replacing 'b' with 'd' at position 1 makes the strings equal.
Solution Implementation
1class Solution:
2 def isOneEditDistance(self, s: str, t: str) -> bool:
3 # Ensure s is always the longer or equal length string for simplicity
4 if len(s) < len(t):
5 return self.isOneEditDistance(t, s)
6
7 # Get the lengths of both strings
8 len_s, len_t = len(s), len(t)
9
10 # If length difference is more than 1, it's impossible to be one edit distance
11 if len_s - len_t > 1:
12 return False
13
14 # Iterate through the shorter string
15 for i, char in enumerate(t):
16 # When we find the first differing character
17 if char != s[i]:
18 # If strings have same length, it's a replace operation
19 # Check if remaining parts after the mismatch are equal
20 if len_s == len_t:
21 return s[i + 1:] == t[i + 1:]
22 # If s is longer, it's a delete operation from s (or insert to t)
23 # Check if s without current char equals remaining t
24 else:
25 return s[i + 1:] == t[i:]
26
27 # If no mismatch found in loop, strings are identical except possibly last char of s
28 # Return true only if s has exactly one more character than t (delete operation)
29 return len_s == len_t + 1
30
1class Solution {
2 /**
3 * Checks if two strings are exactly one edit distance apart.
4 * One edit distance means we can transform string s to string t by:
5 * 1. Inserting exactly one character
6 * 2. Deleting exactly one character
7 * 3. Replacing exactly one character
8 *
9 * @param s first string
10 * @param t second string
11 * @return true if strings are one edit distance apart, false otherwise
12 */
13 public boolean isOneEditDistance(String s, String t) {
14 int sLength = s.length();
15 int tLength = t.length();
16
17 // Ensure s is always the longer or equal length string for consistency
18 // This simplifies our logic by reducing cases to handle
19 if (sLength < tLength) {
20 return isOneEditDistance(t, s);
21 }
22
23 // If length difference is more than 1, impossible to be one edit apart
24 if (sLength - tLength > 1) {
25 return false;
26 }
27
28 // Iterate through the shorter string to find first difference
29 for (int i = 0; i < tLength; i++) {
30 // When we find the first character that differs
31 if (s.charAt(i) != t.charAt(i)) {
32 // Case 1: Same length strings - must be a replace operation
33 // Check if remaining substrings after position i are equal
34 if (sLength == tLength) {
35 return s.substring(i + 1).equals(t.substring(i + 1));
36 }
37 // Case 2: s is longer by 1 - must be a delete operation from s
38 // Skip character at position i in s and check if rest matches
39 return s.substring(i + 1).equals(t.substring(i));
40 }
41 }
42
43 // If no differences found in loop, strings are identical up to length of t
44 // They are one edit apart only if s has exactly one extra character at the end
45 return sLength == tLength + 1;
46 }
47}
48
1class Solution {
2public:
3 bool isOneEditDistance(string s, string t) {
4 int sLength = s.size();
5 int tLength = t.size();
6
7 // Ensure s is always the shorter or equal length string for consistency
8 // This simplifies our logic by reducing cases to handle
9 if (sLength < tLength) {
10 return isOneEditDistance(t, s);
11 }
12
13 // If length difference is more than 1, strings cannot be one edit apart
14 if (sLength - tLength > 1) {
15 return false;
16 }
17
18 // Iterate through the shorter string (t)
19 for (int i = 0; i < tLength; ++i) {
20 // When we find the first different character
21 if (s[i] != t[i]) {
22 // Case 1: Same length - must be a replace operation
23 // Check if remaining substrings after position i are identical
24 if (sLength == tLength) {
25 return s.substr(i + 1) == t.substr(i + 1);
26 }
27 // Case 2: s is longer by 1 - must be a delete operation from s
28 // Skip character at position i in s and compare remaining parts
29 return s.substr(i + 1) == t.substr(i);
30 }
31 }
32
33 // All characters matched in the comparison
34 // Strings are one edit apart only if s has exactly one extra character at the end
35 return sLength == tLength + 1;
36 }
37};
38
1/**
2 * Determines if two strings are exactly one edit distance apart.
3 * An edit can be: insert a character, delete a character, or replace a character.
4 *
5 * @param s - The first string to compare
6 * @param t - The second string to compare
7 * @returns true if strings are exactly one edit distance apart, false otherwise
8 */
9function isOneEditDistance(s: string, t: string): boolean {
10 const sLength: number = s.length;
11 const tLength: number = t.length;
12
13 // Ensure s is always the longer or equal length string for consistency
14 // This simplifies the logic by reducing cases to handle
15 if (sLength < tLength) {
16 return isOneEditDistance(t, s);
17 }
18
19 // If length difference is more than 1, they cannot be one edit apart
20 if (sLength - tLength > 1) {
21 return false;
22 }
23
24 // Iterate through the shorter string to find the first difference
25 for (let i: number = 0; i < tLength; i++) {
26 // When we find the first character that differs
27 if (s[i] !== t[i]) {
28 // If strings have same length, it must be a replacement
29 // Compare remaining parts after skipping current character in both strings
30 if (sLength === tLength) {
31 return s.slice(i + 1) === t.slice(i + 1);
32 }
33 // If s is longer by 1, it must be a deletion from s (or insertion to t)
34 // Compare remaining part of s after current position with remaining part of t from current position
35 else {
36 return s.slice(i + 1) === t.slice(i);
37 }
38 }
39 }
40
41 // If we've gone through all characters without finding a difference,
42 // the strings are one edit apart only if s has exactly one extra character at the end
43 return sLength === tLength + 1;
44}
45
Time and Space Complexity
The time complexity is O(m)
, where m
is the length of the longer string s
. The algorithm ensures s
is always the longer string through the recursive swap at the beginning. The main loop iterates through string t
at most once, which has length n β€ m
. The string slicing operations s[i + 1:]
and t[i + 1:]
or t[i:]
take O(m)
time in the worst case for comparison. Since we return immediately upon finding the first difference, we perform at most one such slicing comparison, giving us O(m)
overall time complexity.
The space complexity is O(m)
due to the string slicing operations which create new string objects. When comparing s[i + 1:] == t[i + 1:]
or s[i + 1:] == t[i:]
, Python creates temporary substring copies that can be up to O(m)
in size. Additionally, there's a recursive call at the beginning when len(s) < len(t)
, but this happens at most once and doesn't add to the asymptotic space complexity.
Note: The reference answer states O(1)
space complexity, which would be accurate if we consider string slicing as a comparison operation without creating copies, or if the implementation used index-based comparison instead of substring creation. However, in Python's standard implementation, string slicing creates new string objects.
Common Pitfalls
1. Forgetting to Handle the "Zero Edit" Case
The Pitfall:
A common mistake is not properly checking whether the strings are already identical. The problem specifically requires exactly one edit, not "at most one" edit. Many developers might write code that returns True
when the strings are already equal.
Incorrect Implementation:
def isOneEditDistance(self, s: str, t: str) -> bool:
if len(s) < len(t):
return self.isOneEditDistance(t, s)
m, n = len(s), len(t)
if m - n > 1:
return False
for i in range(n):
if s[i] != t[i]:
if m == n:
return s[i + 1:] == t[i + 1:]
else:
return s[i + 1:] == t[i:]
# Bug: Returns True even when strings are identical
return True # Wrong! This would return True for s="abc", t="abc"
The Fix:
The correct implementation should return True
only when s
has exactly one extra character compared to t
:
return len_s == len_t + 1 # Correct: only True if s has one extra character
2. Inefficient String Comparison Using Slicing
The Pitfall:
While the current solution is correct, using string slicing (s[i+1:]
and t[i+1:]
) creates new string objects in Python, which can be inefficient for very long strings. This adds unnecessary space complexity.
More Efficient Solution: Instead of creating substrings, compare characters directly:
def isOneEditDistance(self, s: str, t: str) -> bool:
if len(s) < len(t):
return self.isOneEditDistance(t, s)
len_s, len_t = len(s), len(t)
if len_s - len_t > 1:
return False
for i in range(len_t):
if s[i] != t[i]:
if len_s == len_t:
# Compare remaining characters one by one
return all(s[j] == t[j] for j in range(i + 1, len_t))
else:
# Compare s[i+1:] with t[i:] without creating substrings
return all(s[j + 1] == t[j] for j in range(i, len_t))
return len_s == len_t + 1
3. Not Handling Edge Cases Properly
The Pitfall: Failing to consider edge cases like empty strings or single-character strings.
Examples that might be mishandled:
s = "", t = "a"
(should returnTrue
- insert one character)s = "a", t = ""
(should returnTrue
- delete one character)s = "", t = ""
(should returnFalse
- zero edits needed)
Why the current solution handles these correctly: The normalization step and length check naturally handle these cases:
- Empty strings work because the iteration and final check
len_s == len_t + 1
correctly identify the one-character difference - The recursive swap ensures consistent handling regardless of which string is empty
What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!