Table of Contents
Introduction
Array Two Pointers: An Optimization Tool from O(n²) to O(n) covered four pointer patterns. This article begins applying those patterns to real problems.
The nine problems come from LeetCode’s “Array and Two Pointers” introductory plan, split into two articles by theme:
| Article | Content | Core Technique |
|---|---|---|
| This article (Part 1) | Remove Duplicates from Sorted Array, Valid Palindrome, Sort Colors, Merge Sorted Array, Squares of a Sorted Array | Pointer movement on one-dimensional arrays |
| (Part 2) Matrices and Strings | Sort Matrix by Diagonals, Shift 2D Grid, Transpose Matrix, Longest Common Prefix | Index mapping and vertical scanning |
These five problems share a common trait: the answer’s determination depends only on the relationship between the two ends or adjacent positions. Whether it’s “duplicates must be adjacent” (Problem 1), “the first and last must be symmetric” (Problem 2), “only three values allow in-place partitioning” (Problem 3), “two sequences are each sorted” (Problem 4), or “the maximum squared value must be at one of the two ends” (Problem 5), each pointer movement can safely eliminate a batch of candidates, compressing what would otherwise be O(n²) enumeration down to O(n).
Quick Pointer Reference for the Five Problems
| Problem | Pointer Pattern | Pointer Meaning | In One Sentence |
|---|---|---|---|
| Remove Duplicates from Sorted Array | Fast-slow pointers (same direction) | slow end of written range, fast scans | Write new values after slow |
| Valid Palindrome | Left-right pointers (converging) | i left end, j right end | Skip non-alphanumeric then compare |
| Sort Colors | Three-pointer partitioning | p0 collects 0s, p2 collects 2s, i scans | After swapping to p2, i does not advance |
| Merge Sorted Array | Reverse two pointers | p1, p2 each at their end | Fill from back to front, avoiding overwrites |
| Squares of a Sorted Array | Left-right pointers + reverse filling | i left end, j right end | The maximum must be at one of the two ends |
About the Code Each problem provides implementations in C / C++ / JavaScript. The code is fully equivalent; switch tabs to view. The C version omits the return parameter management from LeetCode’s official signature that isn’t used here (such as
returnColumnSizes), in order to focus on the pointer logic.
1. Remove Duplicates from Sorted Array (LeetCode 26)
Problem Description
Given an array nums arranged in non-strictly increasing order, in-place remove the duplicate elements so that each element appears only once, and return the new length of the array after removal. The relative order of the elements should be kept the same.
Since the array’s length cannot be changed in some languages, the result must be placed in the first part of the array nums. More formally, if there are k elements after removing duplicates, then the first k elements of nums should hold the final result. Return k; whatever is placed beyond position k doesn’t matter.
Example:
Input: nums = [1,1,2]Output: 2, nums = [1,2,_]
Input: nums = [0,0,1,1,1,2,2,3,3,4]Output: 5, nums = [0,1,2,3,4,_,_,_,_,_]Constraints: 1 <= nums.length <= 3 * 10^4, -100 <= nums[i] <= 100, nums is sorted in non-decreasing order.
Approach: Fast-Slow Pointers Maintaining a “Deduplicated Prefix”
A sorted array means equal elements must be adjacent. So we can divide the array into two segments:
[0, slow]: the deduplicated result prefix, wherenums[slow]is the last element in it;[fast, n-1]: the part not yet checked.
fast starts at 1 and scans rightward:
- If
nums[fast] != nums[slow], it means a new value has been encountered (since duplicates are necessarily adjacent), so incrementslowand write the new value tonums[slow]; - Otherwise, this value is the same as the last value already written, so skip it.
When the loop ends, slow is the index of the last element of the result, and the length is slow + 1.
Process Illustration
Taking nums = [0,0,1,1,1,2,2,3,3,4] as an example:
Index 0 1 2 3 4 5 6 7 8 9nums 0 0 1 1 1 2 2 3 3 4 ↑ ↑ slow fast
fast=1: nums[1]=0 == nums[0]=0 → duplicate, skipfast=2: nums[2]=1 != nums[0]=0 → slow=1, nums[1]=1fast=3: nums[3]=1 == nums[1]=1 → skipfast=4: nums[4]=1 == nums[1]=1 → skipfast=5: nums[5]=2 != nums[1]=1 → slow=2, nums[2]=2fast=6: nums[6]=2 == nums[2]=2 → skipfast=7: nums[7]=3 != nums[2]=2 → slow=3, nums[3]=3fast=8: nums[8]=3 == nums[3]=3 → skipfast=9: nums[9]=4 != nums[3]=3 → slow=4, nums[4]=4
Return slow + 1 = 5 → the first five positions of nums are [0,1,2,3,4] ✔Code Implementation
int removeDuplicates(int *nums, int numsSize) { if (numsSize == 0) return 0;
int slow = 0; /* [0, slow] is the deduplicated prefix */ for (int fast = 1; fast < numsSize; fast++) { if (nums[fast] != nums[slow]) { /* new value found */ slow++; nums[slow] = nums[fast]; } } return slow + 1; /* length = last index + 1 */}#include <vector>using namespace std;
class Solution {public: int removeDuplicates(vector<int>& nums) { if (nums.empty()) return 0;
int slow = 0; // [0, slow] is the deduplicated prefix for (int fast = 1; fast < (int)nums.size(); ++fast) { if (nums[fast] != nums[slow]) { // new value found ++slow; nums[slow] = nums[fast]; } } return slow + 1; }};function removeDuplicates(nums) { if (nums.length === 0) return 0;
let slow = 0; // [0, slow] is the deduplicated prefix for (let fast = 1; fast < nums.length; fast++) { if (nums[fast] !== nums[slow]) { // new value found slow++; nums[slow] = nums[fast]; } } return slow + 1;}Complexity
| Dimension | Analysis |
|---|---|
| Time Complexity | O(n) — fast traverses once |
| Space Complexity | O(1) — only two index variables used |
| In-place | Yes — only reads and writes within nums |
Common Pitfalls
- Return
slow + 1, notslow.slowis an index; the length needs a plus one. An empty array must return 0 separately, otherwiseslow + 1would return 1. - Compare against
nums[slow], notnums[fast - 1]. Both give the same result for this problem, but usingslownaturally generalizes to “each element may be kept at most k times.” - Don’t also move
slowwhen a duplicate is found.slowonly advances when a new value is written.
Extension: Keep Each Element at Most k Times (LeetCode 80)
Change “compare with the previous written value” to “compare with the previous k written values,” and the template generalizes directly:
// Deduplicate a sorted array, keeping at most k copies of each element (k = 1 is this problem, k = 2 is LeetCode 80)int removeDuplicatesAtMostK(vector<int>& nums, int k) { int slow = 0; for (int fast = 0; fast < (int)nums.size(); ++fast) { // When slow < k there are fewer than k elements, so write directly; // Otherwise, only when nums[fast] differs from "the value written k positions ago" does the (k+1)-th duplicate appear if (slow < k || nums[fast] != nums[slow - k]) nums[slow++] = nums[fast]; } return slow;}2. Valid Palindrome (LeetCode 125)
Problem Description
A phrase is considered a palindrome if, after converting all uppercase characters to lowercase and removing all non-alphanumeric characters, it reads the same forward and backward. Letters and digits are both alphanumeric characters.
Given a string s, return true if it is a palindrome; otherwise, return false.
Example:
Input: s = "A man, a plan, a canal: Panama"Output: trueExplanation: "amanaplanacanalpanama" is a palindrome
Input: s = "race a car"Output: falseExplanation: "raceacar" is not a palindrome
Input: s = " "Output: trueExplanation: After removing non-alphanumeric characters, an empty string "" is obtained, and an empty string reads the same forwards and backwardsConstraints: 1 <= s.length <= 2 * 10^5, s consists only of printable ASCII characters.
Approach: Two Pointers Skipping “Invalid Characters”
If in-place operation is not allowed, the most intuitive approach is to first filter the string into pure alphanumeric characters, then reverse and compare—but that requires O(n) extra space.
The definition of a palindrome only cares about whether the two ends are symmetric, so using two pointers converging from both ends toward the middle is sufficient: i moves from left to right, j moves from right to left, each skipping non-alphanumeric characters, then comparing the two positions (converting to lowercase uniformly).
Only one pass is needed, and no new string needs to be constructed.
Process Illustration
s = "A man, a plan, a canal: Panama" i j
i='A', j='a' → both lowercase to 'a', equal → i++, j--
When i advances, skip ' ', ',' → 'm'; when j moves back, skip 'l', 'a', etc. …… compare pair by pair……
Finally i >= j, all symmetric → true ✔
Counterexample "race a car":i='r', j='r' equal → i='a', j='a' equal → i='c', j='c' equal→ i='e' skip space, j='a' → 'e' != 'a' → false ✘Code Implementation
#include <ctype.h>#include <string.h>
int isPalindrome(char *s) { int i = 0; int j = (int)strlen(s) - 1;
while (i < j) { /* Skip non-alphanumeric characters; note the inner loop must also check i < j */ while (i < j && !isalnum((unsigned char)s[i])) i++; while (i < j && !isalnum((unsigned char)s[j])) j--;
if (tolower((unsigned char)s[i]) != tolower((unsigned char)s[j])) return 0; /* false */ i++; j--; } return 1; /* true */}#include <cctype>#include <string>using namespace std;
class Solution {public: bool isPalindrome(string s) { int i = 0; int j = (int)s.size() - 1;
while (i < j) { // Skip non-alphanumeric characters; note the inner loop must also check i < j while (i < j && !isalnum((unsigned char)s[i])) ++i; while (i < j && !isalnum((unsigned char)s[j])) --j;
if (tolower((unsigned char)s[i]) != tolower((unsigned char)s[j])) return false; ++i; --j; } return true; }};function isPalindrome(s) { const isAlnum = (ch) => /[0-9a-zA-Z]/.test(ch);
let i = 0; let j = s.length - 1;
while (i < j) { // Skip non-alphanumeric characters; note the inner loop must also check i < j while (i < j && !isAlnum(s[i])) i++; while (i < j && !isAlnum(s[j])) j--;
if (s[i].toLowerCase() !== s[j].toLowerCase()) return false; i++; j--; } return true;}Complexity
| Dimension | Analysis |
|---|---|
| Time Complexity | O(n) — i only increases, j only decreases, totaling n moves |
| Space Complexity | O(1) — no new filtered string is constructed |
Common Pitfalls
- The inner
whilemust includei < j. Otherwise, when the entire string consists of non-alphanumeric characters (such as" ","...."),iwill keep advancing pastj, andjwill retreat past-1, with both sides reading and writing outside the array bounds—in C this is undefined behavior, and running with AddressSanitizer will directly reportstack-buffer-overflow. - C / C++
isalnum,tolowerparameters must be cast tounsigned char. These two functions acceptintand require values that areunsigned charorEOF; when passingchardirectly, if the character byte is negative (which is the case for subsequent bytes of UTF-8 multi-byte characters), it is undefined behavior. "0P"isfalse,"0P0"istrue. Digits must also participate in the comparison; don’t only check for letters.- Both
" "and""returntrue. The loop conditioni < jensures that an empty string or a single character is directly judged as successful.
3. Sort Colors (LeetCode 75)
Problem Description
Given an array nums of n elements containing red, white, and blue, sort them in-place so that elements of the same color are adjacent, arranged in the order red, white, and blue. We use the integers 0, 1, and 2 to represent red, white, and blue respectively.
You must solve this problem without using the library’s built-in sort function.
Example:
Input: nums = [2,0,2,1,1,0]Output: [0,0,1,1,2,2]
Input: nums = [2,0,1]Output: [0,1,2]Constraints: n == nums.length, 1 <= n <= 300, nums[i] is 0, 1, or 2.
Follow-up: Could you come up with a one-pass algorithm using only constant extra space?
Approach: Dutch National Flag Problem, Three Pointers
There are only three possible values, so no real sorting is needed—just partition the elements into three regions. Use three pointers to maintain three zones:
[0, p0) all 0s p0: the position where the next 0 should go[p0, i) all 1s i : the position currently being checked[i, p2] not yet processed p2: the position where the next 2 should go(p2, n) all 2sAt each step, look at nums[i] (the leftmost element of the “unprocessed region”):
nums[i] == 0: It belongs on the left, so swap it withnums[p0]. After the swap, positionp0becomes 0, andp0moves right by one; the element swapped into positioniis the originalnums[p0]—since the[p0, i)range is all 1s, the swapped-in element can only be 0 or 1, its placement is already determined, soican also move right.nums[i] == 2: It belongs on the right, so swap it withnums[p2]. The element swapped into positioniis taken from the tail of the unprocessed region, which hasn’t been examined yet, sop2moves left by one butistays put, and the next iteration re-examines this position.nums[i] == 1: It already belongs in the middle, soisimply moves right.
When i > p2, the unprocessed region is empty and sorting is complete.
Walkthrough Diagram
Using nums = [2,0,2,1,1,0] as an example:
Initial: p0=0 i=0 p2=5nums [2, 0, 2, 1, 1, 0]
i=0: nums[0]=2 → swap with nums[5]=0 → p2=4nums [0, 0, 2, 1, 1, 2]i=0: nums[0]=0 → swap with nums[0] (in place) → p0=1, i=1nums [0, 0, 2, 1, 1, 2]i=1: nums[1]=0 → swap with nums[1] (in place) → p0=2, i=2nums [0, 0, 2, 1, 1, 2]i=2: nums[2]=2 → swap with nums[4]=1 → p2=3 (i does not move!)nums [0, 0, 1, 1, 2, 2]i=2: nums[2]=1 → i=3i=3: nums[3]=1 → i=4i=4 > p2=3 → end ✔Code Implementation
static void swapInt(int *a, int *b) { int t = *a; *a = *b; *b = t;}
void sortColors(int *nums, int numsSize) { int p0 = 0; /* position for the next 0 */ int i = 0; /* current scan position */ int p2 = numsSize - 1; /* position for the next 2 */
while (i <= p2) { if (nums[i] == 0) { swapInt(&nums[i], &nums[p0]); p0++; i++; /* what was swapped in must be 0 or 1, so we can skip */ } else if (nums[i] == 2) { swapInt(&nums[i], &nums[p2]); p2--; /* what was swapped in is an unprocessed element, i cannot advance */ } else { i++; /* 1 stays in place */ } }}#include <vector>using namespace std;
class Solution {public: void sortColors(vector<int>& nums) { int p0 = 0; // position for the next 0 int i = 0; // current scan position int p2 = (int)nums.size() - 1; // position for the next 2
while (i <= p2) { if (nums[i] == 0) { swap(nums[i], nums[p0]); ++p0; ++i; // what was swapped in must be 0 or 1 } else if (nums[i] == 2) { swap(nums[i], nums[p2]); --p2; // what was swapped in is an unprocessed element, i cannot advance } else { ++i; // 1 stays in place } } }};function sortColors(nums) { let p0 = 0; // position for the next 0 let i = 0; // current scan position let p2 = nums.length - 1; // position for the next 2
while (i <= p2) { if (nums[i] === 0) { [nums[i], nums[p0]] = [nums[p0], nums[i]]; p0++; i++; // what was swapped in must be 0 or 1, so we can skip } else if (nums[i] === 2) { [nums[i], nums[p2]] = [nums[p2], nums[i]]; p2--; // what was swapped in is an unprocessed element, i cannot advance } else { i++; // 1 stays in place } }}Complexity
| Dimension | Analysis |
|---|---|
| Time Complexity | O(n) — Each element is swapped at most once; i and p2 together advance n steps |
| Space Complexity | O(1) — Only three index variables |
| Number of Passes | 1 pass — Meets the follow-up requirement |
Common Pitfalls
- After handling a 2,
imust absolutely not advance. The element swapped in from the tail hasn’t been examined yet—it could be a 0. This is the easiest place to make a mistake in this problem. - The loop condition must be
i <= p2, noti < p2. Wheni == p2, that position still holds an unprocessed element and must still be checked. Writingi < p2will skip a middle element: for example,nums = [1,0,2,0,1], the correct result is[0,0,1,1,2], buti < p2will output[0,1,1,0,2]. - Don’t take the shortcut of “two-pass counting sort.” Counting the number of 0s/1s/2s and then filling them back in is also O(n) time and O(1) space, but it requires two passes and doesn’t demonstrate the pointer-partitioning idea.
4. Merge Two Sorted Arrays (LeetCode 88)
Problem Description
You are given two integer arrays nums1 and nums2 arranged in non-decreasing order, along with two integers m and n, which represent the number of elements in nums1 and nums2 respectively.
Please merge nums2 into nums1 so that the merged array is also arranged in non-decreasing order.
Note: The final merged array should not be returned by the function, but stored in array nums1. To accommodate this, the initial length of nums1 is m + n, where the first m elements represent the elements to be merged, and the last n elements are 0 (placeholders) that should be ignored.
Example:
Input: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3Output: [1,2,2,3,5,6]
Input: nums1 = [1], m = 1, nums2 = [], n = 0Output: [1]Constraints: nums1.length == m + n, nums2.length == n, 0 <= m, n <= 200.
Approach: Write from Back to Front, Not Front to Back
If you merge from front to back, the first nums2 element written into nums1[0] will overwrite nums1 elements that haven’t been processed yet, unless you first move all of nums1 elsewhere (which degrades to O(m+n) extra space).
But notice that the last n positions of nums1 are already empty! So reverse the direction: fill from the end of the array backward, each time taking the larger of nums1 and nums2’s remaining parts and placing it at the rightmost position. At this point:
- The write position
tailis always to the right ofp1andp2, writing into empty slots that have already been read, so it’s impossible to overwrite unprocessed elements; - When
nums2has been fully moved (p2 < 0), the loop ends — at this point the remaining elements ofnums1are already in their correct positions and don’t need to be touched.
Process Diagram
nums1 = [1,2,3,0,0,0] m=3 nums2 = [2,5,6] n=3 p1=2 tail=5 p2=2
Compare nums1[2]=3 with nums2[2]=6 → 6 is larger → nums1[5]=6, p2=1, tail=4 [1,2,3,0,0,6]Compare 3 with nums2[1]=5 → 5 is larger → nums1[4]=5, p2=0, tail=3 [1,2,3,0,5,6]Compare 3 with nums2[0]=2 → 3 is larger → nums1[3]=3, p1=1, tail=2 [1,2,3,3,5,6]Compare nums1[1]=2 with 2 → equal, take nums2 → nums1[2]=2, p2=-1, tail=1 [1,2,2,3,5,6]p2 < 0 → end, the first m+n positions of nums1 are the answer ✔Code Implementation
void merge(int *nums1, int m, int *nums2, int n) { int p1 = m - 1; /* last element to be merged in nums1 */ int p2 = n - 1; /* last element to be merged in nums2 */ int tail = m + n - 1; /* write position, from back to front */
while (p2 >= 0) { /* p1 may already be -1, so we must check p1 >= 0 first (using && short-circuit) */ if (p1 >= 0 && nums1[p1] > nums2[p2]) { nums1[tail--] = nums1[p1--]; } else { nums1[tail--] = nums2[p2--]; } }}#include <vector>using namespace std;
class Solution {public: void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) { int p1 = m - 1; // last element to be merged in nums1 int p2 = n - 1; // last element to be merged in nums2 int tail = m + n - 1; // write position, from back to front
while (p2 >= 0) { // p1 may already be -1, so we must check p1 >= 0 first (using && short-circuit) */ if (p1 >= 0 && nums1[p1] > nums2[p2]) { nums1[tail--] = nums1[p1--]; } else { nums1[tail--] = nums2[p2--]; } } }};function merge(nums1, m, nums2, n) { let p1 = m - 1; // last element to be merged in nums1 let p2 = n - 1; // last element to be merged in nums2 let tail = m + n - 1; // write position, from back to front
while (p2 >= 0) { // p1 may already be -1, so we must check p1 >= 0 first (using && short-circuit) */ if (p1 >= 0 && nums1[p1] > nums2[p2]) { nums1[tail--] = nums1[p1--]; } else { nums1[tail--] = nums2[p2--]; } }}Complexity
| Dimension | Analysis |
|---|---|
| Time Complexity | O(m + n) — each element is written at most once |
| Space Complexity | O(1) — no new array allocated, only three indices used |
| In-place | Yes |
Common Pitfalls
p1 >= 0must be written beforenums1[p1] > nums2[p2]. Rely on&&’s short-circuit evaluation to prevent out-of-bounds access tonums1[-1].- The loop condition can only be
p2 >= 0. Don’t write it asp1 >= 0 && p2 >= 0: whennums2is exhausted first, the remaining elements ofnums1are already in their correct positions and it’s indeed safe to stop; but whennums1is exhausted first, you must continue moving the remaining elements ofnums2over (at this pointnums1[p1]doesn’t exist, and thep1 >= 0branch automatically takeselse). - Boundary cases of
m = 0orn = 0. Whenn = 0, the loop doesn’t execute even once, andnums1is returned as-is; whenm = 0, all elements are taken fromnums2. - Don’t first copy
nums2to the tail ofnums1and then sort. That discards the premise that “both arrays are individually sorted,” degrading the time to O((m+n)log(m+n)).
5. Squares of a Sorted Array (LeetCode 977)
Problem Description
Given an integer array nums sorted in non-decreasing order, return a new array consisting of the squares of each number, also sorted in non-decreasing order.
Example:
Input: nums = [-4,-1,0,3,10]Output: [0,1,9,16,100]Explanation: After squaring, we get [16,1,0,9,100], and after sorting, it becomes [0,1,9,16,100]
Input: nums = [-7,-3,2,3,11]Output: [4,9,9,49,121]Constraints: 1 <= nums.length <= 10^4, -10^4 <= nums[i] <= 10^4, nums is sorted in non-decreasing order.
Approach: The Maximum Value Can Only Appear at Either End
When the array contains negative numbers, the order after squaring no longer matches the original order — -4 is to the left of -1, but after squaring 16 > 1. However, there is a key property:
The maximum value after squaring must come from either the leftmost end of the array (the negative number with the largest absolute value) or the rightmost end (the largest positive number); it cannot come from the middle.
Because nums is monotonically increasing, |nums[i]| is monotonically decreasing on the left half and monotonically increasing on the right half, so the maximum value of |nums[i]| must be at one of the two ends of the interval.
Therefore, we can generate the result from largest to smallest: use two pointers i and j pointing to the two ends respectively, compare nums[i]² with nums[j]² each time, fill the larger one into the end of the result array, then move the corresponding pointer inward. Filling from back to front this way, the final result is in ascending order.
Process Illustration
nums = [-4,-1,0,3,10] res = [ , , , , ] i j 16 vs 100 → 100 is larger → res[4]=100, j=3[-4,-1,0,3,10] res = [ , , , ,100] i j 16 vs 9 → 16 is larger → res[3]=16, i=1[-4,-1,0,3,10] res = [ , , ,16,100] i j 1 vs 9 → 9 is larger → res[2]=9, j=2[-4,-1,0,3,10] res = [ , ,9,16,100] i=j 0 vs 0 → i == j, take 0 → res[0]=0
res = [0,1,9,16,100] ✔Code Implementation
#include <stdlib.h>
int *sortedSquares(int *nums, int numsSize, int *returnSize) { int *res = (int *)malloc(sizeof(int) * numsSize); int i = 0; int j = numsSize - 1; int k = numsSize - 1; /* fill from the end of the result array backwards */
while (i <= j) { int left = nums[i] * nums[i]; int right = nums[j] * nums[j]; if (left > right) { res[k--] = left; i++; } else { res[k--] = right; j--; } }
*returnSize = numsSize; return res;}#include <cmath>#include <vector>using namespace std;
class Solution {public: vector<int> sortedSquares(vector<int>& nums) { int n = (int)nums.size(); vector<int> res(n); int i = 0, j = n - 1;
for (int k = n - 1; k >= 0; --k) { // For integers, |x| > |y| is equivalent to x² > y² if (abs(nums[i]) > abs(nums[j])) { res[k] = nums[i] * nums[i]; ++i; } else { res[k] = nums[j] * nums[j]; --j; } } return res; }};function sortedSquares(nums) { const n = nums.length; const res = new Array(n); let i = 0; let j = n - 1;
for (let k = n - 1; k >= 0; k--) { // For integers, |x| > |y| is equivalent to x² > y² if (Math.abs(nums[i]) > Math.abs(nums[j])) { res[k] = nums[i] * nums[i]; i++; } else { res[k] = nums[j] * nums[j]; j--; } } return res;}Complexity
| Dimension | Analysis |
|---|---|
| Time Complexity | O(n) — the two pointers together move n times |
| Space Complexity | O(n) — the new array returned (this is necessary; in-place output is not possible) |
Common Pitfalls
- The result array must be filled from back to front. We can only determine the maximum value first; filling from front to back would reverse the order.
i <= jinstead ofi < j. For odd lengths, the middlemost element must also be written in.- Watch out for squaring overflow. In this problem
|nums[i]| <= 10^4, the maximum square is 10^8, whichintcan hold; but if the problem were relaxed to10^5(square 10^10), C / C++ would have to switch tolong long. - Don’t fool yourself with the square-then-sort approach. That requires O(n log n), wasting the information that “the array is sorted.”
Summary
| Problem | Pointer Pattern | Movement Rule | Time | Space |
|---|---|---|---|---|
| Remove Duplicates from Sorted Array | Fast-slow pointers (same direction) | fast always advances; slow moves only when a new value is found | O(n) | O(1) |
| Valid Palindrome | Left-right pointers (converging) | Skip invalid characters first, then move inward in sync | O(n) | O(1) |
| Sort Colors | Three-pointer partitioning | On 0, both pointers advance together; on 2, only p2 moves | O(n) | O(1) |
| Merge Sorted Array | Reverse two pointers | Compare sizes, the larger one takes tail | O(m+n) | O(1) |
| Squares of a Sorted Array | Left-right pointers + reverse filling | Compare squares (absolute values), the larger one fills the end | O(n) | O(n) |
The three movement rhythms that appear across these five problems are worth remembering:
- Same-direction fast-slow: one pointer is responsible for “scanning,” one for “writing,” and the write pointer only advances when a condition is met (Problem 1).
- Converging squeeze: both ends close in toward the middle, and each step eliminates a batch of impossible states (Problems 2 and 5).
- Partition swapping: multiple boundary pointers cut the array into a confirmed region and an unprocessed region; after swapping, you must judge whether the newly arrived element’s belonging has been determined (Problem 3).
Each rhythm must be able to answer the same question: When I move the pointer this time, why won’t I miss the answer? If you can answer that, you won’t write the code wrong.
Related Articles
- Classic Array Two-Pointer Exercises (Part 2): Matrices and Strings — Next up, index mapping for 2D matrices and longest common prefix
- Sliding Window Core Code Template: Three Questions to Master All Substring and Subarray Problems — When both pointers only move right and the interval they squeeze out has a clear meaning, two pointers level up into a sliding window
- Array Two Pointers: The Optimization Tool from O(n²) to O(n) — The overall framework of the four pointer patterns
- Linked List Two Pointers: The Elegant Application of Fast-Slow Pointers in Linked Lists — The same pattern, transferred to linked lists
- Binary Search: From Beginner to Boundary Handling Mastery — Another means of turning sortedness into O(log n)