mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-25 22:46:20 +08:00
Add divide and conquer example: best time to buy and sell stocks.
This commit is contained in:
parent
e71dc8dc16
commit
4973392cb9
14
README.md
14
README.md
@ -129,9 +129,9 @@ a set of rules that precisely define a sequence of operations.
|
||||
* `B` [Depth-First Search](src/algorithms/graph/depth-first-search) (DFS)
|
||||
* `B` [Breadth-First Search](src/algorithms/graph/breadth-first-search) (BFS)
|
||||
* `B` [Kruskal’s Algorithm](src/algorithms/graph/kruskal) - finding Minimum Spanning Tree (MST) for weighted undirected graph
|
||||
* `A` [Dijkstra Algorithm](src/algorithms/graph/dijkstra) - finding shortest paths to all graph vertices from single vertex
|
||||
* `A` [Bellman-Ford Algorithm](src/algorithms/graph/bellman-ford) - finding shortest paths to all graph vertices from single vertex
|
||||
* `A` [Floyd-Warshall Algorithm](src/algorithms/graph/floyd-warshall) - find shortest paths between all pairs of vertices
|
||||
* `A` [Dijkstra Algorithm](src/algorithms/graph/dijkstra) - finding the shortest paths to all graph vertices from single vertex
|
||||
* `A` [Bellman-Ford Algorithm](src/algorithms/graph/bellman-ford) - finding the shortest paths to all graph vertices from single vertex
|
||||
* `A` [Floyd-Warshall Algorithm](src/algorithms/graph/floyd-warshall) - find the shortest paths between all pairs of vertices
|
||||
* `A` [Detect Cycle](src/algorithms/graph/detect-cycle) - for both directed and undirected graphs (DFS and Disjoint Set based versions)
|
||||
* `A` [Prim’s Algorithm](src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) for weighted undirected graph
|
||||
* `A` [Topological Sorting](src/algorithms/graph/topological-sorting) - DFS method
|
||||
@ -157,6 +157,7 @@ a set of rules that precisely define a sequence of operations.
|
||||
* `B` [Unique Paths](src/algorithms/uncategorized/unique-paths) - backtracking, dynamic programming and Pascal's Triangle based examples
|
||||
* `B` [Rain Terraces](src/algorithms/uncategorized/rain-terraces) - trapping rain water problem (dynamic programming and brute force versions)
|
||||
* `B` [Recursive Staircase](src/algorithms/uncategorized/recursive-staircase) - count the number of ways to reach to the top (4 solutions)
|
||||
* `B` [Best Time To Buy Sell Stocks](src/algorithms/uncategorized/best-time-to-buy-sell-stocks) - divide and conquer and one-pass examples
|
||||
* `A` [N-Queens Problem](src/algorithms/uncategorized/n-queens)
|
||||
* `A` [Knight's Tour](src/algorithms/uncategorized/knight-tour)
|
||||
|
||||
@ -176,7 +177,7 @@ algorithm is an abstraction higher than a computer program.
|
||||
* **Greedy** - choose the best option at the current time, without any consideration for the future
|
||||
* `B` [Jump Game](src/algorithms/uncategorized/jump-game)
|
||||
* `A` [Unbound Knapsack Problem](src/algorithms/sets/knapsack-problem)
|
||||
* `A` [Dijkstra Algorithm](src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices
|
||||
* `A` [Dijkstra Algorithm](src/algorithms/graph/dijkstra) - finding the shortest path to all graph vertices
|
||||
* `A` [Prim’s Algorithm](src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) for weighted undirected graph
|
||||
* `A` [Kruskal’s Algorithm](src/algorithms/graph/kruskal) - finding Minimum Spanning Tree (MST) for weighted undirected graph
|
||||
* **Divide and Conquer** - divide the problem into smaller parts and then solve those parts
|
||||
@ -191,6 +192,7 @@ algorithm is an abstraction higher than a computer program.
|
||||
* `B` [Matrices](src/algorithms/math/matrix) - generating and traversing the matrices of different shapes
|
||||
* `B` [Jump Game](src/algorithms/uncategorized/jump-game)
|
||||
* `B` [Fast Powering](src/algorithms/math/fast-powering)
|
||||
* `B` [Best Time To Buy Sell Stocks](src/algorithms/uncategorized/best-time-to-buy-sell-stocks) - divide and conquer and one-pass examples
|
||||
* `A` [Permutations](src/algorithms/sets/permutations) (with and without repetitions)
|
||||
* `A` [Combinations](src/algorithms/sets/combinations) (with and without repetitions)
|
||||
* **Dynamic Programming** - build up a solution using previously found sub-solutions
|
||||
@ -207,8 +209,8 @@ algorithm is an abstraction higher than a computer program.
|
||||
* `A` [0/1 Knapsack Problem](src/algorithms/sets/knapsack-problem)
|
||||
* `A` [Integer Partition](src/algorithms/math/integer-partition)
|
||||
* `A` [Maximum Subarray](src/algorithms/sets/maximum-subarray)
|
||||
* `A` [Bellman-Ford Algorithm](src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices
|
||||
* `A` [Floyd-Warshall Algorithm](src/algorithms/graph/floyd-warshall) - find shortest paths between all pairs of vertices
|
||||
* `A` [Bellman-Ford Algorithm](src/algorithms/graph/bellman-ford) - finding the shortest path to all graph vertices
|
||||
* `A` [Floyd-Warshall Algorithm](src/algorithms/graph/floyd-warshall) - find the shortest paths between all pairs of vertices
|
||||
* `A` [Regular Expression Matching](src/algorithms/string/regular-expression-matching)
|
||||
* **Backtracking** - similarly to brute force, try to generate all possible solutions, but each time you generate next solution you test
|
||||
if it satisfies all conditions, and only then continue generating subsequent solutions. Otherwise, backtrack, and go on a
|
||||
|
@ -0,0 +1,107 @@
|
||||
# Best Time to Buy and Sell Stock
|
||||
|
||||
## Task Description
|
||||
|
||||
Say you have an array prices for which the `i`-th element is the price of a given stock on day `i`.
|
||||
|
||||
Find the maximum profit. You may complete as many transactions as you like (i.e., buy one and sell one share of the stock multiple times).
|
||||
|
||||
> Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).
|
||||
|
||||
**Example #1**
|
||||
|
||||
```
|
||||
Input: [7, 1, 5, 3, 6, 4]
|
||||
Output: 7
|
||||
```
|
||||
|
||||
_Explanation:_ Buy on day `2` (`price = 1`) and sell on day `3` (`price = 5`), `profit = 5-1 = 4`. Then buy on day `4` (`price = 3`) and sell on day `5` (`price = 6`), `profit = 6-3 = 3`.
|
||||
|
||||
**Example #2**
|
||||
|
||||
```
|
||||
Input: [1, 2, 3, 4, 5]
|
||||
Output: 4
|
||||
```
|
||||
|
||||
_Explanation:_ Buy on day `1` (`price = 1`) and sell on day `5` (`price = 5`), `profit = 5-1 = 4`. Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are engaging multiple transactions at the same time. You must sell before buying again.
|
||||
|
||||
**Example #3**
|
||||
|
||||
```
|
||||
Input: [7, 6, 4, 3, 1]
|
||||
Output: 0
|
||||
```
|
||||
|
||||
_Explanation:_ In this case, no transaction is done, i.e. max `profit = 0`.
|
||||
|
||||
## Possible Solutions
|
||||
|
||||
### Divide and conquer approach
|
||||
|
||||
We may try **all** combinations of buying and selling and find out the most profitable one by applying _divide and conquer approach_.
|
||||
|
||||
Let's say we have an array of prices `[7, 6, 4, 3, 1]` and we're on the _1st_ day of trading (at the very beginning of the array). At this point we may say that the overall maximum profit would be the _maximum_ of two following values:
|
||||
|
||||
1. _Option 1: Keep the money_ → profit would equal to the profit from buying/selling the rest of the stocks → `keepProfit = profit([6, 4, 3, 1])`.
|
||||
2. _Option 2: Buy/sell at current price_ → profit in this case would equal to the profit from buying/selling the rest of the stocks plus (or minus, depending on whether we're selling or buying) the current stock price → `buySellProfit = -7 + profit([6, 4, 3, 1])`.
|
||||
|
||||
The overall profit would be equal to → `overalProfit = Max(keepProfit, buySellProfit)`.
|
||||
|
||||
As you can see the `profit([6, 4, 3, 1])` task is being solved in the same recursive manner.
|
||||
|
||||
> See the full code example in [dqBestTimeToBuySellStocks.js](dqBestTimeToBuySellStocks.js)
|
||||
|
||||
#### Time Complexity
|
||||
|
||||
As you may see, every recursive call will produce _2_ more recursive branches. The depth of the recursion will be `n` (size of prices array) and thus, the time complexity will equal to `O(2^n)`.
|
||||
|
||||
As you may see, this is very inefficient. For example for just `20` prices the number of recursive calls will be somewhere close to `2M`!
|
||||
|
||||
#### Additional Space Complexity
|
||||
|
||||
If we avoid cloning the prices array between recursive function calls and will use the array pointer then additional space complexity will be proportional to the depth of the recursion: `O(n)`
|
||||
|
||||
## Peak Valley Approach
|
||||
|
||||
If we plot the prices array (i.e. `[7, 1, 5, 3, 6, 4]`) we may notice that the points of interest are the consecutive valleys and peaks
|
||||
|
||||
![Peak Valley Approach](https://leetcode.com/media/original_images/122_maxprofit_1.PNG)
|
||||
|
||||
_Image source: [LeetCode](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/solution/)_
|
||||
|
||||
So, if we will track the growing price and will sell the stocks immediately _before_ the price goes down we'll get the maximum profit (remember, we bought the stock in the valley at its low price).
|
||||
|
||||
> See the full code example in [peakvalleyBestTimeToBuySellStocks.js](peakvalleyBestTimeToBuySellStocks.js)
|
||||
|
||||
#### Time Complexity
|
||||
|
||||
Since the algorithm requires only one pass through the prices array, the time complexity would equal `O(n)`.
|
||||
|
||||
#### Additional Space Complexity
|
||||
|
||||
Except of the prices array itself the algorithm consumes the constant amount of memory. Thus, additional space complexity is `O(1)`.
|
||||
|
||||
## Accumulator Approach
|
||||
|
||||
There is even simpler approach exists. Let's say we have the prices array which looks like this `[1, 7, 2, 3, 6, 7, 6, 7]`:
|
||||
|
||||
![Simple One Pass](https://leetcode.com/media/original_images/122_maxprofit_2.PNG)
|
||||
|
||||
_Image source: [LeetCode](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/solution/)_
|
||||
|
||||
You may notice, that we don't even need to keep tracking of a constantly growing price. Instead, we may simply add the price difference for _all growing segments_ of the chart which eventually sums up to the highest possible profit,
|
||||
|
||||
> See the full code example in [accumulatorBestTimeToBuySellStocks.js](accumulatorBestTimeToBuySellStocks.js)
|
||||
|
||||
#### Time Complexity
|
||||
|
||||
Since the algorithm requires only one pass through the prices array, the time complexity would equal `O(n)`.
|
||||
|
||||
#### Additional Space Complexity
|
||||
|
||||
Except of the prices array itself the algorithm consumes the constant amount of memory. Thus, additional space complexity is `O(1)`.
|
||||
|
||||
## References
|
||||
|
||||
- [Best Time to Buy and Sell Stock on LeetCode](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/)
|
@ -0,0 +1,48 @@
|
||||
import accumulatorBestTimeToBuySellStocks from '../accumulatorBestTimeToBuySellStocks';
|
||||
|
||||
describe('accumulatorBestTimeToBuySellStocks', () => {
|
||||
it('should find the best time to buy and sell stocks', () => {
|
||||
let visit;
|
||||
|
||||
expect(accumulatorBestTimeToBuySellStocks([1, 5])).toEqual(4);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(accumulatorBestTimeToBuySellStocks([1], visit)).toEqual(0);
|
||||
expect(visit).toHaveBeenCalledTimes(1);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(accumulatorBestTimeToBuySellStocks([1, 5], visit)).toEqual(4);
|
||||
expect(visit).toHaveBeenCalledTimes(2);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(accumulatorBestTimeToBuySellStocks([5, 1], visit)).toEqual(0);
|
||||
expect(visit).toHaveBeenCalledTimes(2);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(accumulatorBestTimeToBuySellStocks([1, 5, 10], visit)).toEqual(9);
|
||||
expect(visit).toHaveBeenCalledTimes(3);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(accumulatorBestTimeToBuySellStocks([10, 1, 5, 20, 15, 21], visit)).toEqual(25);
|
||||
expect(visit).toHaveBeenCalledTimes(6);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(accumulatorBestTimeToBuySellStocks([7, 1, 5, 3, 6, 4], visit)).toEqual(7);
|
||||
expect(visit).toHaveBeenCalledTimes(6);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(accumulatorBestTimeToBuySellStocks([1, 2, 3, 4, 5], visit)).toEqual(4);
|
||||
expect(visit).toHaveBeenCalledTimes(5);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(accumulatorBestTimeToBuySellStocks([7, 6, 4, 3, 1], visit)).toEqual(0);
|
||||
expect(visit).toHaveBeenCalledTimes(5);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(accumulatorBestTimeToBuySellStocks(
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
|
||||
visit,
|
||||
)).toEqual(19);
|
||||
expect(visit).toHaveBeenCalledTimes(20);
|
||||
});
|
||||
});
|
@ -0,0 +1,48 @@
|
||||
import dqBestTimeToBuySellStocks from '../dqBestTimeToBuySellStocks';
|
||||
|
||||
describe('dqBestTimeToBuySellStocks', () => {
|
||||
it('should find the best time to buy and sell stocks', () => {
|
||||
let visit;
|
||||
|
||||
expect(dqBestTimeToBuySellStocks([1, 5])).toEqual(4);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(dqBestTimeToBuySellStocks([1], visit)).toEqual(0);
|
||||
expect(visit).toHaveBeenCalledTimes(3);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(dqBestTimeToBuySellStocks([1, 5], visit)).toEqual(4);
|
||||
expect(visit).toHaveBeenCalledTimes(7);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(dqBestTimeToBuySellStocks([5, 1], visit)).toEqual(0);
|
||||
expect(visit).toHaveBeenCalledTimes(7);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(dqBestTimeToBuySellStocks([1, 5, 10], visit)).toEqual(9);
|
||||
expect(visit).toHaveBeenCalledTimes(15);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(dqBestTimeToBuySellStocks([10, 1, 5, 20, 15, 21], visit)).toEqual(25);
|
||||
expect(visit).toHaveBeenCalledTimes(127);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(dqBestTimeToBuySellStocks([7, 1, 5, 3, 6, 4], visit)).toEqual(7);
|
||||
expect(visit).toHaveBeenCalledTimes(127);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(dqBestTimeToBuySellStocks([1, 2, 3, 4, 5], visit)).toEqual(4);
|
||||
expect(visit).toHaveBeenCalledTimes(63);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(dqBestTimeToBuySellStocks([7, 6, 4, 3, 1], visit)).toEqual(0);
|
||||
expect(visit).toHaveBeenCalledTimes(63);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(dqBestTimeToBuySellStocks(
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
|
||||
visit,
|
||||
)).toEqual(19);
|
||||
expect(visit).toHaveBeenCalledTimes(2097151);
|
||||
});
|
||||
});
|
@ -0,0 +1,48 @@
|
||||
import peakvalleyBestTimeToBuySellStocks from '../peakvalleyBestTimeToBuySellStocks';
|
||||
|
||||
describe('peakvalleyBestTimeToBuySellStocks', () => {
|
||||
it('should find the best time to buy and sell stocks', () => {
|
||||
let visit;
|
||||
|
||||
expect(peakvalleyBestTimeToBuySellStocks([1, 5])).toEqual(4);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(peakvalleyBestTimeToBuySellStocks([1], visit)).toEqual(0);
|
||||
expect(visit).toHaveBeenCalledTimes(1);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(peakvalleyBestTimeToBuySellStocks([1, 5], visit)).toEqual(4);
|
||||
expect(visit).toHaveBeenCalledTimes(2);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(peakvalleyBestTimeToBuySellStocks([5, 1], visit)).toEqual(0);
|
||||
expect(visit).toHaveBeenCalledTimes(2);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(peakvalleyBestTimeToBuySellStocks([1, 5, 10], visit)).toEqual(9);
|
||||
expect(visit).toHaveBeenCalledTimes(3);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(peakvalleyBestTimeToBuySellStocks([10, 1, 5, 20, 15, 21], visit)).toEqual(25);
|
||||
expect(visit).toHaveBeenCalledTimes(6);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(peakvalleyBestTimeToBuySellStocks([7, 1, 5, 3, 6, 4], visit)).toEqual(7);
|
||||
expect(visit).toHaveBeenCalledTimes(6);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(peakvalleyBestTimeToBuySellStocks([1, 2, 3, 4, 5], visit)).toEqual(4);
|
||||
expect(visit).toHaveBeenCalledTimes(5);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(peakvalleyBestTimeToBuySellStocks([7, 6, 4, 3, 1], visit)).toEqual(0);
|
||||
expect(visit).toHaveBeenCalledTimes(5);
|
||||
|
||||
visit = jest.fn();
|
||||
expect(peakvalleyBestTimeToBuySellStocks(
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
|
||||
visit,
|
||||
)).toEqual(19);
|
||||
expect(visit).toHaveBeenCalledTimes(20);
|
||||
});
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Finds the maximum profit from selling and buying the stocks.
|
||||
* ACCUMULATOR APPROACH.
|
||||
*
|
||||
* @param {number[]} prices - Array of stock prices, i.e. [7, 6, 4, 3, 1]
|
||||
* @param {function(): void} visit - Visiting callback to calculate the number of iterations.
|
||||
* @return {number} - The maximum profit
|
||||
*/
|
||||
const accumulatorBestTimeToBuySellStocks = (prices, visit = () => {}) => {
|
||||
visit();
|
||||
let profit = 0;
|
||||
for (let day = 1; day < prices.length; day += 1) {
|
||||
visit();
|
||||
// Add the increase of the price from yesterday till today (if there was any) to the profit.
|
||||
profit += Math.max(prices[day] - prices[day - 1], 0);
|
||||
}
|
||||
return profit;
|
||||
};
|
||||
|
||||
export default accumulatorBestTimeToBuySellStocks;
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Finds the maximum profit from selling and buying the stocks.
|
||||
* DIVIDE & CONQUER APPROACH.
|
||||
*
|
||||
* @param {number[]} prices - Array of stock prices, i.e. [7, 6, 4, 3, 1]
|
||||
* @param {function(): void} visit - Visiting callback to calculate the number of iterations.
|
||||
* @return {number} - The maximum profit
|
||||
*/
|
||||
const dqBestTimeToBuySellStocks = (prices, visit = () => {}) => {
|
||||
/**
|
||||
* Recursive implementation of the main function. It is hidden from the users.
|
||||
*
|
||||
* @param {boolean} buy - Whether we're allow to sell or to buy now
|
||||
* @param {number} day - Current day of trading (current index of prices array)
|
||||
* @returns {number} - Max profit from buying/selling
|
||||
*/
|
||||
const recursiveBuyerSeller = (buy, day) => {
|
||||
// Registering the recursive call visit to calculate the complexity.
|
||||
visit();
|
||||
|
||||
// Quitting the recursion if this is the last day of trading (prices array ended).
|
||||
if (day === prices.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we're buying - we're loosing money (-1), if we're selling we're getting money (+1).
|
||||
const operationSign = buy ? -1 : +1;
|
||||
return Math.max(
|
||||
// Option 1: Don't do anything.
|
||||
recursiveBuyerSeller(buy, day + 1),
|
||||
// Option 2: Sell or Buy at the current price.
|
||||
operationSign * prices[day] + recursiveBuyerSeller(!buy, day + 1),
|
||||
);
|
||||
};
|
||||
|
||||
const buy = true;
|
||||
const day = 0;
|
||||
|
||||
return recursiveBuyerSeller(buy, day);
|
||||
};
|
||||
|
||||
export default dqBestTimeToBuySellStocks;
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Finds the maximum profit from selling and buying the stocks.
|
||||
* PEAK VALLEY APPROACH.
|
||||
*
|
||||
* @param {number[]} prices - Array of stock prices, i.e. [7, 6, 4, 3, 1]
|
||||
* @param {function(): void} visit - Visiting callback to calculate the number of iterations.
|
||||
* @return {number} - The maximum profit
|
||||
*/
|
||||
const peakvalleyBestTimeToBuySellStocks = (prices, visit = () => {}) => {
|
||||
visit();
|
||||
let profit = 0;
|
||||
let low = prices[0];
|
||||
let high = prices[0];
|
||||
|
||||
prices.slice(1).forEach((currentPrice) => {
|
||||
visit();
|
||||
if (currentPrice < high) {
|
||||
// If price went down, we need to sell.
|
||||
profit += high - low;
|
||||
low = currentPrice;
|
||||
high = currentPrice;
|
||||
} else {
|
||||
// If price went up, we don't need to do anything but increase a high record.
|
||||
high = currentPrice;
|
||||
}
|
||||
});
|
||||
|
||||
// In case if price went up during the last day
|
||||
// and we didn't have chance to sell inside the forEach loop.
|
||||
profit += high - low;
|
||||
|
||||
return profit;
|
||||
};
|
||||
|
||||
export default peakvalleyBestTimeToBuySellStocks;
|
Loading…
Reference in New Issue
Block a user