Skip to content

Commit 066b0ae

Browse files
authored
Merge pull request #16 from Alonza0314/2025/05/23
2025/05/23
2 parents 62be285 + a287c07 commit 066b0ae

10 files changed

+629
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# 2071. Maximum Number of Tasks You Can Assign
2+
3+
## Intuition
4+
5+
The problem asks us to find the maximum number of tasks we can assign to workers, where each worker can complete at most one task. Workers have different strengths, tasks have different requirements, and we have a limited number of pills that can boost a worker's strength by a fixed amount.
6+
7+
The key insight is that this is an optimization problem where we want to maximize the number of completed tasks. We can use binary search on the answer - if we can complete k tasks, we might be able to complete k+1 tasks, but if we can't complete k tasks, we definitely can't complete more than k tasks.
8+
9+
## Approach
10+
11+
1. **Sort both arrays**: Sort tasks in ascending order and workers in descending order of their strength.
12+
2. **Binary search on the answer**: Use binary search to find the maximum number of tasks we can complete. The search space is from 0 to min(len(tasks), len(workers)).
13+
3. **Greedy assignment strategy**: For a given number k of tasks to complete:
14+
- Take the k easiest tasks (first k tasks after sorting)
15+
- Take the k strongest workers (last k workers after sorting)
16+
- Process tasks from hardest to easiest among the selected k tasks
17+
- For each task, try to assign it using the greedy strategy:
18+
- First, try to assign the strongest available worker without using a pill
19+
- If that's not possible, find the weakest worker who can complete the task with a pill
20+
- If no worker can complete the task even with a pill, return false
21+
4. **Optimization with pills**: When we need to use a pill, we should use it on the weakest possible worker who can complete the task. This preserves stronger workers for harder tasks.
22+
23+
The greedy strategy works because:
24+
25+
- We process harder tasks first, ensuring they get priority for stronger workers
26+
- When using pills, we use them on the weakest capable worker to preserve stronger workers
27+
- This maximizes our chances of completing all k tasks
28+
29+
## Complexity
30+
31+
- Time complexity: O(n log n + m log m + log(min(n,m)) × m log m)
32+
- Space complexity: O(m)
33+
34+
## Keywords
35+
36+
- Binary Search
37+
- Greedy Algorithm
38+
39+
## Code
40+
41+
```go
42+
func maxTaskAssign(tasks []int, workers []int, pills int, strength int) int {
43+
sort.Ints(tasks)
44+
sort.Ints(workers)
45+
46+
canAssign := func(k int) bool {
47+
avail, remain := append(make([]int, 0), workers[len(workers) - k:]...), pills
48+
for i := k - 1; i >= 0; i -= 1 {
49+
require := tasks[i]
50+
if len(avail) > 0 && avail[len(avail) - 1] >= require {
51+
avail = avail[: len(avail) - 1]
52+
} else {
53+
if remain <= 0 {
54+
return false
55+
}
56+
thresold := require - strength
57+
idx := sort.Search(len(avail), func(j int) bool {
58+
return avail[j] >= thresold
59+
})
60+
if idx == len(avail) {
61+
return false
62+
}
63+
avail = append(avail[:idx], avail[idx + 1:]...)
64+
remain -= 1
65+
}
66+
}
67+
return true
68+
}
69+
70+
left, right, complete := 0, min(len(tasks), len(workers)), 0
71+
for left <= right {
72+
mid := (left + right) / 2
73+
if canAssign(mid) {
74+
complete, left = mid, mid + 1
75+
} else {
76+
right = mid - 1
77+
}
78+
}
79+
return complete
80+
}
81+
```

