right-leftTwo Pointers

Notes and typical questions for two pointers algorithm

Notes

  • Check if the question requires manipulating the array in place. If not, maybe having a new array can resolve it quickly.

Typical Questions

Other 2 Pointers Problems

  • LC 977. Squares of a Sorted Array

  • 🔴 LC 76. Minimum Window Substring

  • LC 438. Find All Anagrams in a String

    • This is also listed under HashMap, my LC submission's time complexity is O(n)

  • 🌟 🔴 LC 962. Maximum Width Ramp

  • 🌟 🔴 LC 2863. Maximum Length of Semi-Decreasing Subarrays

    • This question is the same as LC 962

    • Approach 1: Monotonix Stack. TC: O(n)O(n), SC: O(n)O(n)

    • Approach 2: 2 pointers + rightMin array. TC: O(n)O(n), SC: O(n)O(n)

  • LC 696. Count Binary Substrings

Strings Match

  • LC 392. Is Subsequence

    • Method 1: 2 pointers. Its performance is better DP and it's super easy to implement;

    • 🟠 Method 2: DP. Need to realize it's very similar to LC 1143...

  • LC 2337. Move Pieces to Obtain a String

  • LC 408. Valid Word Abbreviation

N Sum Problems

  • LC 167. Two Sum II - Input Array Is Sorted

  • 🌟 LC 15. 3Sum

    • Although "2 sum" can be solved by HashMap, this 3 sum is actually a 3 pointers problem, it's hard to dedup result without sorting input array

  • LC 16. 3Sum Closest

  • LC 1925. Count Square Sum Triples

  • LC 11. Container With Most Water

    • This is a two-pointer problem (variant of LC 167), but it is easily mistaken for a monotonic stack solution.

    • Optimal Answerarrow-up-right. TC: O(n)O(n), SC: O(1)O(1)

    The widest container (using first and last line) is a good candidate, because of its width. Its water level is the height of the smaller one of first and last line. All other containers are less wide and thus would need a higher water level in order to hold more water. The smaller one of first and last line doesn't support a higher water level and can thus be safely removed from further consideration. if h[i], h[j], where h[i]==h[j], are at maximum width, then for any other pair of heights with narrower width, the min height would need to be taller than both h[i] and h[j]. So NEITHER i or j is a valid contender for future maximums at a narrower widthand you can actually increment/decrement both. You can change the code to check for equal heights and do i++ and j-- and verify.

Merge Elements

  • LC 88. Merge Sorted Array

Delete/Overwrite Elements

  • Notes

    • Fast and slow pointers are useful for questions related to moving/removing items in an array. The fast pointer is used for exploring the array. The slow pointer is used to point to the index that can store value when the fast pointer points to the expected value.

    • For moving element, consider it in 2 ways: moving target value or moving items that are not target values

    • Check if the question requires keeping the array in the same order at the end

    • When swapping f/s pointer items, always introduce temp value to avoid exceptions in corner cases (LC 283)

  • LC 26. Remove Duplicates from Sorted Array

  • LC 80. Remove Duplicates from Sorted Array II

    • It's easy to miss a corner case (if you just compare nums[f] and nums[f-2])

    • The optimal solution is more logical than the solution I came up with by myself (although TC and SC are the same)

    • Optimal Answerarrow-up-right. TC: O(n)O(n), SC: O(1)O(1)

  • LC 27. Remove Element

  • LC 443. String Compression

  • LC 283. Move Zeroes

  • LC 2390. Removing Stars From a String

    • Stack approach: Use StringBuilder to simulate Stack. Answerarrow-up-right. TC: O(n)O(n), SC: O(n)O(n)

    • 2 pointers approach: Answerarrow-up-right. TC: O(n)O(n), SC: pseudoO(1)pseudo-O(1) due to the immutability of String in Java

Sliding Window - General

Sliding Window - Subarray/Substring Search Meeting With Requirements

Notes

  • Two-pointers are also useful when you need to explore a set of subarrays/substrings that meet certain conditions in a given input array/string and calculate results while exploring subarrays. For this kind of question, you usually need to use a fast point to explore the array and move the left pointer when the subarray does not meet the condition.

