Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2bbe12b

Browse files
authored
Add divide and conquer example: best time to buy and sell stocks (trekhleb#612)
* Add divide and conquer example: best time to buy and sell stocks. * Add divide and conquer example: best time to buy and sell stocks. * Add dynamic programming version.
1 parent e71dc8d commit 2bbe12b

10 files changed

+429
-6
lines changed

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ a set of rules that precisely define a sequence of operations.
129129
* `B` [Depth-First Search](src/algorithms/graph/depth-first-search) (DFS)
130130
* `B` [Breadth-First Search](src/algorithms/graph/breadth-first-search) (BFS)
131131
* `B` [Kruskal’s Algorithm](src/algorithms/graph/kruskal) - finding Minimum Spanning Tree (MST) for weighted undirected graph
132-
* `A` [Dijkstra Algorithm](src/algorithms/graph/dijkstra) - finding shortest paths to all graph vertices from single vertex
133-
* `A` [Bellman-Ford Algorithm](src/algorithms/graph/bellman-ford) - finding shortest paths to all graph vertices from single vertex
134-
* `A` [Floyd-Warshall Algorithm](src/algorithms/graph/floyd-warshall) - find shortest paths between all pairs of vertices
132+
* `A` [Dijkstra Algorithm](src/algorithms/graph/dijkstra) - finding the shortest paths to all graph vertices from single vertex
133+
* `A` [Bellman-Ford Algorithm](src/algorithms/graph/bellman-ford) - finding the shortest paths to all graph vertices from single vertex
134+
* `A` [Floyd-Warshall Algorithm](src/algorithms/graph/floyd-warshall) - find the shortest paths between all pairs of vertices
135135
* `A` [Detect Cycle](src/algorithms/graph/detect-cycle) - for both directed and undirected graphs (DFS and Disjoint Set based versions)
136136
* `A` [Prim’s Algorithm](src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) for weighted undirected graph
137137
* `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.
157157
* `B` [Unique Paths](src/algorithms/uncategorized/unique-paths) - backtracking, dynamic programming and Pascal's Triangle based examples
158158
* `B` [Rain Terraces](src/algorithms/uncategorized/rain-terraces) - trapping rain water problem (dynamic programming and brute force versions)
159159
* `B` [Recursive Staircase](src/algorithms/uncategorized/recursive-staircase) - count the number of ways to reach to the top (4 solutions)
160+
* `B` [Best Time To Buy Sell Stocks](src/algorithms/uncategorized/best-time-to-buy-sell-stocks) - divide and conquer and one-pass examples
160161
* `A` [N-Queens Problem](src/algorithms/uncategorized/n-queens)
161162
* `A` [Knight's Tour](src/algorithms/uncategorized/knight-tour)
162163

@@ -176,7 +177,7 @@ algorithm is an abstraction higher than a computer program.
176177
* **Greedy** - choose the best option at the current time, without any consideration for the future
177178
* `B` [Jump Game](src/algorithms/uncategorized/jump-game)
178179
* `A` [Unbound Knapsack Problem](src/algorithms/sets/knapsack-problem)
179-
* `A` [Dijkstra Algorithm](src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices
180+
* `A` [Dijkstra Algorithm](src/algorithms/graph/dijkstra) - finding the shortest path to all graph vertices
180181
* `A` [Prim’s Algorithm](src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) for weighted undirected graph
181182
* `A` [Kruskal’s Algorithm](src/algorithms/graph/kruskal) - finding Minimum Spanning Tree (MST) for weighted undirected graph
182183
* **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.
191192
* `B` [Matrices](src/algorithms/math/matrix) - generating and traversing the matrices of different shapes
192193
* `B` [Jump Game](src/algorithms/uncategorized/jump-game)
193194
* `B` [Fast Powering](src/algorithms/math/fast-powering)
195+
* `B` [Best Time To Buy Sell Stocks](src/algorithms/uncategorized/best-time-to-buy-sell-stocks) - divide and conquer and one-pass examples
194196
* `A` [Permutations](src/algorithms/sets/permutations) (with and without repetitions)
195197
* `A` [Combinations](src/algorithms/sets/combinations) (with and without repetitions)
196198
* **Dynamic Programming** - build up a solution using previously found sub-solutions
@@ -207,8 +209,8 @@ algorithm is an abstraction higher than a computer program.
207209
* `A` [0/1 Knapsack Problem](src/algorithms/sets/knapsack-problem)
208210
* `A` [Integer Partition](src/algorithms/math/integer-partition)
209211
* `A` [Maximum Subarray](src/algorithms/sets/maximum-subarray)
210-
* `A` [Bellman-Ford Algorithm](src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices
211-
* `A` [Floyd-Warshall Algorithm](src/algorithms/graph/floyd-warshall) - find shortest paths between all pairs of vertices
212+
* `A` [Bellman-Ford Algorithm](src/algorithms/graph/bellman-ford) - finding the shortest path to all graph vertices
213+
* `A` [Floyd-Warshall Algorithm](src/algorithms/graph/floyd-warshall) - find the shortest paths between all pairs of vertices
212214
* `A` [Regular Expression Matching](src/algorithms/string/regular-expression-matching)
213215
* **Backtracking** - similarly to brute force, try to generate all possible solutions, but each time you generate next solution you test
214216
if it satisfies all conditions, and only then continue generating subsequent solutions. Otherwise, backtrack, and go on a
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Best Time to Buy and Sell Stock
2+
3+
## Task Description
4+
5+
Say you have an array prices for which the `i`-th element is the price of a given stock on day `i`.
6+
7+
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).
8+
9+
> Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).
10+
11+
**Example #1**
12+
13+
```
14+
Input: [7, 1, 5, 3, 6, 4]
15+
Output: 7
16+
```
17+
18+
_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`.
19+
20+
**Example #2**
21+
22+
```
23+
Input: [1, 2, 3, 4, 5]
24+
Output: 4
25+
```
26+
27+
_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.
28+
29+
**Example #3**
30+
31+
```
32+
Input: [7, 6, 4, 3, 1]
33+
Output: 0
34+
```
35+
36+
_Explanation:_ In this case, no transaction is done, i.e. max `profit = 0`.
37+
38+
## Possible Solutions
39+
40+
### Divide and conquer approach `O(2^n)`
41+
42+
We may try **all** combinations of buying and selling and find out the most profitable one by applying _divide and conquer approach_.
43+
44+
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:
45+
46+
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])`.
47+
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])`.
48+
49+
The overall profit would be equal to → `overalProfit = Max(keepProfit, buySellProfit)`.
50+
51+
As you can see the `profit([6, 4, 3, 1])` task is being solved in the same recursive manner.
52+
53+
> See the full code example in [dqBestTimeToBuySellStocks.js](dqBestTimeToBuySellStocks.js)
54+
55+
#### Time Complexity
56+
57+
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)`.
58+
59+
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`!
60+
61+
#### Additional Space Complexity
62+
63+
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)`
64+
65+
## Peak Valley Approach `O(n)`
66+
67+
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
68+
69+
![Peak Valley Approach](https://leetcode.com/media/original_images/122_maxprofit_1.PNG)
70+
71+
_Image source: [LeetCode](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/solution/)_
72+
73+
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).
74+
75+
> See the full code example in [peakvalleyBestTimeToBuySellStocks.js](peakvalleyBestTimeToBuySellStocks.js)
76+
77+
#### Time Complexity
78+
79+
Since the algorithm requires only one pass through the prices array, the time complexity would equal `O(n)`.
80+
81+
#### Additional Space Complexity
82+
83+
Except of the prices array itself the algorithm consumes the constant amount of memory. Thus, additional space complexity is `O(1)`.
84+
85+
## Accumulator Approach `O(n)`
86+
87+
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]`:
88+
89+
![Simple One Pass](https://leetcode.com/media/original_images/122_maxprofit_2.PNG)
90+
91+
_Image source: [LeetCode](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/solution/)_
92+
93+
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,
94+
95+
> See the full code example in [accumulatorBestTimeToBuySellStocks.js](accumulatorBestTimeToBuySellStocks.js)
96+
97+
#### Time Complexity
98+
99+
Since the algorithm requires only one pass through the prices array, the time complexity would equal `O(n)`.
100+
101+
#### Additional Space Complexity
102+
103+
Except of the prices array itself the algorithm consumes the constant amount of memory. Thus, additional space complexity is `O(1)`.
104+
105+
## References
106+
107+
- [Best Time to Buy and Sell Stock on LeetCode](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import accumulatorBestTimeToBuySellStocks from '../accumulatorBestTimeToBuySellStocks';
2+
3+
describe('accumulatorBestTimeToBuySellStocks', () => {
4+
it('should find the best time to buy and sell stocks', () => {
5+
let visit;
6+
7+
expect(accumulatorBestTimeToBuySellStocks([1, 5])).toEqual(4);
8+
9+
visit = jest.fn();
10+
expect(accumulatorBestTimeToBuySellStocks([1], visit)).toEqual(0);
11+
expect(visit).toHaveBeenCalledTimes(1);
12+
13+
visit = jest.fn();
14+
expect(accumulatorBestTimeToBuySellStocks([1, 5], visit)).toEqual(4);
15+
expect(visit).toHaveBeenCalledTimes(2);
16+
17+
visit = jest.fn();
18+
expect(accumulatorBestTimeToBuySellStocks([5, 1], visit)).toEqual(0);
19+
expect(visit).toHaveBeenCalledTimes(2);
20+
21+
visit = jest.fn();
22+
expect(accumulatorBestTimeToBuySellStocks([1, 5, 10], visit)).toEqual(9);
23+
expect(visit).toHaveBeenCalledTimes(3);
24+
25+
visit = jest.fn();
26+
expect(accumulatorBestTimeToBuySellStocks([10, 1, 5, 20, 15, 21], visit)).toEqual(25);
27+
expect(visit).toHaveBeenCalledTimes(6);
28+
29+
visit = jest.fn();
30+
expect(accumulatorBestTimeToBuySellStocks([7, 1, 5, 3, 6, 4], visit)).toEqual(7);
31+
expect(visit).toHaveBeenCalledTimes(6);
32+
33+
visit = jest.fn();
34+
expect(accumulatorBestTimeToBuySellStocks([1, 2, 3, 4, 5], visit)).toEqual(4);
35+
expect(visit).toHaveBeenCalledTimes(5);
36+
37+
visit = jest.fn();
38+
expect(accumulatorBestTimeToBuySellStocks([7, 6, 4, 3, 1], visit)).toEqual(0);
39+
expect(visit).toHaveBeenCalledTimes(5);
40+
41+
visit = jest.fn();
42+
expect(accumulatorBestTimeToBuySellStocks(
43+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
44+
visit,
45+
)).toEqual(19);
46+
expect(visit).toHaveBeenCalledTimes(20);
47+
});
48+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import dpBestTimeToBuySellStocks from '../dpBestTimeToBuySellStocks';
2+
3+
describe('dpBestTimeToBuySellStocks', () => {
4+
it('should find the best time to buy and sell stocks', () => {
5+
let visit;
6+
7+
expect(dpBestTimeToBuySellStocks([1, 5])).toEqual(4);
8+
9+
visit = jest.fn();
10+
expect(dpBestTimeToBuySellStocks([1], visit)).toEqual(0);
11+
expect(visit).toHaveBeenCalledTimes(1);
12+
13+
visit = jest.fn();
14+
expect(dpBestTimeToBuySellStocks([1, 5], visit)).toEqual(4);
15+
expect(visit).toHaveBeenCalledTimes(2);
16+
17+
visit = jest.fn();
18+
expect(dpBestTimeToBuySellStocks([5, 1], visit)).toEqual(0);
19+
expect(visit).toHaveBeenCalledTimes(2);
20+
21+
visit = jest.fn();
22+
expect(dpBestTimeToBuySellStocks([1, 5, 10], visit)).toEqual(9);
23+
expect(visit).toHaveBeenCalledTimes(3);
24+
25+
visit = jest.fn();
26+
expect(dpBestTimeToBuySellStocks([10, 1, 5, 20, 15, 21], visit)).toEqual(25);
27+
expect(visit).toHaveBeenCalledTimes(6);
28+
29+
visit = jest.fn();
30+
expect(dpBestTimeToBuySellStocks([7, 1, 5, 3, 6, 4], visit)).toEqual(7);
31+
expect(visit).toHaveBeenCalledTimes(6);
32+
33+
visit = jest.fn();
34+
expect(dpBestTimeToBuySellStocks([1, 2, 3, 4, 5], visit)).toEqual(4);
35+
expect(visit).toHaveBeenCalledTimes(5);
36+
37+
visit = jest.fn();
38+
expect(dpBestTimeToBuySellStocks([7, 6, 4, 3, 1], visit)).toEqual(0);
39+
expect(visit).toHaveBeenCalledTimes(5);
40+
41+
visit = jest.fn();
42+
expect(dpBestTimeToBuySellStocks(
43+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
44+
visit,
45+
)).toEqual(19);
46+
expect(visit).toHaveBeenCalledTimes(20);
47+
});
48+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import dqBestTimeToBuySellStocks from '../dqBestTimeToBuySellStocks';
2+
3+
describe('dqBestTimeToBuySellStocks', () => {
4+
it('should find the best time to buy and sell stocks', () => {
5+
let visit;
6+
7+
expect(dqBestTimeToBuySellStocks([1, 5])).toEqual(4);
8+
9+
visit = jest.fn();
10+
expect(dqBestTimeToBuySellStocks([1], visit)).toEqual(0);
11+
expect(visit).toHaveBeenCalledTimes(3);
12+
13+
visit = jest.fn();
14+
expect(dqBestTimeToBuySellStocks([1, 5], visit)).toEqual(4);
15+
expect(visit).toHaveBeenCalledTimes(7);
16+
17+
visit = jest.fn();
18+
expect(dqBestTimeToBuySellStocks([5, 1], visit)).toEqual(0);
19+
expect(visit).toHaveBeenCalledTimes(7);
20+
21+
visit = jest.fn();
22+
expect(dqBestTimeToBuySellStocks([1, 5, 10], visit)).toEqual(9);
23+
expect(visit).toHaveBeenCalledTimes(15);
24+
25+
visit = jest.fn();
26+
expect(dqBestTimeToBuySellStocks([10, 1, 5, 20, 15, 21], visit)).toEqual(25);
27+
expect(visit).toHaveBeenCalledTimes(127);
28+
29+
visit = jest.fn();
30+
expect(dqBestTimeToBuySellStocks([7, 1, 5, 3, 6, 4], visit)).toEqual(7);
31+
expect(visit).toHaveBeenCalledTimes(127);
32+
33+
visit = jest.fn();
34+
expect(dqBestTimeToBuySellStocks([1, 2, 3, 4, 5], visit)).toEqual(4);
35+
expect(visit).toHaveBeenCalledTimes(63);
36+
37+
visit = jest.fn();
38+
expect(dqBestTimeToBuySellStocks([7, 6, 4, 3, 1], visit)).toEqual(0);
39+
expect(visit).toHaveBeenCalledTimes(63);
40+
41+
visit = jest.fn();
42+
expect(dqBestTimeToBuySellStocks(
43+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
44+
visit,
45+
)).toEqual(19);
46+
expect(visit).toHaveBeenCalledTimes(2097151);
47+
});
48+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import peakvalleyBestTimeToBuySellStocks from '../peakvalleyBestTimeToBuySellStocks';
2+
3+
describe('peakvalleyBestTimeToBuySellStocks', () => {
4+
it('should find the best time to buy and sell stocks', () => {
5+
let visit;
6+
7+
expect(peakvalleyBestTimeToBuySellStocks([1, 5])).toEqual(4);
8+
9+
visit = jest.fn();
10+
expect(peakvalleyBestTimeToBuySellStocks([1], visit)).toEqual(0);
11+
expect(visit).toHaveBeenCalledTimes(1);
12+
13+
visit = jest.fn();
14+
expect(peakvalleyBestTimeToBuySellStocks([1, 5], visit)).toEqual(4);
15+
expect(visit).toHaveBeenCalledTimes(2);
16+
17+
visit = jest.fn();
18+
expect(peakvalleyBestTimeToBuySellStocks([5, 1], visit)).toEqual(0);
19+
expect(visit).toHaveBeenCalledTimes(2);
20+
21+
visit = jest.fn();
22+
expect(peakvalleyBestTimeToBuySellStocks([1, 5, 10], visit)).toEqual(9);
23+
expect(visit).toHaveBeenCalledTimes(3);
24+
25+
visit = jest.fn();
26+
expect(peakvalleyBestTimeToBuySellStocks([10, 1, 5, 20, 15, 21], visit)).toEqual(25);
27+
expect(visit).toHaveBeenCalledTimes(6);
28+
29+
visit = jest.fn();
30+
expect(peakvalleyBestTimeToBuySellStocks([7, 1, 5, 3, 6, 4], visit)).toEqual(7);
31+
expect(visit).toHaveBeenCalledTimes(6);
32+
33+
visit = jest.fn();
34+
expect(peakvalleyBestTimeToBuySellStocks([1, 2, 3, 4, 5], visit)).toEqual(4);
35+
expect(visit).toHaveBeenCalledTimes(5);
36+
37+
visit = jest.fn();
38+
expect(peakvalleyBestTimeToBuySellStocks([7, 6, 4, 3, 1], visit)).toEqual(0);
39+
expect(visit).toHaveBeenCalledTimes(5);
40+
41+
visit = jest.fn();
42+
expect(peakvalleyBestTimeToBuySellStocks(
43+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
44+
visit,
45+
)).toEqual(19);
46+
expect(visit).toHaveBeenCalledTimes(20);
47+
});
48+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Finds the maximum profit from selling and buying the stocks.
3+
* ACCUMULATOR APPROACH.
4+
*
5+
* @param {number[]} prices - Array of stock prices, i.e. [7, 6, 4, 3, 1]
6+
* @param {function(): void} visit - Visiting callback to calculate the number of iterations.
7+
* @return {number} - The maximum profit
8+
*/
9+
const accumulatorBestTimeToBuySellStocks = (prices, visit = () => {}) => {
10+
visit();
11+
let profit = 0;
12+
for (let day = 1; day < prices.length; day += 1) {
13+
visit();
14+
// Add the increase of the price from yesterday till today (if there was any) to the profit.
15+
profit += Math.max(prices[day] - prices[day - 1], 0);
16+
}
17+
return profit;
18+
};
19+
20+
export default accumulatorBestTimeToBuySellStocks;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Finds the maximum profit from selling and buying the stocks.
3+
* DYNAMIC PROGRAMMING APPROACH.
4+
*
5+
* @param {number[]} prices - Array of stock prices, i.e. [7, 6, 4, 3, 1]
6+
* @param {function(): void} visit - Visiting callback to calculate the number of iterations.
7+
* @return {number} - The maximum profit
8+
*/
9+
const dpBestTimeToBuySellStocks = (prices, visit = () => {}) => {
10+
visit();
11+
let lastBuy = -prices[0];
12+
let lastSold = 0;
13+
14+
for (let day = 1; day < prices.length; day += 1) {
15+
visit();
16+
const curBuy = Math.max(lastBuy, lastSold - prices[day]);
17+
const curSold = Math.max(lastSold, lastBuy + prices[day]);
18+
lastBuy = curBuy;
19+
lastSold = curSold;
20+
}
21+
22+
return lastSold;
23+
};
24+
25+
export default dpBestTimeToBuySellStocks;

0 commit comments

Comments
 (0)