Two Pointers Techniques
Elements Filtering, Elements Replacements, Elements Reordering, Partitioning..
Explanation of In-Place Algorithms Using Two-Pointers Technique
The two-pointers technique is an efficient algorithmic approach utilized to manipulate arrays in-place, minimizing the need for additional memory. This method leverages two indices (pointers), typically starting from different positions in the array, to traverse and rearrange the elements based on certain conditions, without duplicating the array.
Examples
- Objective: To remove all instances of a given value from an array without using extra space.
- Mechanism: Two pointers (
left
andright
) are used. Theright
pointer traverses the entire array, while theleft
pointer keeps track of the position where the next non-target value should be placed. Ifright
points to a value not equal to the target, the value is moved to the position indicated byleft
, andleft
is incremented.
- Objective: To return the length of the array after removing all duplicates, modifying the original array to hold unique elements at the start.
- Mechanism: Similar to the first, but here both pointers start at the beginning of the array. The
left
pointer marks the end of the array of unique elements. As theright
pointer encounters a new unique element, it is placed immediately after the last unique element found (left
).
-
- Objective: To move all zeroes in an array to its end while maintaining the order of non-zero elements.
- Mechanism: This utilizes a similar approach where the
left
pointer tracks the position for the next non-zero element. Whenever a non-zero is identified by theright
pointer, it is swapped with the element atleft
index.
-
Array Partitioning (Quick Sort)
- Tutorial : Quicksort: Partitioning an array
- Objective: To partition an array around a pivot such that elements less than the pivot are on the left, and those greater are on the right.
- Mechanism: Two pointers,
i
andj
, are used wherei
starts just left of the section to be partitioned and moves right when a smaller-than-pivot element is found. Thej
pointer scans through the array, and when a less-than-pivot element is found, it is swapped with the position indicated byi
.
Generic Mechanism of the In-Place Two-Pointers Technique
Core Concept:
- Two Pointers: One pointer (
left
) is used to track the location where the next qualifying element should be placed or manipulated, while the other pointer (right
) iterates through the array to evaluate each element against a given condition.
Generic Function Design:
- Array Modification: A function
inPlaceTransform
accepts the array, a condition (shouldTransform
), and an action (performTransform
). The condition determines if an element needs rearranging, and the action defines how the rearrangement is executed.
/**
* Function to modify elements of an array in-place based on a provided condition.
* @param {Array} arr - The array to be modified.
* @param {Function} shouldTransform - A callback function that determines if the element should be transformed.
* @param {Function} performTransform - A callback function that defines how to transform the array elements.
* @return {number} The new length or state of the array after processing.
*/
export function inPlaceTransform(arr, shouldTransform, performTransform) {
let left = 0;
for (let right = 0; right < arr.length; right++) {
if (shouldTransform(arr[right])) {
performTransform(arr, left, right);
left++;
}
}
return left; // This might be the length or a different measurement based on the use case.
}
// Sample transformation function to swap elements
function swapElements(arr, i, j) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
// Example usage
let nums = [0, 1, 0, 3, 12];
console.log("Before:", nums);
inPlaceTransform(nums, (value) => value !== 0, swapElements);
console.log("After:", nums);
// After: [ 1, 3, 12, 0, 0 ]
Usage Scenario:
import { inPlaceTransform } from "./inPlaceTransform.js";
function swapElements(arr, i, j) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
// 1. Remove all occurrences of val from an array
function removeElement(nums, val) {
return inPlaceTransform(nums, (value) => value !== val, swapElements);
}
// 2. Remove duplicates from a sorted array
function removeDuplicates(nums) {
return inPlaceTransform(
nums,
(value, idx) => idx === 0 || nums[idx - 1] !== value,
swapElements
);
}
// 3. Move zeroes to the end of the array
function moveZeroes(nums) {
return inPlaceTransform(nums, (value) => value !== 0, swapElements);
}
// 4. Quick sort partition function
function partition(arr, left, right) {
const pivot = arr[right];
let pivotIndex =
inPlaceTransform(arr, (value) => value < pivot, swapElements) - 1;
swapElements(arr, pivotIndex + 1, right); // Move the pivot to its correct position
return pivotIndex + 1;
}
// Example usage of each function
let nums1 = [3, 2, 2, 3];
console.log("\nOriginal Array:", [...nums1]);
console.log("Elements after removing 3:", removeElement(nums1, 3), nums1);
let nums2 = [1, 1, 2];
console.log("\nOriginal Array:", [...nums2]);
console.log("Array after duplicates removed:", removeDuplicates(nums2), nums2);
let nums3 = [0, 1, 0, 3, 12];
console.log("\nOriginal Array:", [...nums3]);
console.log("Array after moving zeroes:", moveZeroes(nums3), nums3);
let nums4 = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
console.log("\nOriginal Array:", [...nums4]);
console.log("New pivot index:", partition(nums4, 0, nums4.length - 1), nums4);
Common Operations:
- Element Filtering: Removing unwanted elements (e.g., specific values or duplicates).
- Element Reordering: Moving elements to specific parts of the array (e.g., moving zeros to the end).
- Partitioning: Segregating elements around a pivot for sorting or quick sort algorithms.
Advantages:
- Space Efficiency: Operates directly on the input array, eliminating the need for additional space proportional to the input size.
- Adaptability: Easily adaptable for different operations by changing the condition and action callbacks.
- Performance: Offers efficient processing by minimizing memory usage and often reducing complexity compared to other methods that require additional data structures.
This technique is powerful for a range of problems where the array needs to be modified in place, providing a robust, reusable, and efficient tool in a developer's arsenal. The flexibility to define both the condition for rearrangement and the method of rearrangement ensures that the function can be tailored to fit a wide array of needs.