🧩 Sliding Window with Aggressive Shrinking

  • Notes

    • You may need to use while loop to move the left pointer until the subarray meets the condition.

  • LC 3. Longest Substring Without Repeating Characters

    • Approach 1: Normal 2 pointers.

    • 👍 Approach 2: Optimized 2-pointers. Reduce the inside while loop from the outside for loop. If the window has duplicates, We can move the left pointer directly to the right element of the duplicated one. Since this way will skip removing elements from Map, we need to ensure the index of duplicates gets from Map is within the window.

  • 🌟 LC 209. Minimum Size Subarray Sum

  • 🔴 LC 904. Fruit Into Baskets

  • 🔴 LC 567. Permutation in String

    • Be careful that there are 2 invalid cases (1. character is not in the target string, 2. character is in the target string but already occurred in windows previously)

    • if it is the 2nd invalid case, after taking actions, it may become a valid case!

    • Therefore, we should always check if it is a valid case at the end of each iteration

  • 🔴 LC 3346. Maximum Frequency of an Element After Performing Operations I

    • I made some progress. I realized the input array needs to be sorted first, and get an O(n2)O(n^2) idea for the 1st case below, but then I got stuck.

    • 2 cases for central point (the value that elements get updated to)

      • It's an existing element

        • After sorting, for each element e in the array, there is a technique to find the number of elements within the range [e-k,e+k] in O(n)O(n) by scanning the array once.

      • It's a value that does not exist in the input array

    • Optimal Answerarrow-up-right. TC: O(nlogn)O(n*log{n}), SC: O(n)O(n)

  • LC 2762. Continuous Subarrays

    • There is another optimal answer that only uses 2 pointers without TreeMap . Since it has the same TC and SC as my answer and it's more complicated to understand, I did not use it.

    • Optimal Answerarrow-up-right.

      • Let nn be the size of input, and kk be the size of TreeMap . Since kk should always <= 3 based on the requirement of the question, we can treat kk as a constant

      • TC: O(nlogk)=>O(n)O(n*log{k}) => O(n)

      • SC: O(k)=>O(1)O(k) => O(1)

  • 🔴 LC 30. Substring with Concatenation of All Words

  • 🌟 🔴 LC 76. Minimum Window Substring

    • Common mistake when simply using the normal sliding window idea

      • Pitfall: Cannot simply move the left pointer when the right pointer points to such a char c (String t has c with number k, and we already see k times of c in the current window). Reason is that it's possible to have k+1 times of c in the final answer window. For example, s="abbc", t="abc"

      • Solution: Core idea to resolve concerns above: Once you have a valid window, aggressively move the left pointer until it becomes invalid.

    • Answer

  • 🟠 LC 2106. Maximum Fruits Harvested After at Most K Steps

    • Approach 1 with TreeMap (my own solution)

    • Approach 2 with Binary Search

      • Implementation trick: Use prefixSum[n+1], which means prefix[i] represents the sum of elements in the half-open range [0, i) (i.e., before index i).

      • Answerarrow-up-right. TC: O(n+klogn)O(n+klogn), SC: O(n)O(n)

    • 👍 Approach 3 with Sliding Window

🧩 Sliding Window with Non-Aggressive Shrinking

  • Notes

    • You may NOT use while loop to move the left pointer until the subarray meets the condition and NOT shrink the window below the max window size found previously.

  • 🌟 LC 1004. Max Consecutive Ones III

    • Think it this way instead: Find the max window which includes at most k 0's

      • Straightforward idea: Have a 2 loops. Outer loop to explore array. When the window is invalid, the inner loop is used to keep reducing windows from the left side until it becomes valid. Answerarrow-up-right. TC: O(n)O(n), SC: O(1)O(1)

      • Improved idea: No need for the inner loop. when the window becomes invalid, j - i effectively represents the current max window size. We don’t need to keep shrinking the window from the left until it becomes valid; we only need to maintain the current size and slide the window forward. Once the window becomes valid again after a shift, we can naturally continue expanding it. Answerarrow-up-right. TC: O(n)O(n), SC: O(1)O(1)

  • LC 1493. Longest Subarray of 1's After Deleting One Element

  • 🔴 LC 424. Longest Repeating Character Replacement

    • The idea of not shrinking window size is similar to LC 1004

      • When finding the first valid window, assume the window size is l and the max frequency of characters within this window is maxFreq. Valid window means l-maxFreq <= k

      • In the next round,

        • Increase windows by moving end index to the right by 1.

        • If new l+1 length window becomes invalid now, we just need to move start index to right by 1 and update the frequency map.

        • Then the window size becomes to l again but this new l size window may be INVALID and maxFreq may be STALE.

        • However, we do NOT care about these 2 issues. Since we want to find the max window, we can just use the previous valid max window size to scan the array.

        • If there is a larger valid window size (> l), there must be a larger maxFreq because this equation l-maxFreq <= k. Only when maxFreq becomes larger, l can be larger as well.

        • That's also why we do NOT need to consider decreasing maxFreq when moving start index and do NOT care if it is stale. We ONLY care and want to find a larger maxFreq

Last updated