Bài toán
Link đến bài toán: 1793. Maximum Score of a Good Subarray
Phân tích
Bài toán này có thể giải bằng cách duyệt mảng và tìm ra các cặp (left, right) sao cho left <= i <= right
và min(nums[left:right+1]) * (right - left + 1) là lớn nhất.
Để tìm được cặp (left, right) như vậy, ta có thể dùng monotonic stack, hoặc kĩ thuật two pointers.
Monotonic stack
Monotonic stack là một cấu trúc dữ liệu dạng stack, trong đó đảm bảo rằng các phần tử trong stack luôn được sắp xếp theo một thứ tự nhất định.
Nếu ta thêm một phần tử nghịch thế với thứ tự này, thì ta sẽ pop các phần tử trong stack cho đến khi phần tử đó thỏa mãn thứ tự của stack.
Tham khảo thêm tại đây.
Vậy làm sao để áp dụng monotonic stack vào bài toán này?
Ta sẽ khởi tạo 2 mảng left và right
với left[i] là vị trí đầu tiên bên trái của i mà nums[left[i]] < nums[i]
và right[i] là vị trí đầu tiên bên phải của i mà nums[right[i]] < nums[i].
Lúc này, mỗi cặp (left[i], right[i]) sẽ là một cặp (left, right) thỏa mãn yêu cầu của bài toán.
Sau đó, ta sẽ duyệt mảng nums từ trái sang phải, và với mỗi phần tử nums[i], nếu left[i] < k và right[i] > k (thỏa điều kiện k)
thì ta sẽ tính max(ans, nums[i] * (right[i] - left[i] - 1)).
Độ phức tạp: O(n)
Two pointers
Ta sẽ khởi tạo 2 con trỏ left và right tại vị trí k.
Nếu nums[left-1] < nums[right+1] thì ta sẽ tăng right lên 1 đơn vị, ngược lại ta sẽ giảm left đi 1 đơn vị.
Với mỗi cặp (left, right) ta sẽ tính luôn max(ans, min(nums[left:right+1]) * (right - left + 1)).
Độ phức tạp: O(n)
Giải thuật
Monotonic stack
- Khởi tạo mảng
leftvàrightvớileft[i] = -1vàright[i] = n. - Khởi tạo một
monotonic stackrỗng. - Lặp
itừ0đếnn:- Lặp chừng nào
stackkhông rỗng vànums[stack.top()] > nums[i]:- Lấy
topcủastackra và gánleft[top] = i. popstack.
- Lấy
pushivàostack.
- Lặp chừng nào
- Lặp
itừn-1đến0:- Lặp chừng nào
stackkhông rỗng vànums[stack.top()] > nums[i]:- Lấy
topcủastackra và gánright[top] = i. popstack.
- Lấy
pushivàostack.
- Lặp chừng nào
- Khởi tạo
ansbằng 0. - Lặp
itừ0đếnn:- Nếu
left[i] < kvàright[i] > k:- Gán
ans = max(ans, nums[i] * (right[i] - left[i] - 1)).
- Gán
- Nếu
- Trả về
ans.
Two pointers
- Khởi tạo
leftvàrightbằngk. - Khởi tạo
ansbằngnums[k]. - Khởi tạo
minValbằngnums[k]. - Lặp chừng nào
left > 0 || right < n-1:- Nếu
left == 0hoặcnums[left-1] < nums[right+1]:- Tăng
rightlên 1 đơn vị. - Gán
minVal = min(minVal, nums[right]).
- Tăng
- Ngược lại:
- Giảm
leftđi 1 đơn vị. - Gán
minVal = min(minVal, nums[left]).
- Giảm
- Gán
ans = max(ans, minVal * (right - left + 1)).
- Nếu
- Trả về
ans.
func maximumScore(nums []int, k int) int {
n := len(nums)
i, j := k, k
res := nums[k]
minVal := nums[k]
for i > 0 || j < n-1 {
if i == 0 {
j++
} else if j == n-1 {
i--
} else if nums[i-1] < nums[j+1] {
j++
} else {
i--
}
minVal = min(minVal, min(nums[i], nums[j]))
res = max(res, minVal*(j-i+1))
}
return res
}
func min(a int, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}