mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-10 11:09:43 +08:00
Merge 01c924567a
into ca3d16dcce
This commit is contained in:
commit
40ca2700a9
@ -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
1320
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
|
25
src/algorithms/search/twin-pointers/README.md
Normal file
25
src/algorithms/search/twin-pointers/README.md
Normal 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)
|
@ -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);
|
||||
});
|
||||
});
|
101
src/algorithms/search/twin-pointers/twinPointers.js
Normal file
101
src/algorithms/search/twin-pointers/twinPointers.js
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user