Hard/220 Contains Duplicate III.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# 220. Contains Duplicate III
2+
3+
## Intuition
4+
5+
The problem asks us to find if there are two distinct indices i and j such that:
6+
7+
1. The absolute difference between indices is at most k: |i - j| <= k
8+
2. The absolute difference between values is at most t: |nums[i] - nums[j]| <= t
9+
10+
The key insight is to use a bucket sort approach where we group numbers into buckets of size (t + 1). If two numbers are in the same bucket, their difference is guaranteed to be <= t. We also need to check adjacent buckets since numbers in neighboring buckets might have a difference <= t.
11+
12+
## Approach
13+
14+
1. **Bucket Strategy**: Create buckets of width (t + 1). Numbers in the same bucket will have a difference <= t.
15+
2. **Bucket Index Calculation**:
16+
- For positive numbers: `bucketIdx = num / width`
17+
- For negative numbers: `bucketIdx = (num / width) - 1` (to handle negative division correctly)
18+
3. **Three Checks for Each Number**:
19+
- Same bucket: If current bucket already has a number, return true
20+
- Right adjacent bucket: Check if |num - bucket[bucketIdx + 1]| <= t
21+
- Left adjacent bucket: Check if |num - bucket[bucketIdx - 1]| <= t
22+
4. **Sliding Window**: Maintain at most k+1 elements by removing the element that's k+1 positions behind the current element.
23+
5. **Edge Case Handling**: Special handling for negative numbers to ensure correct bucket assignment.
24+
25+
## Complexity
26+
27+
- Time complexity: O(n)
28+
- Space complexity: O(min(n, k))
29+
30+
## Keywords
31+
32+
- Bucket Sort
33+
- Sliding Window
34+
- Hash Map
35+
36+
## Code
37+
38+
```go
39+
func containsNearbyAlmostDuplicate(nums []int, k int, t int) bool {
40+
abs := func(x int) int {
41+
if x < 0 {
42+
return -x
43+
}
44+
return x
45+
}
46+
47+
bucket, width := make(map[int]int), t + 1
48+
for i, num := range nums {
49+
var bucketIdx int
50+
51+
if num >= 0 {
52+
bucketIdx = num / width
53+
} else {
54+
bucketIdx = (num / width) - 1
55+
}
56+
57+
if _, exist := bucket[bucketIdx]; exist {
58+
return true
59+
}
60+
if n, exist := bucket[bucketIdx + 1]; exist && abs(num - n) < width {
61+
return true
62+
}
63+
if n, exist := bucket[bucketIdx - 1]; exist && abs(num - n) < width {
64+
return true
65+
}
66+
67+
bucket[bucketIdx] = num
68+
69+
if i >= k {
70+
var delBucketIdx int
71+
72+
if nums[i - k] >= 0 {
73+
delBucketIdx = nums[i - k] / width
74+
} else {
75+
delBucketIdx = (nums[i - k] / width) - 1
76+
}
77+
78+
delete(bucket, delBucketIdx)
79+
}
80+
}
81+
return false
82+
}
83+
```

