The answer by ggorlen covers most of the points and the alternative that was presented is very efficient, well sort of. In terms of complexity it is less complex and that shows when you compare your function.
If we use your algorithm and clean up some of the nasty bits, we get.
function longestSubstr(str) {
var res = 0, tmp = [];
for (const char of str){
const idx = tmp.indexOf(char);
if (idx > -1) { tmp = tmp.slice(idx + 1) }
tmp.push(char);
if (tmp.length > res) { res = tmp.length }
}
return res;
}
Which makes it competitive up to about 160-180 characters, at which point using a hashing function beats the cost of stepping over each character.
If you consider a string 1Mb long your function does not stand a chance.
Beating the hashing function.
All the benefit of maps and sets come from the hash function. It takes an object (in this case a character) and turns it into a unique index that gives you the memory address of the data you are after.
In JavaScript the hash function must work for any type of data, not just strings and it is impressive at how well it does, but in this case, with 2 caveats, there is an even faster way.
The caveats:
It takes 256 array items to hold all the ASCII characters and we can create a unique hash directly from the ASCII character code.
The resulting function has the same complexity but by avoiding the JS hash calculation we get an order of magnitude better performance.
The following runs 10times faster than the existing answer. The algorithm (some slight mods) is ggorlen's so the credit is belongs to him
const longestASCIISubStr = (() => {
const charLookup = new Uint32Array(256);
return function (str) {
var max = 0, start = 0, i = 0, char;
charLookup.fill(0);
const len = str.length;
while (i < len) {
const pos = charLookup[char = str.charCodeAt(i)];
if (pos && start < pos) {
max < i - start && (max = i - start);
if (max > len - pos && i + max >= len) { return max }
start = pos;
}
charLookup[char] = ++i;
}
return Math.max(len - start, max);
}
})();
I do not know the conditions of leetcode runtime environment so it may not work as allocating the lookup array is costly and will bring the average down for short strings.
The following does not require the reserved memory and is only about 5 times as fast for long string and equal at about 30 character strings but may be accepted as valid.
function longestASCIISubStr(str) {
var max = 0, start = 0, i = 0, char;
const charLookup = new Uint32Array(256);
const len = str.length;
while (i < len) {
const pos = charLookup[char = str.charCodeAt(i)];
if (pos && start < pos) {
max < i - start && (max = i - start);
if (max > len - pos && i + max >= len) { return max }
start = pos;
}
charLookup[char] = ++i;
}
return Math.max(len - start, max);
}
The problem with the less complex solution is that it comes with considerable overhead. The hashing function is complex compared to searching a dozen characters.