Skip to content
DSAPrep

    ↑↓ to move · Enter to open · Esc to close

    Longest Substring Without Repeating Characters

    Given a string s, find the length of the longest substring without duplicate characters.

    Example 1

    Input: s = "abcabcbb"

    Output: 3

    Explanation: The answer is "abc", with the length of 3.

    Example 2

    Input: s = "bbbbb"

    Output: 1

    Explanation: The answer is "b", with the length of 1.

    Example 3

    Input: s = "pwwkew"

    Output: 3

    Explanation: The answer is "wke", with the length of 3. Notice that the answer must be a substring; "pwke" is a subsequence and not a substring.

    Constraints

    • 0 <= s.length <= 10^5
    • s consists of English letters, digits, symbols and spaces.
    View original on LeetCode ↗

    The brute-force way is to check every substring for duplicates. The key insight for the optimal approach: maintain a window [left, right] that never contains a duplicate. When extending right would introduce a duplicate, do not restart from scratch — just slide left forward past the previous occurrence of that character. A hash map from character to its last-seen index makes that jump O(1).

    Brute Force

    Time O(n³)Space O(min(n, m))

    For every starting index i, grow j outward while a set of seen characters stays duplicate-free, tracking the best length found.

    class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
    n = len(s)
    best = 0
    for i in range(n):
    seen = set()
    for j in range(i, n):
    if s[j] in seen:
    break
    seen.add(s[j])
    best = max(best, j - i + 1)
    return best

    Why it’s slow: for each of the n starting indices we rebuild a fresh set and rescan forward, so building every substring costs O(n²) substrings times O(n) work to check each one — O(n³) overall (or O(n²) with the early break, but still redundant: index i+1 recomputes everything index i already knew about the same characters).

    Sliding Window (Hash Map)

    OptimalTime O(n)Space O(min(n, m))

    Walk right across the string once. Keep a map of character -> last index seen. If the character at right was last seen at or after left, it is a duplicate inside the current window — jump left to one past that occurrence. Otherwise the window is still duplicate-free. Either way, update the map and check if the current window [left, right] is the longest so far.

    class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
    seen = {} # char -> last index seen
    left = 0
    best = 0
    for right, ch in enumerate(s):
    if ch in seen and seen[ch] >= left:
    left = seen[ch] + 1
    seen[ch] = right
    best = max(best, right - left + 1)
    return best

    Watch the L marker jump past a repeat instead of creeping forward, while the last-seen map updates every step.

    LR
    a
    0
    b
    1
    c
    2
    a
    3
    b
    4
    c
    5
    b
    6
    b
    7
    window [0,0] = abest 1L held

    char → last index

    a → 0
    1 / 8
    currentcomparingresultseen

    a is unseen, so the window extends to index 0. best becomes 1.

    Correctness: left only ever moves forward, and it always jumps to exactly one past the most recent duplicate, so [left, right] is always the longest duplicate-free window ending at right. Every window length is checked, so the true maximum is found.

    Complexity: each index is visited by right exactly once and by left at most once, so the total work is O(n). The map holds at most min(n, m) entries, where m is the size of the character set — O(min(n, m)) space.

    More Sliding Window practice