Две трети задач на массивы на собеседованиях решаются двумя паттернами: sliding window и two pointers. Если вы их освоите — большинство задач будет сводиться к распознаванию «а, это sliding window» и написанию 15 строк кода.
Разбираем оба паттерна: когда использовать, как реализовать и на каких задачах тренироваться.
Массив или строка — окно фиксированной или переменной длины «едет» по ним, и на каждом шаге мы обновляем результат. Вместо вложенных циклов — один проход, O(n).
function slidingWindow(arr, k) {
let windowSum = 0;
let maxSum = -Infinity;
for (let i = 0; i < arr.length; i++) {
windowSum += arr[i];
if (i >= k) {
windowSum -= arr[i - k];
}
if (i >= k - 1) {
maxSum = Math.max(maxSum, windowSum);
}
}
return maxSum;
}Дан массив nums и число k. Найдите максимальную сумму подмассива длины k.
function maxSumSubarray(nums, k) {
let windowSum = 0;
for (let i = 0; i < k; i++) {
windowSum += nums[i];
}
let maxSum = windowSum;
for (let i = k; i < nums.length; i++) {
windowSum += nums[i] - nums[i - k];
maxSum = Math.max(maxSum, windowSum);
}
return maxSum;
}
maxSumSubarray([2, 1, 5, 1, 3, 2], 3); // 9 (подмассив [5, 1, 3])Сложность: O(n) время, O(1) память.
Дан массив положительных чисел nums и число target. Найдите максимальную длину подмассива, сумма которого не превышает target.
function longestSubarray(nums, target) {
let windowSum = 0;
let left = 0;
let maxLen = 0;
for (let right = 0; right < nums.length; right++) {
windowSum += nums[right];
while (windowSum > target) {
windowSum -= nums[left];
left++;
}
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
longestSubarray([3, 1, 2, 7, 4, 2, 1, 1, 5], 8); // 4Внутренний while сдвигает левую границу, пока условие не выполнится. Это variable-size sliding window — длина окна меняется.
Дан массив из 0 и 1 и число k — максимальное количество нулей, которое можно заменить на единицы. Найдите длину самого длинного подмассива единиц.
function longestOnes(nums, k) {
let left = 0;
let zeros = 0;
let maxLen = 0;
for (let right = 0; right < nums.length; right++) {
if (nums[right] === 0) zeros++;
while (zeros > k) {
if (nums[left] === 0) zeros--;
left++;
}
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
longestOnes([1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0], 2); // 6Ключевая идея: окно «допускает» до k нулей. Когда нулей больше — сдвигаем левую границу.
Два указателя движутся навстречу друг другу или в одном направлении по отсортированному массиву. На каждом шаге — сравнение и решение: сдвинуть левый, правый или оба.
function twoPointers(arr) {
let left = 0;
let right = arr.length - 1;
while (left < right) {
const sum = arr[left] + arr[right];
if (sum === target) return [left, right];
if (sum < target) left++;
else right--;
}
return [-1, -1];
}Дан отсортированный массив nums и число target. Найдите индексы двух элементов, сумма которых равна target.
function twoSumSorted(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === target) return [left, right];
if (sum < target) left++;
else right--;
}
return [-1, -1];
}
twoSumSorted([2, 7, 11, 15], 9); // [0, 1]
twoSumSorted([1, 2, 3, 4, 6], 6); // [1, 3]Сложность: O(n) время, O(1) память. Вместо хэш-таблицы — просто два указателя.
Проверьте, является ли строка палиндромом, учитывая только буквы и цифры.
function isPalindrome(s) {
let left = 0;
let right = s.length - 1;
while (left < right) {
while (left < right && !/[a-z0-9]/i.test(s[left])) left++;
while (left < right && !/[a-z0-9]/i.test(s[right])) right--;
if (s[left].toLowerCase() !== s[right].toLowerCase()) return false;
left++;
right--;
}
return true;
}
isPalindrome("A man, a plan, a canal: Panama"); // true
isPalindrome("race a car"); // falseУказатели прыгают через не-алфавитные символы и сравнивают по краям к центру.
Удалите дубликаты на месте. Верните количество уникальных элементов.
function removeDuplicates(nums) {
if (nums.length === 0) return 0;
let slow = 0;
for (let fast = 1; fast < nums.length; fast++) {
if (nums[fast] !== nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}
removeDuplicates([1, 1, 2, 3, 3]); // 3, массив → [1, 2, 3, ...]Здесь два указателя движутся в одном направлении: fast ищет новые элементы, slow записывает их на позицию|unique.
Найдите длину самой длинной подстроки без повторяющихся символов.
function lengthOfLongestSubstring(s) {
const seen = new Set();
let left = 0;
let maxLen = 0;
for (let right = 0; right < s.length; right++) {
while (seen.has(s[right])) {
seen.delete(s[left]);
left++;
}
seen.add(s[right]);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
lengthOfLongestSubstring("abcabcbb"); // 3
lengthOfLongestSubstring("bbbbb"); // 1Это sliding window + hash set. Окно расширяется вправо, сжимается слева при повторе.
function merge(nums1, m, nums2, n) {
let i = m - 1;
let j = n - 1;
let k = m + n - 1;
while (i >= 0 && j >= 0) {
if (nums1[i] > nums2[j]) {
nums1[k--] = nums1[i--];
} else {
nums1[k--] = nums2[j--];
}
}
while (j >= 0) {
nums1[k--] = nums2[j--];
}
return nums1;
}
merge([1, 2, 3, 0, 0, 0], 3, [2, 5, 6], 3); // [1, 2, 2, 3, 5, 6]Два указателя идут с конца обоих массивов, записывая в конец nums1.
| Условия задачи | Паттерн |
|---|---|
| Подмассив фиксированной длины | Sliding window (fixed) |
| Подмассив с условием (сумма, уникальность) | Sliding window (variable) |
| Отсортированный массив + пара | Two pointers (с концов) |
| Удаление/запись на месте | Two pointers (slow/fast) |
| Палиндром, сравнение строк с краёв | Two pointers |
Эти паттерны — основа большинства алгоритмических задач. Но знание паттерна без практики не работает: нужно научиться распознавать, когда какой паттерн применять, и быстро писать чистый код.
Попробуйте решить задачи на Девстанции — здесь есть десятки задач, которые тренируют sliding window и two pointers. AI проверит ваш код и подскажет, если есть более эффективное решение.
А если готовитесь к собеседованию — посмотрите чеклист из 50 вопросов и план подготовки за неделю.