Skip to content

Commit 261207c

Browse files
committed
Add solutions to new prefix sum problems
1 parent 0508426 commit 261207c

11 files changed

+633
-43
lines changed

leetcode/152.maximum-product-subarray.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ fun maxProduct(nums: IntArray): Int {
1515
// The local max / min when considering the current number
1616
val localMax = IntArray(nums.size)
1717
val localMin = IntArray(nums.size)
18-
var globalMax = nums[0]
19-
localMax[0] = nums[0]
20-
localMin[0] = nums[0]
18+
var globalMax = nums.first()
19+
localMax[0] = nums.first()
20+
localMin[0] = nums.first()
2121
for (i in 1 until nums.size) {
2222
val current = nums[i]
2323
localMax[i] = maxOf(
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# [1749. Maximum Absolute Sum of Any Subarray](https://leetcode.com/problems/maximum-absolute-sum-of-any-subarray/)
2+
3+
## Key Insights
4+
The maximum absolute sum of any subarray could be either:
5+
* The positive maximum subarray sum.
6+
* The negative minimum subarray sum, its absolute value is the candidate of the answer.
7+
8+
So we have to keep track of both the maximum and minimum subarray sum. We can use the similar idea as [53. Maximum Subarray](../leetcode/53.maximum-subarray.md) to solve this problem.
9+
10+
```js
11+
1, -3, -2
12+
13+
1 1 1 // local max
14+
1 -3 -5 // local min
15+
1 3 5 // global max
16+
```
17+
18+
## Dynamic Programming
19+
```kotlin
20+
fun maxAbsoluteSum(nums: IntArray): Int {
21+
val n = nums.size
22+
val localMax = IntArray(n)
23+
val localMin = IntArray(n)
24+
localMax[0] = nums.first()
25+
localMin[0] = nums.first()
26+
var globalMax = abs(nums.first())
27+
for (i in 1 until n) {
28+
localMax[i] = maxOf(localMax[i - 1] + nums[i], nums[i])
29+
localMin[i] = minOf(localMin[i - 1] + nums[i], nums[i])
30+
globalMax = maxOf(globalMax, abs(localMax[i]))
31+
globalMax = maxOf(globalMax, abs(localMin[i]))
32+
}
33+
return globalMax
34+
}
35+
36+
// Or equivalently, we can use two variables to keep track of the local / global max and min.
37+
fun maxAbsoluteSum(nums: IntArray): Int {
38+
// Local max / min
39+
var maxEndingHere = 0
40+
var minEndingHere = 0
41+
// Global max / min
42+
var maxSoFar = 0
43+
var minSoFar = 0
44+
for (num in nums) {
45+
maxEndingHere = maxOf(maxEndingHere + num, num)
46+
minEndingHere = minOf(minEndingHere + num, num)
47+
maxSoFar = maxOf(maxSoFar, maxEndingHere)
48+
minSoFar = minOf(minSoFar, minEndingHere)
49+
}
50+
return maxOf(maxSoFar, abs(minSoFar))
51+
}
52+
```
53+
54+
* **Time Complexity**: `O(n)`
55+
* **Space Complexity**: `O(1)`
56+
57+
## Prefix Sum
58+
```kotlin
59+
fun maxAbsoluteSum(nums: IntArray): Int {
60+
// We need `0` as the base prefix sum.
61+
var minPrefixSum = 0 // Can't set to Int.MAX_VALUE, `0` is the base.
62+
var maxPrefixSum = 0 // Can't set to Int.MIN_VALUE, `0` is the base.
63+
var prefixSum = 0
64+
var maxSum = Int.MIN_VALUE
65+
for (num in nums) {
66+
prefixSum += num
67+
68+
maxSum = maxOf(maxSum, abs(prefixSum - minPrefixSum))
69+
maxSum = maxOf(maxSum, abs(prefixSum - maxPrefixSum))
70+
71+
minPrefixSum = minOf(minPrefixSum, prefixSum)
72+
maxPrefixSum = maxOf(maxPrefixSum, prefixSum)
73+
}
74+
return maxSum
75+
}
76+
```
77+
78+
* **Time Complexity**: `O(n)`
79+
* **Space Complexity**: `O(1)`
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# [2055. Plates Between Candles](https://leetcode.com/problems/plates-between-candles/)
2+
3+
## Breakdowns
4+
> 1. Given a string which contains ONLY plates, how to count the number of plates from a query?
5+
6+
We can use prefix sum to count the number of plates.
7+
8+
> 2. Given a string which contains ONLY plates and candles, how to count the number of plates?
9+
10+
We can use prefix sum to count the number of plates and candles, then return the number of plates - number of candles.
11+
12+
> 3. Given a string which contains ONLY plates and candles, how to count the number of plates between two candles?
13+
14+
This problem.
15+
16+
## Binary Search + Prefix Sum
17+
We are calculating the number of plates between two candles, what does "between two candles" mean? We are only allowed to count the `*` between two candles.
18+
19+
```js
20+
// query
21+
<--------->
22+
... * * | * * | ...
23+
X X ^^^
24+
Answer: 2
25+
```
26+
27+
Given a query `(L, R)`, we need to find the leftmost and rightmost candles between `L` and `R`.
28+
29+
```js
30+
... * * | * * | ...
31+
^ ^ // leftmost and rightmost candles
32+
L R
33+
```
34+
35+
We can use binary search to find the leftmost and rightmost candles:
36+
* Leftmost: Search the first element that `L <= candles`.
37+
```js
38+
... * * | ... | ... | * * *
39+
^
40+
L R
41+
```
42+
43+
* Rightmost: Search the last element that `candles <= R`.
44+
```js
45+
... * * | ... | ... | * * *
46+
^
47+
L R
48+
```
49+
50+
We can iterate through the string to get the positions of the candles,
51+
52+
How can we count the number of plates, we can use prefix sum to accumulate the number of plates.
53+
```js
54+
0 1 2 3 4 5 6 7
55+
* * * | * * | *
56+
1 2 3 3 4 5 5 6
57+
```
58+
59+
```kotlin
60+
fun platesBetweenCandles(s: String, queries: Array<IntArray>): IntArray {
61+
val candlePositions = mutableListOf<Int>()
62+
val prefixSum = IntArray(s.length)
63+
var sum = 0
64+
for (i in 0 until s.length) {
65+
val c = s[i]
66+
if (c == candle) {
67+
candlePositions.add(i)
68+
} else {
69+
sum++
70+
}
71+
prefixSum[i] = sum
72+
}
73+
val n = queries.size
74+
val answer = IntArray(n)
75+
for (q in queries.indices) {
76+
val (l, r) = queries[q]
77+
val leftmost = searchLeftmost(candlePositions, l)
78+
val rightmost = searchRightmost(candlePositions, r)
79+
80+
if (leftmost == -1 || rightmost == -1) {
81+
continue
82+
} else {
83+
val left = candlePositions[leftmost]
84+
val right = candlePositions[rightmost]
85+
// check if the leftmost and rightmost candles are between l and r
86+
if (left in l..r && right in l..r) {
87+
answer[q] = prefixSum[candlePositions[rightmost]] - prefixSum[candlePositions[leftmost]]
88+
}
89+
}
90+
}
91+
return answer
92+
}
93+
94+
// Search the leftmost candle
95+
/**
96+
candles = [2, 3, 6]
97+
range = 1
98+
1 2 3 4 5 6 7 8 9
99+
| | |
100+
L
101+
Search the first element that "target <= positions[i]" = 2
102+
*/
103+
private fun searchLeftmost(positions: List<Int>, target: Int): Int {
104+
var left = 0
105+
var right = positions.size - 1
106+
while (left <= right) {
107+
val middle = left + (right - left) / 2
108+
val isValid = target <= positions[middle]
109+
if (isValid) right = middle - 1
110+
else left = middle + 1
111+
}
112+
return if (left in 0 until positions.size) left else -1
113+
}
114+
115+
/**
116+
candles = [2, 3, 6]
117+
range = 9
118+
1 2 3 4 5 6 7 8 9
119+
| | |
120+
R
121+
Search the last element that "positions[i] <= target" = 6
122+
*/
123+
private fun searchRightmost(positions: List<Int>, target: Int): Int {
124+
var left = 0
125+
var right = positions.size - 1
126+
while (left <= right) {
127+
val middle = left + (right - left) / 2
128+
val isValid = positions[middle] <= target
129+
if (isValid) left = middle + 1
130+
else right = middle - 1
131+
}
132+
return if (right in 0 until positions.size) right else -1
133+
}
134+
```
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# [2389. Longest Subsequence With Limited Sum](https://leetcode.com/problems/longest-subsequence-with-limited-sum/description/)
2+
3+
## Key Insights
4+
To find the **subsequence** that sums up to threshold (`queries[i]`):
5+
6+
```js
7+
nums = [3, 1, 4, 2]
8+
threshold = 6
9+
```
10+
11+
There are 2 subsequences that sum up to 6:
12+
13+
```js
14+
[3, 1, 4, 2]
15+
O O O
16+
17+
[3, 1, 4, 2]
18+
O O
19+
```
20+
21+
A *subsequence* is any subset of the array, regardless of the order, so to maximum size of a subsequence that you can take from nums under the threshold, the best way is:
22+
* Sort the array in ascending order.
23+
* Take from the smallest element until the sum exceeds the threshold. (greedy)
24+
25+
The best subsequence is just the **first K smallest elements**, that is **the prefix sum of the sorted array**.
26+
27+
```js
28+
nums = [1, 2, 3, 4]
29+
1 +2 +3 = 6
30+
threshold = 6
31+
```
32+
33+
Even this problems is asking for the longest subsequence, we can sort the array first, then the problem becomes to find the longest subarray (prefix) that sums up <= threshold. It's similar to the key idea in [2779. Maximum Beauty of an Array After Applying Operation](../leetcode/2779.maximum-beauty-of-an-array-after-applying-operation.md).
34+
35+
Let's walk through the example:
36+
37+
```js
38+
nums = [4, 2, 1, 3]
39+
queries = [4, 10, 21]
40+
41+
After sorting:
42+
[1, 2, 3, 4]
43+
[1, 3, 6, 10] // prefix sum
44+
45+
Query: 4, max subarray sum <= 4, result = [1, 2]
46+
Query: 10, max subarray sum <= 10, result = [1, 3, 6]
47+
Query: 21, max subarray sum <= 21, result = [1, 3, 6, 10]
48+
```
49+
50+
## Prefix Sum + Binary Search
51+
We can build the prefix sum array first, then for each query, we can use binary search to find the longest subarray that sums up <= threshold.
52+
53+
```js
54+
0 1 2 3 // index
55+
[1, 3, 6, 10] // prefix sum
56+
57+
Query: 4, binary search, find the first element > 4, result = 2
58+
Query: 10, binary search, find the first element > 10, result = 4
59+
Query: 21, binary search, find the first element > 21, result = 4
60+
```
61+
62+
```kotlin
63+
fun answerQueries(nums: IntArray, queries: IntArray): IntArray {
64+
nums.sort()
65+
for (i in 1 until nums.size) {
66+
nums[i] += nums[i - 1]
67+
}
68+
69+
val ans = IntArray(queries.size)
70+
for (q in queries.indices) {
71+
val index = binarySearch(nums, queries[q])
72+
ans[q] = index
73+
}
74+
return ans
75+
}
76+
77+
// Find the first element > target
78+
// Or we can find the last element <= target
79+
fun binarySearch(nums: IntArray, target: Int): Int {
80+
var left = 0
81+
var right = nums.size - 1
82+
while (left <= right) {
83+
val middle = left + (right - left) / 2
84+
val isValid = nums[middle] > target
85+
if (isValid) {
86+
right = middle - 1
87+
} else {
88+
left = middle + 1
89+
}
90+
}
91+
return left
92+
}
93+
```
94+
95+
* **Time Complexity**: `O(n * log(n) + m * log(n))`, where `n` is the length of `nums` and `m` is the length of `queries`.
96+
* **Space Complexity**: `O(1)`
97+
98+
## Brute Force
99+
```kotlin
100+
fun answerQueries(nums: IntArray, queries: IntArray): IntArray {
101+
nums.sort()
102+
val n = nums.size
103+
val m = queries.size
104+
val ans = IntArray(m)
105+
for (i in queries.indices) {
106+
val max = queries[i]
107+
var j = 0
108+
var sum = 0
109+
while (j < n && sum + nums[j] <= max) {
110+
sum += nums[j]
111+
j++
112+
}
113+
ans[i] = j
114+
}
115+
return ans
116+
}
117+
```
118+
119+
* **Time Complexity**: `O(n * m)`
120+
* **Space Complexity**: `O(1)`

0 commit comments

Comments
 (0)