|
| 1 | +# Thời Điểm Tốt Nhất Để Mua và Bán Cổ Phiếu |
| 2 | + |
| 3 | +## Mô tả công việc |
| 4 | + |
| 5 | +Giả sử bạn có một mảng giá với phần tử thứ `i` là giá cổ phiếu của ngày `i`. |
| 6 | + |
| 7 | +Hãy tìm lợi nhuận lớn nhất có thể. Bạn có thể thực hiện bao nhiêu giao dịch tuỳ thích (có thể mua một cổ phiếu và bán một cổ phiếu nhiều lần) |
| 8 | + |
| 9 | +> Lưu ý: Bạn không thể thực hiện nhiều giao dịch với một cổ phiếu cùng thời điểm (vd, bạn phải bán cổ phiếu trước khi mua lại nó lần nữa). |
| 10 | +
|
| 11 | +**Ví dụ 1** |
| 12 | + |
| 13 | +``` |
| 14 | +Input: [7, 1, 5, 3, 6, 4] |
| 15 | +Output: 7 |
| 16 | +``` |
| 17 | + |
| 18 | +_Giải thích_ : Mua vào ngày thứ `2` với giá là `1` và bán nó vào ngày thứ `3` với giá là `5`, lợi nhuận là `4`. Sau đó mua lại và vào ngày thứ `4` với giá là `3` sau đấy bán lại vào ngày thứ `5` với giá là `6`, lợi nhuận có thêm là `3`. |
| 19 | + |
| 20 | +**Ví dụ 2** |
| 21 | + |
| 22 | +``` |
| 23 | +Input: [1, 2, 3, 4, 5] |
| 24 | +Output: 4 |
| 25 | +``` |
| 26 | + |
| 27 | +_Giải thích_: Mua vào ngày đầu tiên (`price - 1`) và bán vào ngày cuối cùng (`price = 5`), lợi nhuận thu được `profit = 5-1 = 4`. Lưu ý bạn không thể mua vào ngày 1, tiếp tục mua vào ngày thứ 2 và bán chúng sau. Vì như vậy là thực hiện nhiều giao dịch, ta phải bán nó trước khi mua lại. |
| 28 | + |
| 29 | +**Ví dụ 3** |
| 30 | + |
| 31 | +``` |
| 32 | +Input: [7, 6, 4, 3, 1] |
| 33 | +Output: 0 |
| 34 | +``` |
| 35 | + |
| 36 | +_Giải thích_: vì giá cổ phiếu giảm dần nên lợi nhuận lớn nhất là không mua gì cả `profit = 0`. |
| 37 | + |
| 38 | +## Giải pháp |
| 39 | + |
| 40 | +### Giải pháp chia để trị `O(2^n)` |
| 41 | + |
| 42 | +Ta sẽ thử **tất cả** các trường hợp mua bán để tìm ra lợi nhuận lớn nhất có thể bằng giải pháp _chia để trị_. |
| 43 | + |
| 44 | +Bắt đầu ta có một mảng giá `[7, 6, 4, 3, 1]` và ta ở vị trị của ngày thứ _nhất_ (đầu mảng) của giao dịch. Tại thời điểm này, ta có thể nói rằng tổng lợi nhuận tối đa sẽ là _giá trị lớn nhất_ của hai giá trị sau: |
| 45 | + |
| 46 | +1. _Giữ lại tiền_ → tổng lợi nhuận sẽ bằng lợi nhuận từ việc mua/bán cổ phiếu còn lại → `keepProfit = profit([6, 4, 3, 1])`. |
| 47 | +2. _Mua/bán theo giá hiện tại_ → lợi nhuận trong trường hợp này sẽ bằng lợi nhuận từ việc mua/bán cổ phiếu còn lại cộng (hoặc trừ, tùy thuộc vào việc đang bán hay mua) giá cổ phiếu hiện tại → `buySellProfit = -7 + profit([6, 4, 3, 1])`. |
| 48 | + |
| 49 | +Tổng lợi nhuận sẽ bằng → `overalProfit = Max(keepProfit, buySellProfit)`. |
| 50 | + |
| 51 | +Như vậy, bạn có thể thấy `profit([6, 4, 3, 1])` đang được giải quyết theo cùng một cách đệ quy. |
| 52 | + |
| 53 | +> Xem [dqBestTimeToBuySellStocks.js](dqBestTimeToBuySellStocks.js) |
| 54 | +
|
| 55 | +### Độ phức tạp thời gian |
| 56 | + |
| 57 | +Tất cả các gọi đệ quy sẽ tạo ra _2_ nhánh đệ quy. Độ sâu của đệ quy là `n` (độ dài của mảng) do đó, độ phức tạp thời gian sẽ là `O(2^n)` |
| 58 | + |
| 59 | +Như bạn có thể thấy, điều này là rất kém hiệu quả. Ví dụ với mảng giá kích thước `20`, số lượng gọi đệ quy sẽ ở đâu đó gần với `2M`! |
| 60 | + |
| 61 | +### Độ phức tạp không gian bổ sung |
| 62 | + |
| 63 | +Nếu ta tránh sao chép mảng giữa các lần gọi đệ quy và sẽ sử dụng con trỏ mảng thì độ phức tạp của không gian bổ sung sẽ tỷ lệ với độ sâu của đệ quy: `O (n)` |
| 64 | + |
| 65 | +## Giải pháp Peek Valley `O(n)` |
| 66 | + |
| 67 | +Nếu ta phát hoạ mảng giá (vd `[7, 1, 5, 3, 6, 4]`) ta sẽ nhận thấy các điểm đáng lưu ý là các đỉnh và đáy liên tiếp. |
| 68 | + |
| 69 | + |
| 70 | + |
| 71 | +_Image source: [LeetCode](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/solution/)_ |
| 72 | + |
| 73 | +Vì vậy, nếu ta theo dõi giá đang tăng và sẽ bán cổ phiếu ngay lập tức _trước_ khi giá đi xuống, ta sẽ thu được lợi nhuận tối đa (hãy nhớ rằng ta đã mua cổ phiếu ở đáy với giá thấp của nó). |
| 74 | + |
| 75 | +> Xem [peakvalleyBestTimeToBuySellStocks.js](peakvalleyBestTimeToBuySellStocks.js) |
| 76 | +
|
| 77 | +### Độ phức tạp thời gian |
| 78 | + |
| 79 | +Vì thuật toán chỉ yêu cầu một lần chuyển qua mảng giá nên độ phức tạp về thời gian sẽ bằng `O (n)`. |
| 80 | + |
| 81 | +### Độ phức tạp không gian bổ sung |
| 82 | + |
| 83 | +Ngoại trừ mảng giá, thuật toán sử dụng lượng bộ nhớ không đổi. Do đó, độ phức tạp không gian bổ sung là `O (1)`. |
| 84 | + |
| 85 | +## Giải pháp tích luỹ `O(n)` |
| 86 | + |
| 87 | +Có một cách tiếp cận đơn giản hơn tồn tại. Giả sử ta có mảng giá trông như sau `[1, 7, 2, 3, 6, 7, 6, 7]`: |
| 88 | + |
| 89 | + |
| 90 | + |
| 91 | +_Image source: [LeetCode](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/solution/)_ |
| 92 | + |
| 93 | +Bạn có thể nhận thấy rằng ta thậm chí không cần phải theo dõi mức giá liên tục tăng. Thay vào đó, ta có thể chỉ cần thêm chênh lệch giá cho _tất cả các đoạn đang phát triển_ của biểu đồ mà cuối cùng sẽ tính đến lợi nhuận cao nhất có thể, |
| 94 | + |
| 95 | +> Xem [accumulatorBestTimeToBuySellStocks.js](accumulatorBestTimeToBuySellStocks.js) |
| 96 | +
|
| 97 | + |
| 98 | +### Độ phức tạp về thời gian |
| 99 | + |
| 100 | +Vì thuật toán chỉ yêu cầu một lần chuyển qua mảng giá nên độ phức tạp về thời gian sẽ bằng `O (n)`. |
| 101 | + |
| 102 | +### Độ phức tạp không gian bổ sung |
| 103 | + |
| 104 | +Ngoại trừ mảng giá, thuật toán sử dụng lượng bộ nhớ không đổi. Do đó, độ phức tạp không gian bổ sung là `O (1)`. |
| 105 | + |
| 106 | +## Liên kết |
| 107 | + |
| 108 | +- [Best Time to Buy and Sell Stock on LeetCode](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/) |
0 commit comments