From 7042f5b07d5291cdde5f9993b16e58536ea10513 Mon Sep 17 00:00:00 2001 From: Alex Small Date: Thu, 26 Mar 2026 14:33:35 +0000 Subject: [PATCH] Add solution, tests and explanation for longest substring without repeated characters. --- .../.test.ts | 96 +++++++++++++ .../explanation.md | 130 ++++++++++++++++++ .../solution.ts | 26 ++++ 3 files changed, 252 insertions(+) create mode 100644 problems/two-pointers-and-sliding-window/longest-substring-without-repeating-characters/.test.ts create mode 100644 problems/two-pointers-and-sliding-window/longest-substring-without-repeating-characters/explanation.md create mode 100644 problems/two-pointers-and-sliding-window/longest-substring-without-repeating-characters/solution.ts diff --git a/problems/two-pointers-and-sliding-window/longest-substring-without-repeating-characters/.test.ts b/problems/two-pointers-and-sliding-window/longest-substring-without-repeating-characters/.test.ts new file mode 100644 index 0000000..a38d8eb --- /dev/null +++ b/problems/two-pointers-and-sliding-window/longest-substring-without-repeating-characters/.test.ts @@ -0,0 +1,96 @@ +import { test, expect, describe } from 'vitest' +import { lengthOfLongestSubstring } from './solution' + +const expectCorrectLength = (inputs: [string, number][]) => { + inputs.forEach(([s, expected]) => { + expect(lengthOfLongestSubstring(s)).toBe(expected) + }) +} + +// Tests + +// Standard cases +describe('Longest substring without repeating characters is found correctly when:', () => { + test('There are no repeating characters in the string.', () => { + const inputs = [ + ['abcde', 5], + ['1234567890', 10], + ['!@#$%^&*()', 10] + ] satisfies [string, number][] + expectCorrectLength(inputs) + }) + test('There are no consecutive duplicate characters in the string.', () => { + const inputs = [ + ['abcdeafgh', 8], + ['optrsrtp', 5], + ['1abc123gh', 8] + ] satisfies [string, number][] + expectCorrectLength(inputs) + }) + test('There are consecutive duplicate characters in the middle of the string.', () => { + const inputs = [ + ['abcdeaaafghaab', 5], + ['optrssrtp', 5], + ['1abc1233gh', 6] + ] satisfies [string, number][] + expectCorrectLength(inputs) + }) + test('There are consecutive duplicate characters at the beginning or end of the string.', () => { + const inputs = [ + ['aabcde', 5], + ['abcdee', 5], + ['aabcdepp', 6] + ] satisfies [string, number][] + expectCorrectLength(inputs) + }) + test('Only one character in the string is unique.', () => { + const inputs = [ + ['aaaaab', 2], + ['111112', 2], + ['$$$$$1', 2] + ] satisfies [string, number][] + expectCorrectLength(inputs) + }) +}) + +// Edge Cases +describe('Length of longest substring without repeating characters is 1 when:', () => { + test('All characters in the string are the same.', () => { + const inputs = [ + ['aaaaaa', 1], + ['111111', 1], + ['$$$$$$', 1] + ] satisfies [string, number][] + expectCorrectLength(inputs) + }) + test('There is only one character in the string.', () => { + const inputs = [ + ['a', 1], + ['1', 1], + ['$', 1] + ] satisfies [string, number][] + expectCorrectLength(inputs) + }) +}) + +describe('Length of longest substring without repeating characters is 0 when:', () => { + test('Input string is empty.', () => { + const inputs = [ + ['', 0] + ] satisfies [string, number][] + expectCorrectLength(inputs) + }) +}) + +// Performance test +describe('Solution runs on large inputs without timeout.', () => { + test('Input string has length 5x10^4.', () => { + // Set length to maximum allowed by problem constraints + const n = 5 * 10 ** 4 + const s = 'a'.repeat(n / 2) + 'b'.repeat(n / 2) + const inputs = [ + [s, 2] + ] satisfies [string, number][] + expectCorrectLength(inputs) + }) +}) \ No newline at end of file diff --git a/problems/two-pointers-and-sliding-window/longest-substring-without-repeating-characters/explanation.md b/problems/two-pointers-and-sliding-window/longest-substring-without-repeating-characters/explanation.md new file mode 100644 index 0000000..f5aa8a3 --- /dev/null +++ b/problems/two-pointers-and-sliding-window/longest-substring-without-repeating-characters/explanation.md @@ -0,0 +1,130 @@ +# Longest Substring Without Repeated Characters +Difficulty: 🟠 Medium + +Patterns: Sliding Window with Set +## Problem +Given a string ```s```, find the longest substring without duplicate characters. +## Constraints +- ```0 <= s.length <= 5 * 104``` +- ```s``` consists of English letters, digits, symbols, and spaces. +## Examples +#### Example 1 +``` +s = "abcabcbb" +output = 3 +``` +The substrings ```"abc"```, ```"bca"```, and ```"cab"``` are all the longest, valid answers with length 3. +#### Example 2 +``` +s = "bbbbb" +output = 1 +``` +Since there is only one unique character in the string, the answer is 1. +#### Example 3 +``` +s = "tmmzuxt" +output = 5 +``` +The substring ```"mzuxt"``` has length 5 and contains no duplicates. +## Intuition +A suitable approach to this problem would be to maintain a sliding window of substrings, along with a set to keep track of which characters are currently 'taken'. We can use the set to determine whether to expand or shrink the window, keeping track of the longest valid window found so far. + + +- If the next character on the right is already in the window, we shrink the window from the left until it contains only unique characters again. +- If the next character on the right is not in the window, we expand the window from the right. + + +This approach guarantees all valid substrings are found because: +- Every character is considered the end of a potential substring exactly once. +- The left pointer moves forward to remove duplicates, ensuring the window isn't expanding while duplicates are present. +- No substring is missed that could be longer than the current maximum. + + +Since each character is added and removed from the set at most once, the total number of operations is at most 2n. + + +### Psuedo-Code +The following pseudo-code implements this intuition in a more structured way: +``` +Initialize set +Initialize max length +left = 0 + + +for character in string: + while character is in set: + remove string[left] from set + increment left + + add character to set + update max length + + +return max length +``` +## Solution +The solution is implemented formally by initializing the ```left``` to 0, the ```longestWindow``` to 1, and an empty set ```windowSet```. We loop through the array with ```right``` and perform the following sequence: +- If ```character = s[right]``` is currently in the set, remove the left pointer from the set and increment ```left```. Repeat until ```character``` is not in the set. +- Add ```character``` to the set, and update ```longestWindow```. + + +```javascript +for (let right=0; right