Skip to content

Commit 2d22bd3

Browse files
Merge pull request #115 from romankurnovskii/problems-3531-3728-3724-3720-3719-3714-3713-3709-3708-3705-3703-3702
Add solutions and explanations for problems 3531, 3728, 3724, 3720, 3719, 3714, 3713, 3709, 3708, 3703, 3702
2 parents 6a21afa + cbd2caa commit 2d22bd3

File tree

26 files changed

+1261
-120
lines changed

26 files changed

+1261
-120
lines changed

books/All.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10750,3 +10750,106 @@ def countTrapezoids(points):
1075010750
res = (total_sum * total_sum - sum_of_squares) // 2
1075110751
return res % MOD
1075210752
```
10753+
10754+
## 3709. Design Exam Scores Tracker [Medium]
10755+
https://leetcode.com/problems/design-exam-scores-tracker/
10756+
10757+
### Explanation
10758+
10759+
## Explanation
10760+
10761+
### Strategy (The "Why")
10762+
10763+
**Restate the problem:** We need to design a data structure that can record exam scores with timestamps and efficiently calculate the total score for exams taken within a time range.
10764+
10765+
**1.1 Constraints & Complexity:**
10766+
10767+
* **Input Size:** At most 10^5 calls to record() and totalScore(), with times and scores between 1 and 10^9.
10768+
* **Time Complexity:** O(log n) for totalScore() using binary search, O(1) amortized for record().
10769+
* **Space Complexity:** O(n) - We store times, scores, and prefix sums, where n is the number of records.
10770+
* **Edge Case:** If no exams exist in the time range, return 0.
10771+
10772+
**1.2 High-level approach:**
10773+
10774+
The goal is to maintain sorted lists of times and scores, along with prefix sums for efficient range sum queries. We use binary search to find the range of indices that fall within the query time range.
10775+
10776+
10777+
**1.3 Brute force vs. optimized strategy:**
10778+
10779+
* **Brute Force:** For each totalScore() call, iterate through all records and sum those in the time range. This is O(n) per query.
10780+
* **Optimized (Binary Search + Prefix Sum):** Maintain sorted times and prefix sums. Use binary search to find the range bounds in O(log n), then use prefix sums to calculate the sum in O(1). This is O(log n) per query.
10781+
* **Why it's better:** Binary search reduces query time from O(n) to O(log n), and prefix sums allow O(1) sum calculation once we know the range.
10782+
10783+
**1.4 Decomposition:**
10784+
10785+
1. Maintain three arrays: times (sorted), scores, and prefix_sum.
10786+
2. In record(), append the time and score, and update the prefix sum.
10787+
3. In totalScore(), use binary search to find the left bound (first time >= startTime) and right bound (first time > endTime).
10788+
4. If left >= right, return 0 (no exams in range).
10789+
5. Otherwise, return prefix_sum[right] - prefix_sum[left].
10790+
10791+
### Steps (The "How")
10792+
10793+
**2.1 Initialization & Example Setup:**
10794+
10795+
Let's use the example from the problem:
10796+
* `record(1, 98)`
10797+
* `totalScore(1, 1)`
10798+
* `record(5, 99)`
10799+
* `totalScore(1, 5)`
10800+
10801+
We initialize:
10802+
* `times = []`
10803+
* `scores = []`
10804+
* `prefix_sum = [0]`
10805+
10806+
**2.2 Start Recording:**
10807+
10808+
We record exams and update our data structures.
10809+
10810+
**2.3 Trace Walkthrough:**
10811+
10812+
| Step | Operation | times | scores | prefix_sum | Result |
10813+
| ---- | --------------- | ----- | ------- | ---------- | --------------------------------- |
10814+
| 1 | record(1,98) | [1] | [98] | [0,98] | - |
10815+
| 2 | totalScore(1,1) | [1] | [98] | [0,98] | 98 (prefix_sum[1]-prefix_sum[0]) |
10816+
| 3 | record(5,99) | [1,5] | [98,99] | [0,98,197] | - |
10817+
| 4 | totalScore(1,5) | [1,5] | [98,99] | [0,98,197] | 197 (prefix_sum[2]-prefix_sum[0]) |
10818+
10819+
At step 4, binary search finds left=0 (first time >= 1) and right=2 (first time > 5), so we return prefix_sum[2] - prefix_sum[0] = 197.
10820+
10821+
**2.4 Increment and Loop:**
10822+
10823+
Each record() call appends to the arrays and updates prefix sums. Each totalScore() call uses binary search to find the range.
10824+
10825+
**2.5 Return Result:**
10826+
10827+
After processing queries, we return the sum of scores in the specified time range using prefix sums.
10828+
10829+
### Solution
10830+
10831+
```python
10832+
def __init__(self):
10833+
self.times = []
10834+
self.scores = []
10835+
self.prefix_sum = [0] # prefix_sum[i] = sum of scores[0:i]
10836+
10837+
def record(self, time: int, score: int) -> None:
10838+
self.times.append(time)
10839+
self.scores.append(score)
10840+
# Update prefix sum
10841+
self.prefix_sum.append(self.prefix_sum[-1] + score)
10842+
10843+
def totalScore(self, startTime: int, endTime: int) -> int:
10844+
# Find the range of indices in times array
10845+
# Find left bound: first index where times[i] >= startTime
10846+
left = bisect_left(self.times, startTime)
10847+
# Find right bound: first index where times[i] > endTime
10848+
right = bisect_right(self.times, endTime)
10849+
10850+
if left >= right:
10851+
return 0
10852+
10853+
# Sum of scores from left to right-1
10854+
return self.prefix_sum[right] - self.prefix_sum[left]
10855+
```

data/book-sets.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@
6161
1304, 1318, 1337, 1372, 1423, 1431, 1448, 1456, 1466, 1480, 1493, 1515, 1523, 1528, 1557, 1584, 1657, 1672, 1679, 1704, 1732, 1768, 1798, 1920,
6262
1925, 1926, 1929, 1957, 1963, 2011, 2095, 2119, 2130, 2211, 2215, 2300, 2336, 2352, 2390, 2419, 2462, 2542, 2627, 2703, 2723, 2769, 2807, 2862,
6363
2879, 2884, 2888, 2894, 2942, 3100, 3110, 3133, 3164, 3190, 3197, 3228, 3291, 3320, 3351, 3380, 3381, 3413, 3424, 3432, 3444, 3471, 3512, 3522,
64-
3577, 3583, 3602, 3603, 3606, 3607, 3608, 3622, 3623, 3625, 3663, 3668, 3678, 3683, 3688, 3692, 3697, 3701, 3707, 3712, 3718, 3722, 3723, 3724,
65-
3726, 3727, 3728, 3731, 3732, 3733, 3736, 3737, 3738, 3739, 3740, 3741, 3742, 3743, 3745, 3747, 3748, 3750, 3751, 3752, 3753, 3754, 3755, 3756,
66-
3757, 3759, 3760, 3761, 3764, 3765, 3766, 3767, 3768, 3770, 3771, 3772
64+
3531, 3577, 3583, 3602, 3603, 3606, 3607, 3608, 3622, 3623, 3625, 3663, 3668, 3678, 3683, 3688, 3692, 3697, 3701, 3702, 3703, 3705, 3707, 3708,
65+
3709, 3712, 3713, 3714, 3718, 3719, 3720, 3722, 3723, 3724, 3726, 3727, 3728, 3731, 3732, 3733, 3736, 3737, 3738, 3739, 3740, 3741, 3742, 3743,
66+
3745, 3747, 3748, 3750, 3751, 3752, 3753, 3754, 3755, 3756, 3757, 3759, 3760, 3761, 3764, 3765, 3766, 3767, 3768, 3770, 3771, 3772
6767
]
6868
},
6969
{"title": "Visualization", "description": "", "tags": [], "problems": [1, 2, 11, 1431, 1679, 1768, 1798, 2215, 3603, 3622, 3623]},
@@ -174,15 +174,15 @@
174174
3417, 3418, 3419, 3420, 3421, 3423, 3425, 3426, 3427, 3428, 3429, 3430, 3433, 3434, 3435, 3436, 3438, 3439, 3440, 3441, 3442, 3443, 3445, 3446,
175175
3447, 3448, 3449, 3451, 3452, 3453, 3454, 3455, 3456, 3457, 3458, 3459, 3461, 3462, 3463, 3464, 3465, 3467, 3468, 3469, 3470, 3472, 3473, 3474,
176176
3475, 3477, 3478, 3479, 3480, 3482, 3483, 3484, 3485, 3486, 3487, 3488, 3489, 3490, 3492, 3493, 3494, 3495, 3497, 3498, 3499, 3500, 3501, 3502,
177-
3503, 3504, 3505, 3507, 3508, 3509, 3510, 3513, 3514, 3515, 3516, 3517, 3518, 3519, 3521, 3523, 3524, 3525, 3527, 3528, 3529, 3530, 3531, 3532,
178-
3533, 3534, 3536, 3537, 3538, 3539, 3541, 3542, 3543, 3544, 3545, 3546, 3547, 3548, 3550, 3551, 3552, 3553, 3554, 3556, 3557, 3558, 3559, 3560,
179-
3561, 3562, 3563, 3564, 3566, 3567, 3568, 3569, 3570, 3572, 3573, 3574, 3575, 3576, 3578, 3579, 3580, 3582, 3584, 3585, 3586, 3587, 3588, 3589,
180-
3590, 3591, 3592, 3593, 3594, 3597, 3598, 3599, 3600, 3601, 3604, 3605, 3609, 3611, 3612, 3613, 3614, 3615, 3617, 3618, 3619, 3620, 3621, 3624,
181-
3626, 3627, 3628, 3629, 3630, 3633, 3634, 3635, 3636, 3637, 3638, 3639, 3640, 3642, 3643, 3644, 3645, 3646, 3648, 3649, 3650, 3651, 3652, 3653,
182-
3654, 3655, 3657, 3658, 3659, 3660, 3661, 3664, 3665, 3666, 3669, 3670, 3671, 3673, 3674, 3675, 3676, 3677, 3679, 3680, 3681, 3684, 3685, 3686,
183-
3689, 3690, 3691, 3693, 3694, 3695, 3698, 3699, 3700, 3702, 3703, 3704, 3705, 3706, 3708, 3709, 3710, 3711, 3713, 3714, 3715, 3716, 3717, 3719,
184-
3720, 3721, 3725, 3729, 3730, 3734, 3735, 3744, 3746, 3749, 3758, 3763, 3773, 3774, 3775, 3776, 3777, 3778, 3779, 3780, 3781, 3782, 3783, 3784,
185-
3785, 3786, 3787, 3788, 3789, 3790, 3791, 3792, 3793, 3794, 3795, 3796, 3797, 3798, 3799
177+
3503, 3504, 3505, 3507, 3508, 3509, 3510, 3513, 3514, 3515, 3516, 3517, 3518, 3519, 3521, 3523, 3524, 3525, 3527, 3528, 3529, 3530, 3532, 3533,
178+
3534, 3536, 3537, 3538, 3539, 3541, 3542, 3543, 3544, 3545, 3546, 3547, 3548, 3550, 3551, 3552, 3553, 3554, 3556, 3557, 3558, 3559, 3560, 3561,
179+
3562, 3563, 3564, 3566, 3567, 3568, 3569, 3570, 3572, 3573, 3574, 3575, 3576, 3578, 3579, 3580, 3582, 3584, 3585, 3586, 3587, 3588, 3589, 3590,
180+
3591, 3592, 3593, 3594, 3597, 3598, 3599, 3600, 3601, 3604, 3605, 3609, 3611, 3612, 3613, 3614, 3615, 3617, 3618, 3619, 3620, 3621, 3624, 3626,
181+
3627, 3628, 3629, 3630, 3633, 3634, 3635, 3636, 3637, 3638, 3639, 3640, 3642, 3643, 3644, 3645, 3646, 3648, 3649, 3650, 3651, 3652, 3653, 3654,
182+
3655, 3657, 3658, 3659, 3660, 3661, 3664, 3665, 3666, 3669, 3670, 3671, 3673, 3674, 3675, 3676, 3677, 3679, 3680, 3681, 3684, 3685, 3686, 3689,
183+
3690, 3691, 3693, 3694, 3695, 3698, 3699, 3700, 3704, 3706, 3710, 3711, 3715, 3716, 3717, 3721, 3725, 3729, 3730, 3734, 3735, 3744, 3746, 3749,
184+
3758, 3763, 3773, 3774, 3775, 3776, 3777, 3778, 3779, 3780, 3781, 3782, 3783, 3784, 3785, 3786, 3787, 3788, 3789, 3790, 3791, 3792, 3793, 3794,
185+
3795, 3796, 3797, 3798, 3799
186186
],
187187
"premium": [
188188
27, 156, 157, 158, 159, 161, 163, 170, 186, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 259, 261, 265, 266, 267, 269,

explanations/3531/en.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
## Explanation
2+
3+
### Strategy (The "Why")
4+
5+
**Restate the problem:** We need to count buildings that are "covered", meaning they have at least one building in all four directions: above (same x, smaller y), below (same x, larger y), left (same y, smaller x), and right (same y, larger x).
6+
7+
**1.1 Constraints & Complexity:**
8+
9+
* **Input Size:** n can be up to 10^5, and we can have up to 10^5 buildings.
10+
* **Time Complexity:** O(n log n) - We group and sort buildings by x and y coordinates, then check each building once.
11+
* **Space Complexity:** O(n) - We store buildings grouped by x and y coordinates.
12+
* **Edge Case:** If a building is at the edge (e.g., x=1 or y=1), it cannot have buildings in all directions.
13+
14+
**1.2 High-level approach:**
15+
16+
The goal is to efficiently check for each building whether it has neighbors in all four directions. We group buildings by their x and y coordinates, then sort each group to quickly check for the existence of buildings in each direction.
17+
18+
19+
**1.3 Brute force vs. optimized strategy:**
20+
21+
* **Brute Force:** For each building, check all other buildings to see if any exist in each of the four directions. This is O(n^2) time.
22+
* **Optimized (Grouping + Sorting):** Group buildings by x and y coordinates, then sort each group. For each building, check if its group has any element smaller/larger than it. This is O(n log n) time.
23+
* **Why it's better:** By grouping and sorting, we avoid checking all pairs and can determine existence in each direction by checking the first/last element of sorted groups.
24+
25+
**1.4 Decomposition:**
26+
27+
1. Group buildings by x-coordinate and y-coordinate into separate dictionaries.
28+
2. Sort each group (by y for x-groups, by x for y-groups).
29+
3. For each building, check if its x-group has buildings above (smaller y) and below (larger y).
30+
4. Check if its y-group has buildings left (smaller x) and right (larger x).
31+
5. Count buildings that have neighbors in all four directions.
32+
33+
### Steps (The "How")
34+
35+
**2.1 Initialization & Example Setup:**
36+
37+
Let's use the example: `n = 3`, `buildings = [[1,2],[2,2],[3,2],[2,1],[2,3]]`
38+
39+
We initialize:
40+
* `by_x = {1: [2], 2: [2,1,3], 3: [2]}` (grouped by x, sorted by y)
41+
* `by_y = {1: [2], 2: [1,2,3], 3: [2]}` (grouped by y, sorted by x)
42+
* `res = 0`
43+
44+
**2.2 Start Checking:**
45+
46+
We check each building to see if it's covered.
47+
48+
**2.3 Trace Walkthrough:**
49+
50+
| Step | Building | x-group | y-group | Above? | Below? | Left? | Right? | Covered? |
51+
| ---- | -------- | ------- | ------- | ------------- | ------------- | ------------- | ------------- | -------- |
52+
| 1 | [1,2] | [2] | [1,2,3] | No (min=2) | No (max=2) | No (min=1) | Yes (max=3) | No |
53+
| 2 | [2,2] | [1,2,3] | [1,2,3] | Yes (min=1<2) | Yes (max=3>2) | Yes (min=1<2) | Yes (max=3>2) | Yes |
54+
| 3 | [3,2] | [2] | [1,2,3] | No (min=2) | No (max=2) | Yes (min=1<3) | No (max=3) | No |
55+
| 4 | [2,1] | [1,2,3] | [2] | No (min=1) | Yes (max=3>1) | No (min=2) | No (max=2) | No |
56+
| 5 | [2,3] | [1,2,3] | [2] | Yes (min=1<3) | No (max=3) | No (min=2) | No (max=2) | No |
57+
58+
At step 2, building [2,2] is covered because it has buildings in all four directions.
59+
60+
**2.4 Increment and Loop:**
61+
62+
For each building, we check its groups to determine if it has neighbors in all directions.
63+
64+
**2.5 Return Result:**
65+
66+
After checking all buildings, we return `res = 1`, representing the number of covered buildings.

explanations/3702/en.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
## Explanation
2+
3+
### Strategy (The "Why")
4+
5+
**Restate the problem:** We need to find the length of the longest subsequence whose bitwise XOR is non-zero. A subsequence can be formed by removing zero or more elements (not necessarily contiguous).
6+
7+
**1.1 Constraints & Complexity:**
8+
9+
* **Input Size:** The array `nums` can have up to 10^5 elements, with values between 0 and 10^9.
10+
* **Time Complexity:** O(n) - We calculate the XOR of the entire array once, then check each element in worst case.
11+
* **Space Complexity:** O(1) - We only use a constant amount of extra space.
12+
* **Edge Case:** If all elements are 0, the XOR of any subsequence is 0, so we return 0.
13+
14+
**1.2 High-level approach:**
15+
16+
The goal is to find the longest subsequence with non-zero XOR. If the entire array has non-zero XOR, we can take all elements. Otherwise, we need to remove at least one element to make the XOR non-zero.
17+
18+
19+
**1.3 Brute force vs. optimized strategy:**
20+
21+
* **Brute Force:** Try all possible subsequences and calculate their XOR. This is O(2^n) which is infeasible.
22+
* **Optimized (XOR Property):** Use the property that XOR of entire array XOR an element gives XOR of array without that element. If total XOR is non-zero, take all elements. Otherwise, try removing each element. This is O(n) time.
23+
* **Why it's better:** By using XOR properties, we avoid checking all subsequences and can determine the answer in linear time.
24+
25+
**1.4 Decomposition:**
26+
27+
1. Calculate the XOR of the entire array.
28+
2. If the total XOR is non-zero, return n (entire array is valid).
29+
3. If the total XOR is zero, try removing each element and check if the XOR becomes non-zero.
30+
4. If removing any element makes XOR non-zero, return n-1.
31+
5. Otherwise (all elements are 0), return 0.
32+
33+
### Steps (The "How")
34+
35+
**2.1 Initialization & Example Setup:**
36+
37+
Let's use the example: `nums = [1, 2, 3]`
38+
39+
We initialize:
40+
* `n = 3`
41+
* `total_xor = 0`
42+
43+
**2.2 Start Checking:**
44+
45+
We calculate the XOR of all elements: `total_xor = 1 XOR 2 XOR 3 = 0`.
46+
47+
**2.3 Trace Walkthrough:**
48+
49+
| Step | Action | total_xor | Result |
50+
| ---- | ------------------- | ----------------- | --------------------- |
51+
| 1 | Calculate total XOR | 1 XOR 2 XOR 3 = 0 | - |
52+
| 2 | Check if non-zero | 0 (zero) | Try removing elements |
53+
| 3 | Remove nums[0]=1 | 0 XOR 1 = 1 | Found! Return 2 |
54+
55+
At step 3, we find that removing element 1 gives XOR = 1 (non-zero), so we return n-1 = 2.
56+
57+
**2.4 Increment and Loop:**
58+
59+
If total XOR is zero, we iterate through each element and check if removing it makes XOR non-zero.
60+
61+
**2.5 Return Result:**
62+
63+
After checking, we return `res = 2`, representing the length of the longest subsequence with non-zero XOR (e.g., [2, 3] with XOR = 1).

explanations/3703/en.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
## Explanation
2+
3+
### Strategy (The "Why")
4+
5+
**Restate the problem:** We need to repeatedly remove all non-overlapping k-balanced substrings from a string. A k-balanced substring is exactly k consecutive '(' followed by k consecutive ')'. We continue until no more removals are possible.
6+
7+
**1.1 Constraints & Complexity:**
8+
9+
* **Input Size:** The string `s` can have up to 10^5 characters, consisting only of '(' and ')', with 1 <= k <= s.length/2.
10+
* **Time Complexity:** O(n) - We process each character once, and stack operations are O(1) amortized.
11+
* **Space Complexity:** O(n) - In the worst case, the stack stores all characters.
12+
* **Edge Case:** If k=1 and s="()", the entire string is removed, returning empty string.
13+
14+
**1.2 High-level approach:**
15+
16+
The goal is to use a stack-like structure that groups consecutive characters. When we see k closing parentheses following at least k opening parentheses, we remove them. This simulates the repeated removal process efficiently.
17+
18+
19+
**1.3 Brute force vs. optimized strategy:**
20+
21+
* **Brute Force:** Repeatedly scan the string to find and remove k-balanced substrings until no more can be found. This is O(n^2) or worse in the worst case.
22+
* **Optimized (Stack with Run-Length Encoding):** Group consecutive characters together. When we have a group of '(' with count >= k followed by a group of ')' with count == k, remove k from '(' and remove the entire ')' group. This is O(n) time.
23+
* **Why it's better:** By grouping consecutive characters, we avoid repeatedly scanning the string and can process removals in a single pass.
24+
25+
**1.4 Decomposition:**
26+
27+
1. Use a stack to store [character, count] pairs representing consecutive character groups.
28+
2. Process each character: if it matches the last group, increment count; otherwise, add a new group.
29+
3. After adding each character, check if the last two groups form a k-balanced substring.
30+
4. If yes, remove k from the '(' group and remove the ')' group entirely.
31+
5. Reconstruct the final string from remaining groups.
32+
33+
### Steps (The "How")
34+
35+
**2.1 Initialization & Example Setup:**
36+
37+
Let's use the example: `s = "(())"`, `k = 1`
38+
39+
We initialize:
40+
* `st = []` (stack of [character, count] pairs)
41+
42+
**2.2 Start Checking:**
43+
44+
We process each character in the string, grouping consecutive characters.
45+
46+
**2.3 Trace Walkthrough:**
47+
48+
| Step | char | st before | st after | Action |
49+
| ---- | ---- | --------- | ----------------- | ----------------------------------------------------- |
50+
| 1 | '(' | [] | [['(',1]] | Add new group |
51+
| 2 | '(' | [['(',1]] | [['(',2]] | Increment count |
52+
| 3 | ')' | [['(',2]] | [['(',2],[')',1]] | Add new group, check: remove k=1 from '(', remove ')' |
53+
| 4 | ')' | [['(',1]] | [['(',1],[')',1]] | Add new group, check: remove k=1 from '(', remove ')' |
54+
55+
After step 4, the stack is empty, so the result is "".
56+
57+
**2.4 Increment and Loop:**
58+
59+
For each character, we update the stack and check for k-balanced substrings, removing them immediately.
60+
61+
**2.5 Return Result:**
62+
63+
After processing all characters, we reconstruct the string from remaining groups. In this example, we return `""` since all characters were removed.

0 commit comments

Comments
 (0)