Learn how to solve this Data Structures and Algorithms problem so you can ace your next interview.

Two Pointers

Hash Sets

medium

Kenny is a software engineer and technical leader with four years of professional experience spanning Amazon, Wayfair, and U.S. Digital Response. He has taught courses on Data Structures and Algorithms at Galvanize, helping over 30 students land new software engineering roles across the industry, and has personally received offers from Google, Square, and TikTok.

Tom has four years of experience developing software professionally, including experience at both Amazon and Pinterest. He is experienced both as an interviewer, conducting interviews on behalf of the companies he's worked for, and interviewee, as he has landed software engineering offers from Google, Twitter, Stripe, Airtable and Doordash during previous job searches.

interviewing.io is a mock interview practice platform. In our lifetime we’ve hosted close to 100K mock interviews, conducted primarily by senior engineers from FAANG.

We have the recordings from these mock interviews, as well as post-interview feedback and outcomes. We’ve curated information from these interviews and interviewers to bring you the best guidance on solving interview problems on the web!

- Problem Description
- Examples Inputs and Outputs
- Example 1
- Example 2
- Example 3
- Constraints

- Problem Solution
- Watch Someone Solve Longest Substring Without Repeating Characters
- Problem Approaches
- 1. Brute Force
- Longest Substring Without Repeating Characters Python and JavaScript Solutions - Brute Force
- Time / Space Complexity Analysis
- 2. Sliding Window
- Longest Substring Without Repeating Characters Python and JavaScript Solutions - Sliding Window
- Time / Space Complexity Analysis
- 3. Sliding Window Optimized
- Longest Substring Without Repeating Characters Python and JavaScript Solutions - Sliding Window Optimized
- Time / Space Complexity Analysis

- Similar Questions

Given a string `s`

, find the length of the longest substring without repeating characters.

**Input:** "abcabcbb"

**Output:** 3

The longest substrings without repeating characters are `abc`

, `bca`

, and `cab`

, all with length 3.

**Input:** "bbbbb"

**Output:** 1

The longest substring without repeating characters is `b`

, with length 1.

**Input:** = "kvvfev"

**Output:** 3

The longest substring without repeating characters is `vfe`

, with length 3.

- 0 <= s.length <= 1000000

- The character set for
`s`

is ASCII.

When asked to explore a "subsection" of an array or string, always confirm your understanding of the search space. Remember that a substring is a contiguous slice of characters taken from a string, as opposed to a subsequence which can skip characters, or a permutation which can be out of order. With this knowledge, we can rule out any need for backtracking, and instead start to think more greedy.

Another important thing to confirm with your interviewer when handling strings is the character set, as this will help us define the space complexity of the problem. A safe assumption is that our solution will implement ASCII (128 characters) or Extended ASCII (256 characters)

With the search space defined, a naive approach becomes fairly straightforward: generate each substring from the input string, check if there are any repeating characters for each, and record the max length found for valid substrings.

We can separate this work into two subproblems. First, we need a helper function to determine if a given string has any repeating characters - this can be done by scanning a given range and counting character occurrences with a hash map.

Next, to generate each substring from the input string `s`

, we can enumerate all substrings with two nested loops. Given `i`

and `j`

as all possible start and end indices for a substring of `s`

, we have `0 <= i <= j <= n`

, where `n`

is the length of the input string.

- Time Complexity: O(n³). Two nested loops is O(n²), and the helper function makes an additional nested loop.

- Space Complexity: O(min(n, m)), where
`n`

is the length of the string`s`

and`m`

is the size of the character set.

To optimize the naive approach, let's see where duplicate work is being performed.

Consider this example: if the string "abc" has no repeating characters, and we append a new character "d" at the end to make the string "abcd", do we need to re-check if "abc" has repeating characters? Our two nested loops are doing just that - repeatedly checking sections of the string that have already been evaluated - and our helper function is re-checking for repeat characters without using information from overlapping checks.

