The algorithmic complexity of your solution is \$O(N*C)\$ where \$N\$ is the number of characters in the string and \$C\$ is the size of the alphabet. Assuming lowercase ASCII characters, that's a linear algorithm and will probably be good enough for most things. That being said, it is possible to solve the same problem in \$O(N)\$.
Before that though, your current solution is hard to read. So let's simplify it.
- If most of your code is inside an if statement, use a guard instead.
- Don't use generic names like
dict
for variables. Especially not if the name is already taken. dict
is a class in python.
- You can use
in
and not in
operators instead of has_key
. e.g, if key in dictionary:
- Use functions like
max
and min
instead of conditionals. Actually, just avoid conditionals wherever possible. The more nested your code is, the harder it is to read.
Here's my take on the same program:
def lengthOfLongestSubstring(self, word):
if not word: return 0
currentLength = 1
longest = 1
lastSeen = {word[0] : 0}
i = 1
while i < len(word):
letter = word[i]
if letter in last_seen:
i = last_seen[letter] + 1
last_seen.clear()
longest = max(longest, currentLength)
currentLength = 0
else:
last_seen[letter] = i
currentLength += 1
i += 1
longest = max(longest, currentLength)
return longest
While it's a bit better, I still don't like it. Jumping back and forth is hard to wrap your head around, and it would be nice if the algorithm didn't do that.
Let's try brute force:
def lengthOfLongestSubstring(self, word):
n = len(word)
longest = 0
for i in range(n):
seen = set()
for j in range(i, n):
if word[j] in seen: break
seen.add(word[j])
longest = max(len(seen), longest)
return longest
This one is pretty easy to understand and is still \$O(N * C)\$ in the worst case. Practically this will generally be slower that your solution, but not by that much.
Is there a way to get both readability and speed though? Yep. We don't need to go that far from your idea either. Just a couple minor changes.
Let's think about left most character that you can still include in a replication free substring while going right. Clearly, this character must be to the right of any character that has been seen again.
If we know the position of this character than the longest substring ending at the current index is easy to calculate. Here's an implementation:
def lengthOfLongestSubstring(self, word):
# Initially we can go as far to the left as we want
left_most_valid = 0
longest = 0
last_seen = {}
for i, letter in enumerate(word):
if letter in last_seen:
# left_most_valid must be greater than any position which has been seen again
left_most_valid = max(left_most_valid, last_seen[letter] + 1)
last_seen[letter] = i
# Length of substring from left_most_valid to i is i - left_most_valid + 1
longest = max(longest, i - left_most_valid + 1)
return longest
This is an \$O(n)\$ algorithm and about as fast as reading the string from input.