This commit is contained in:
Francis Yang 2024-07-17 10:43:30 +09:00 committed by GitHub
commit 40ca2700a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 1038 additions and 440 deletions

View File

@ -129,6 +129,7 @@ a set of rules that precisely define a sequence of operations.
* `B` [Jump Search](src/algorithms/search/jump-search) (or Block Search) - search in sorted array
* `B` [Binary Search](src/algorithms/search/binary-search) - search in sorted array
* `B` [Interpolation Search](src/algorithms/search/interpolation-search) - search in uniformly distributed sorted array
* `B` [Twin Pointers](src/algorithms/search/twin-pointers)
* **Sorting**
* `B` [Bubble Sort](src/algorithms/sorting/bubble-sort)
* `B` [Selection Sort](src/algorithms/sorting/selection-sort)

1320
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,7 @@
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jest": "27.2.1",
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-react": "^7.33.2",
"husky": "8.0.3",
"jest": "29.4.1",
"pngjs": "^7.0.0"

View File

@ -0,0 +1,25 @@
# Twin Pointers
The twin pointers method, also known as the two pointers method, is a searching algorithm
that can be used on both sorted and unsorted numerical arrays/lists, depending on the intent of the function.
At its simplest form the twin pointer method employes two "pointers" that either move at different
speeds/from different starting positions in order to draw comparisons between values in order to
find some specified target. In the case that the array/list being searched through is sorted,
a common usage of the twin pointers is to have one at the starting and one at the ending position;
in this manner, moving the left pointer to the right can be assumed to increase its value while moving
the right pointer to the left can be assumed to do vice versa. In the case of an unsorted arrays/list,
the usage methods are generally much more varied based on what the characteristics of the intended
target of the search are.
Note that any array can be sorted to easily use the twin pointer method by using the Array.sort method.
However, the Array.sort method inherently has a time complexity of O(n log n), which can be undesirable
in many cases when the desired time complexity of your solution is simply O(n).
## Complexity
**Time Complexity**: `O(n)` - since we only need to look over every element of our array a single time when comparing, time complexity is O(n).
## References
- [GeeksForGeeks](https://www.geeksforgeeks.org/two-pointers-technique/)
- [YouTube](https://youtu.be/VEPCm3BCtik?si=rH9O1My7Ym_83FrR)

View File

@ -0,0 +1,30 @@
import { twinPointerSorted, twinPointerUnsorted } from '../twinPointers';
describe('twinPointerSorted', () => {
it('should search for a specific combination sum', () => {
expect(twinPointerSorted([], 1)).toBe(-1);
expect(twinPointerSorted([0, 1, 2], 3)).toStrictEqual([1, 2]);
expect(twinPointerSorted([0, 1, 2], 1)).toStrictEqual([0, 1]);
expect(twinPointerSorted([1, 2, 5, 7, 9], 4)).toBe(-1);
expect(twinPointerSorted([1, 2, 5, 7, 9], 14)).toStrictEqual([2, 4]);
expect(twinPointerSorted([3, 5, 7, 9], 1)).toBe(-1);
expect(twinPointerSorted([4, 6, 10, 15, 16, 18, 20], 10)).toStrictEqual([0, 1]);
expect(twinPointerSorted([4, 6, 10, 15, 16, 18, 20], 38)).toStrictEqual([5, 6]);
expect(twinPointerSorted([0, 100, 300, 500, 700, 1000, 2000, 5000], 50)).toBe(-1);
expect(twinPointerSorted([0, 100, 300, 500, 700, 1000, 2000, 5000], 100)).toStrictEqual([0, 1]);
expect(twinPointerSorted([0, 100, 300, 500, 700, 1000, 2000, 5000], 1000)).toStrictEqual([0, 5]);
expect(twinPointerSorted([0, 100, 300, 500, 700, 1000, 2000, 5000], 5000)).toStrictEqual([0, 7]);
});
});
describe('twinPointerUnsorted', () => {
it('should search for the highest possible area', () => {
expect(twinPointerUnsorted([])).toBe(0);
expect(twinPointerUnsorted([2])).toBe(2);
expect(twinPointerUnsorted([0, 1, 2])).toBe(1);
expect(twinPointerUnsorted([1, 2, 5, 7, 9])).toBe(10);
expect(twinPointerUnsorted([3, 5, 7, 9])).toBe(10);
expect(twinPointerUnsorted([4, 6, 10, 15, 16, 18, 20])).toBe(45);
expect(twinPointerUnsorted([0, 100, 300, 500, 700, 1000, 2000, 5000])).toBe(2100);
});
});

View File

@ -0,0 +1,101 @@
import Comparator from '../../../utils/comparator/Comparator';
/**
* Some twin pointer implementations.
*
* @param {*[]} sortedArray
* @param {*} seekElement
* @param {function(a, b)} [comparatorCallback]
* @return {[number, number]}
*/
// Example of a twin pointer application in a sorted array where we are seeking the indices of two elements that sum to equal the target.
export function twinPointerSorted(sortedArray, seekElement, comparatorCallback) {
const comparator = new Comparator(comparatorCallback);
// These variables will be our pointers; since the array is sorted, we can set them to the left and rightmost elements.
let left = 0;
let right = sortedArray.length - 1
// If our left and right pointers have met then we have iterated through the entire array.
while (left < right) {
/**
* If our sum is less than the target then we can increase said sum but by increasing the left value;
* since the array is sorted, this will always result in array[left] becoming a larger number.
*/
if (comparator.lessThan(sortedArray[left] + sortedArray[right], seekElement)) {
left++;
// Same concept as before, only now we decrease our sum because it's greater than the target.
} else if (comparator.greaterThan(sortedArray[left] + sortedArray[right], seekElement)) {
right--;
// Assuming we have found our target, return left and right since they represent the indices that our correct sum is located at.
} else {
return [left, right]
}
}
// Return -1 if we haven't found any combination of numbers that works.
return -1;
}
/* An example of a twin pointer method on an unsorted array. In this problem, we aim to get the heighest possible area from two numbers by using the
small of the two heights, assuming that each number n is a rectangle of 1 width and n height. (Problem and solution taken from Leetcode #11)
*/
/**
*
* @param {*[]} unsortedArray
* @param {function(a, b)} [comparatorCallback]
* @return {number}
*/
export function twinPointerUnsorted(unsortedArray, comparatorCallback) {
const comparator = new Comparator(comparatorCallback);
// Edge cases; not relevant to the pointer method.
if (unsortedArray.length === 0) {
return 0
} else if (unsortedArray.length === 1) {
return unsortedArray[0]
}
// Again, we set our two pointers to the left and rightmost elements of the array.
let left = 0;
let right = unsortedArray.length - 1;
// We initialize two area variables; one for our current area between our two pointers and one for the highest that we'll return.
let area = 0;
let mostArea = 0;
// Functionally equivalent to the while conditional we set in the first example.
while (left !== right) {
// In this situation, since we don't have a specific "target" in mind we instead compare the two values at our two pointers to each other.
if (comparator.lessThan(unsortedArray[left], unsortedArray[right])) {
// Here we simply calculate our current area and whether we need to change our highest area by comparing it with the current.
area = (Math.min(unsortedArray[left], unsortedArray[right]) * (right - left));
mostArea = Math.max(area, mostArea);
/**
* Again, we move the left pointer forward or the right pointer backwards. You may be thinking that this is basically the same as with the
* sorted array; while that is correct from a pure code standpoint, conceptually the reasoning is different. In the first example, because the array
* is sorted we can move the left pointer forward with the knowledge that this will DEFINITELY either keep the value the same or increase it.
* In this situation however, our array isn't sorted and thus moving the left pointer forward isn't guaranteed to increase the value; all we know
* is that it will change. However, because we are calculating area with the smallest height and our value (heght) at the left pointer is smaller than the right pointer,
* we know that the ONLY way to get a higher area is if there is a potentially higher value for the left pointer.
*/
left++;
} else {
area = (Math.min(unsortedArray[left], unsortedArray[right]) * (right - left));
mostArea = Math.max(area, mostArea);
right--;
}
}
// Our greatest area should be correct since we re-state if the current area is greater.
return mostArea
}