Medium/347 Top K Frequent Elements.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# 347. Top K Frequent Elements
2+
3+
## Intuition
4+
5+
The problem asks us to find the k most frequent elements in an array. The key insight is that we need to:
6+
7+
1. Count the frequency of each element
8+
2. Find the k elements with the highest frequencies
9+
10+
A heap (priority queue) is perfect for this task because it allows us to efficiently maintain the top k elements based on their frequencies.
11+
12+
## Approach
13+
14+
1. **Count Frequencies**: Use a hash map to count the frequency of each element in the input array.
15+
2. **Build Max Heap**: Create a max heap where elements are ordered by their frequencies. We implement a custom heap data structure with:
16+
- A comparator function to define the heap property
17+
- Standard heap operations: Push, Pop, Top
18+
- Heap maintenance through up-heap and down-heap operations
19+
3. **Extract Top K**: Push all unique elements with their frequencies into the heap, then pop k times to get the k most frequent elements.
20+
21+
The custom heap implementation uses 1-indexed storage for easier parent-child calculations:
22+
23+
- Parent of node i: i/2
24+
- Left child of node i: 2*i
25+
- Right child of node i: 2*i+1
26+
27+
## Complexity
28+
29+
- Time complexity: O(n log n)
30+
- Space complexity: O(n)
31+
32+
## Keywords
33+
34+
- Hash Map
35+
- Heap (Priority Queue)
36+
- Frequency Counting
37+
- Top K Problems
38+
- Custom Data Structure
39+
40+
## Code
41+
42+
```go
43+
type Comparator func(child, parent interface{}) bool
44+
type heap struct {
45+
Storage []interface{}
46+
CmpFunc Comparator
47+
}
48+
49+
func NewHeap(cmpFunc Comparator) *heap {
50+
return &heap {
51+
Storage: append(make([]interface{}, 0), -1),
52+
CmpFunc: cmpFunc,
53+
}
54+
}
55+
56+
func (h *heap) Len() int {
57+
return len(h.Storage) - 1
58+
}
59+
60+
func (h *heap) IsEmpty() bool {
61+
return h.Len() == 0
62+
}
63+
64+
func (h *heap) cmp(child, parent interface{}) bool {
65+
return h.CmpFunc(child, parent)
66+
}
67+
68+
func (h *heap) swap(x, y int) {
69+
h.Storage[x], h.Storage[y] = h.Storage[y], h.Storage[x]
70+
}
71+
72+
func (h *heap) Top() (interface{}, error) {
73+
if h.IsEmpty() {
74+
return nil, errors.New("Heap is empty.")
75+
}
76+
return h.Storage[1], nil
77+
}
78+
79+
func (h *heap) Push(item interface{}) {
80+
h.Storage = append(h.Storage, item)
81+
now := h.Len()
82+
for now / 2 > 0 && !h.cmp(h.Storage[now], h.Storage[now / 2]) {
83+
h.swap(now, now / 2)
84+
now /= 2
85+
}
86+
}
87+
88+
func (h *heap) Pop() (interface{}, error) {
89+
top, err := h.Top()
90+
if err != nil {
91+
return nil, err
92+
}
93+
last := h.Len()
94+
h.swap(1, last)
95+
h.Storage = h.Storage[: last]
96+
now := 1
97+
for now < last {
98+
left, right := 0, 0
99+
if now * 2 < last && !h.cmp(h.Storage[now * 2], h.Storage[now]) {
100+
left = now * 2
101+
}
102+
if now * 2 + 1 < last && !h.cmp(h.Storage[now * 2 + 1], h.Storage[now]) {
103+
right = now * 2 + 1
104+
}
105+
106+
if left == 0 && right == 0 {
107+
break
108+
} else if left != 0 && right == 0 {
109+
h.swap(now, left)
110+
now = left
111+
} else if left == 0 && right != 0 {
112+
h.swap(now, right)
113+
now = right
114+
} else {
115+
if h.cmp(h.Storage[left], h.Storage[right]) {
116+
h.swap(now, right)
117+
now = right
118+
} else {
119+
h.swap(now, left)
120+
now = left
121+
}
122+
}
123+
}
124+
return top, nil
125+
}
126+
127+
func topKFrequent(nums []int, k int) []int {
128+
cnt := make(map[int]int)
129+
for _, num := range nums {
130+
cnt[num] += 1
131+
}
132+
type unit struct {
133+
num, freq int
134+
}
135+
cmpFunc := func(child, parent interface{}) bool {
136+
return child.(unit).freq < parent.(unit).freq
137+
}
138+
hp, ret := NewHeap(cmpFunc), make([]int, 0)
139+
for key, val := range cnt {
140+
hp.Push(unit{key, val})
141+
}
142+
for k > 0 {
143+
top, _ := hp.Pop()
144+
ret = append(ret, top.(unit).num)
145+
k -= 1
146+
}
147+
return ret
148+
}
149+
```

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@
2727
| 140 | Word Break II | [go](/Hard/140%20Word%20Break%20II.md) | H |
2828
| 146 | LRU Cache | [go](/Medium/146%20LRU%20Cache.md) | M |
2929
| 210 | Course Schedule II | [go](/Medium/210%20Course%20Schedule%20II.md) | M |
30+
| 220 | Contains Duplicate III | [go](/Hard/220%20Contains%20Duplicate%20III.md) | H |
3031
| 236 | Lowest Common Ancestor of a Binary Tree | [go](/Medium/236%20Lowest%20Common%20Ancestor%20of%20a%20Binary%20Tree.md) | M |
3132
| 239 | Sliding Window Maximum | [go](/Hard/239%20Sliding%20Window%20Maximum.md) | H |
3233
| 312 | Burst Balloons | [go](/Hard/312%20Burst%20Balloons.md) | H |
3334
| 316 | Remove Duplicate Letters | [go](/Medium/316.%20Remove%20Duplicate%20Letters.md) | M |
3435
| 322 | Coin Change | [go](/Medium/322%20Coin%20Change.md) | M |
36+
| 347 | Top K Frequent Elements | [go](/Medium/347%20Top%20K%20Frequent%20Elements.md) | M |
3537
| 416 | Partition Equal Subset Sum | [go](/Medium/416%20Partition%20Equal%20Subset%20Sum.md) | M |
3638
| 435 | Non-overlapping Interval | [go](/Medium/435%20Non-overlapping%20Intervals.md) | M |
3739
| 460 | LFU Cache | [go](/Hard/460%20LFU%20Cache.md) | H |
@@ -53,6 +55,7 @@
5355
| 948 | Bag of Tokens | [go](/Medium/948%20Bag%20Of%20Tokens.md) | M |
5456
| 1209 | Remove All Adjacent Duplicates in String II | [go](/Medium/1209%20Remove%20All%20Adjacent%20Duplicates%20in%20String%20II.md) | M |
5557
| 1395 | Count Number of Trams | [go](/Medium/1395%20Count%20Number%20of%20Teams.md) | M |
58+
| 2071 | Maximum Number of Tasks You Can Assign | [go](/Hard/2071%20Maximum%20Number%20of%20Tasks%20You%20Can%20Assign.md) | H |
5659
| 2366 | Mimimum Replacements to Sort the Array | [go](/Hard/2366%20Minimum%20Replacements%20to%20Sort%20the%20Array.md) | H |
5760
| 2444 | Count Subarrays With Fixed Bounds | [go](/Hard/2444%20Count%20Subarrays%20With%20Fixed%20Bounds.md) | H |
5861
| 2742 | Paiting the Walls | [go](/Hard/2742%20Painting%20the%20Walls.md) | H |

Week14/2071 AC.png

42.1 KB
Loading

0 commit comments

Comments
 (0)