Instead of naively enumerating each substring, how can we intelligently skip substrings that we already know to be invalid? Let's look at an example:

`kadb`

is a valid candidate for longest substring since it has no repeating characters.

If `j`

moves to index 4 though, the new substring `kadba`

would be invalid. So, we can deduce that with `i`

at position 0, all subsequent iterations of `j`

will be invalid, since they will contain this invalid substring. We can confidently stop iterating `j`

at this point.

So, let's iterate `i`

and begin considering new candidate substrings, since we've already seen the longest possible substring with `i`

at index 0. But `adba`

is also invalid, so any iteration on `j`

will remain invalid.

Let's iterate `i`

until the substring in the range `i`

to `j`

is valid again. Here, once `i`

is at index 2, there is only one `a`

character in the substring, so we can again try to increase the length by iterating `j`

anew.

This technique is known as a **sliding window**. The window represents a candidate substring bounded by a start and an end pointer, which expands and contracts based on what we're looking for. Sliding windows are commonly used when searching for an **optimal** range in an iterable data structure like a string or an array. This is because the algorithm will first search for a *possible* answer before then expanding (or contracting, whichever is the priority) to try to optimize. For example, if we were searching for the shortest substring that contains the letters `a`

and `b`

, we would only contract our window if those letters existed in the current substring (valid condition), otherwise we're adding characters until the condition is met.

Conversely, when looking for the maximum possible length, as in the problem at hand, we can only expand the window when the current substring is valid (otherwise, we must first arrive at a valid state by removing characters from the left). Looking back at the example above, `j`

only iterates forward while the condition is satisfied - in this case, all characters between `i`

and `j`

are unique - and `i`

only iterates forward while the condition is not satisfied.

Having made this insight on our substring enumeration, we can further improve the way we check for substrings with unique characters. Remember that a sliding window allows us to take advantage of overlapping subproblems - so, instead of re-checking each window for unique characters, let's track the current character count of the window and evaluate the new state when a single character is added or removed.

More formally, if we know that a substring in the range `i`

to `j`

has no repeating characters, then when adding the next character at `j+1`

, we simply need to check if that character already exists in the previous range. And because we established the character set at the beginning of our solution, this can be determined in constant time using an array of length 128 to represent character occurrence in a substring.

Note that, although there are still two nested loops in our code, the time complexity of the iteration is now linear, as `i`

and `j`

will only iterate over each character once. Unlike our naive implementation, the two pointers are not dependent on one another, and instead iterate based on the state of the range between them, which can be determined in constant time and space.

- Time Complexity: O(n). At worst, the two pointers both perform linear scans of the string, which would be O(2n). This can be interpreted as O(n) in Big-O notation, since constants are ignored when determining order of magnitude.

- Space Complexity: O(1). Limited to 128 ASCII characters, space is constant.

As a marginal optimization, we can avoid two passes with our pointers by improving how we cache character occurrences and update our pointers.

Remember what we determined in the previous approach: once a duplicate character is found in a given window, this candidate substring will remain invalid until one of the dupes is removed. Seeing that we need to arrive at a valid state before we can continue iterating our right pointer, can we improve how we iterate the left pointer? Indeed! Instead of iteratively searching for the next valid position, we can deduce where it would necessarily be: the index directly after the first occurring duplicate's index in the substring. Moving `i`

to this position effectively removes the duplicate from the substring.

Tracking character counts in our hash map won't serve us anymore - instead, let's record the last index at which each character was encountered, so the left pointer can be updated in constant time. Thus when the right pointer finds a repeat, we simply update the left pointer to the position of the last occurring repeat (plus 1).

- Time Complexity: O(n)

- Space Complexity: O(1)

Reveal solution

Book mock interviews with engineers from Google, Facebook, Amazon, or other top companies. Get detailed feedback on exactly what you need to work on. Everything is anonymous, and you don't have to until you find a job.