Skip to content

2025/05/23 #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions Hard/2071 Maximum Number of Tasks You Can Assign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# 2071. Maximum Number of Tasks You Can Assign

## Intuition

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.

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.

## Approach

1. **Sort both arrays**: Sort tasks in ascending order and workers in descending order of their strength.
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)).
3. **Greedy assignment strategy**: For a given number k of tasks to complete:
- Take the k easiest tasks (first k tasks after sorting)
- Take the k strongest workers (last k workers after sorting)
- Process tasks from hardest to easiest among the selected k tasks
- For each task, try to assign it using the greedy strategy:
- First, try to assign the strongest available worker without using a pill
- If that's not possible, find the weakest worker who can complete the task with a pill
- If no worker can complete the task even with a pill, return false
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.

The greedy strategy works because:

- We process harder tasks first, ensuring they get priority for stronger workers
- When using pills, we use them on the weakest capable worker to preserve stronger workers
- This maximizes our chances of completing all k tasks

## Complexity

- Time complexity: O(n log n + m log m + log(min(n,m)) × m log m)
- Space complexity: O(m)

## Keywords

- Binary Search
- Greedy Algorithm

## Code

```go
func maxTaskAssign(tasks []int, workers []int, pills int, strength int) int {
sort.Ints(tasks)
sort.Ints(workers)

canAssign := func(k int) bool {
avail, remain := append(make([]int, 0), workers[len(workers) - k:]...), pills
for i := k - 1; i >= 0; i -= 1 {
require := tasks[i]
if len(avail) > 0 && avail[len(avail) - 1] >= require {
avail = avail[: len(avail) - 1]
} else {
if remain <= 0 {
return false
}
thresold := require - strength
idx := sort.Search(len(avail), func(j int) bool {
return avail[j] >= thresold
})
if idx == len(avail) {
return false
}
avail = append(avail[:idx], avail[idx + 1:]...)
remain -= 1
}
}
return true
}

left, right, complete := 0, min(len(tasks), len(workers)), 0
for left <= right {
mid := (left + right) / 2
if canAssign(mid) {
complete, left = mid, mid + 1
} else {
right = mid - 1
}
}
return complete
}
```
83 changes: 83 additions & 0 deletions Hard/220 Contains Duplicate III.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# 220. Contains Duplicate III

## Intuition

The problem asks us to find if there are two distinct indices i and j such that:

1. The absolute difference between indices is at most k: |i - j| <= k
2. The absolute difference between values is at most t: |nums[i] - nums[j]| <= t

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.

## Approach

1. **Bucket Strategy**: Create buckets of width (t + 1). Numbers in the same bucket will have a difference <= t.
2. **Bucket Index Calculation**:
- For positive numbers: `bucketIdx = num / width`
- For negative numbers: `bucketIdx = (num / width) - 1` (to handle negative division correctly)
3. **Three Checks for Each Number**:
- Same bucket: If current bucket already has a number, return true
- Right adjacent bucket: Check if |num - bucket[bucketIdx + 1]| <= t
- Left adjacent bucket: Check if |num - bucket[bucketIdx - 1]| <= t
4. **Sliding Window**: Maintain at most k+1 elements by removing the element that's k+1 positions behind the current element.
5. **Edge Case Handling**: Special handling for negative numbers to ensure correct bucket assignment.

## Complexity

- Time complexity: O(n)
- Space complexity: O(min(n, k))

## Keywords

- Bucket Sort
- Sliding Window
- Hash Map

## Code

```go
func containsNearbyAlmostDuplicate(nums []int, k int, t int) bool {
abs := func(x int) int {
if x < 0 {
return -x
}
return x
}

bucket, width := make(map[int]int), t + 1
for i, num := range nums {
var bucketIdx int

if num >= 0 {
bucketIdx = num / width
} else {
bucketIdx = (num / width) - 1
}

if _, exist := bucket[bucketIdx]; exist {
return true
}
if n, exist := bucket[bucketIdx + 1]; exist && abs(num - n) < width {
return true
}
if n, exist := bucket[bucketIdx - 1]; exist && abs(num - n) < width {
return true
}

bucket[bucketIdx] = num

if i >= k {
var delBucketIdx int

if nums[i - k] >= 0 {
delBucketIdx = nums[i - k] / width
} else {
delBucketIdx = (nums[i - k] / width) - 1
}

delete(bucket, delBucketIdx)
}
}
return false
}
```
149 changes: 149 additions & 0 deletions Medium/347 Top K Frequent Elements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# 347. Top K Frequent Elements

## Intuition

The problem asks us to find the k most frequent elements in an array. The key insight is that we need to:

1. Count the frequency of each element
2. Find the k elements with the highest frequencies

A heap (priority queue) is perfect for this task because it allows us to efficiently maintain the top k elements based on their frequencies.

## Approach

1. **Count Frequencies**: Use a hash map to count the frequency of each element in the input array.
2. **Build Max Heap**: Create a max heap where elements are ordered by their frequencies. We implement a custom heap data structure with:
- A comparator function to define the heap property
- Standard heap operations: Push, Pop, Top
- Heap maintenance through up-heap and down-heap operations
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.

The custom heap implementation uses 1-indexed storage for easier parent-child calculations:

- Parent of node i: i/2
- Left child of node i: 2*i
- Right child of node i: 2*i+1

## Complexity

- Time complexity: O(n log n)
- Space complexity: O(n)

## Keywords

- Hash Map
- Heap (Priority Queue)
- Frequency Counting
- Top K Problems
- Custom Data Structure

## Code

```go
type Comparator func(child, parent interface{}) bool
type heap struct {
Storage []interface{}
CmpFunc Comparator
}

func NewHeap(cmpFunc Comparator) *heap {
return &heap {
Storage: append(make([]interface{}, 0), -1),
CmpFunc: cmpFunc,
}
}

func (h *heap) Len() int {
return len(h.Storage) - 1
}

func (h *heap) IsEmpty() bool {
return h.Len() == 0
}

func (h *heap) cmp(child, parent interface{}) bool {
return h.CmpFunc(child, parent)
}

func (h *heap) swap(x, y int) {
h.Storage[x], h.Storage[y] = h.Storage[y], h.Storage[x]
}

func (h *heap) Top() (interface{}, error) {
if h.IsEmpty() {
return nil, errors.New("Heap is empty.")
}
return h.Storage[1], nil
}

func (h *heap) Push(item interface{}) {
h.Storage = append(h.Storage, item)
now := h.Len()
for now / 2 > 0 && !h.cmp(h.Storage[now], h.Storage[now / 2]) {
h.swap(now, now / 2)
now /= 2
}
}

func (h *heap) Pop() (interface{}, error) {
top, err := h.Top()
if err != nil {
return nil, err
}
last := h.Len()
h.swap(1, last)
h.Storage = h.Storage[: last]
now := 1
for now < last {
left, right := 0, 0
if now * 2 < last && !h.cmp(h.Storage[now * 2], h.Storage[now]) {
left = now * 2
}
if now * 2 + 1 < last && !h.cmp(h.Storage[now * 2 + 1], h.Storage[now]) {
right = now * 2 + 1
}

if left == 0 && right == 0 {
break
} else if left != 0 && right == 0 {
h.swap(now, left)
now = left
} else if left == 0 && right != 0 {
h.swap(now, right)
now = right
} else {
if h.cmp(h.Storage[left], h.Storage[right]) {
h.swap(now, right)
now = right
} else {
h.swap(now, left)
now = left
}
}
}
return top, nil
}

func topKFrequent(nums []int, k int) []int {
cnt := make(map[int]int)
for _, num := range nums {
cnt[num] += 1
}
type unit struct {
num, freq int
}
cmpFunc := func(child, parent interface{}) bool {
return child.(unit).freq < parent.(unit).freq
}
hp, ret := NewHeap(cmpFunc), make([]int, 0)
for key, val := range cnt {
hp.Push(unit{key, val})
}
for k > 0 {
top, _ := hp.Pop()
ret = append(ret, top.(unit).num)
k -= 1
}
return ret
}
```
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
| 140 | Word Break II | [go](/Hard/140%20Word%20Break%20II.md) | H |
| 146 | LRU Cache | [go](/Medium/146%20LRU%20Cache.md) | M |
| 210 | Course Schedule II | [go](/Medium/210%20Course%20Schedule%20II.md) | M |
| 220 | Contains Duplicate III | [go](/Hard/220%20Contains%20Duplicate%20III.md) | H |
| 236 | Lowest Common Ancestor of a Binary Tree | [go](/Medium/236%20Lowest%20Common%20Ancestor%20of%20a%20Binary%20Tree.md) | M |
| 239 | Sliding Window Maximum | [go](/Hard/239%20Sliding%20Window%20Maximum.md) | H |
| 312 | Burst Balloons | [go](/Hard/312%20Burst%20Balloons.md) | H |
| 316 | Remove Duplicate Letters | [go](/Medium/316.%20Remove%20Duplicate%20Letters.md) | M |
| 322 | Coin Change | [go](/Medium/322%20Coin%20Change.md) | M |
| 347 | Top K Frequent Elements | [go](/Medium/347%20Top%20K%20Frequent%20Elements.md) | M |
| 416 | Partition Equal Subset Sum | [go](/Medium/416%20Partition%20Equal%20Subset%20Sum.md) | M |
| 435 | Non-overlapping Interval | [go](/Medium/435%20Non-overlapping%20Intervals.md) | M |
| 460 | LFU Cache | [go](/Hard/460%20LFU%20Cache.md) | H |
Expand All @@ -53,6 +55,7 @@
| 948 | Bag of Tokens | [go](/Medium/948%20Bag%20Of%20Tokens.md) | M |
| 1209 | Remove All Adjacent Duplicates in String II | [go](/Medium/1209%20Remove%20All%20Adjacent%20Duplicates%20in%20String%20II.md) | M |
| 1395 | Count Number of Trams | [go](/Medium/1395%20Count%20Number%20of%20Teams.md) | M |
| 2071 | Maximum Number of Tasks You Can Assign | [go](/Hard/2071%20Maximum%20Number%20of%20Tasks%20You%20Can%20Assign.md) | H |
| 2366 | Mimimum Replacements to Sort the Array | [go](/Hard/2366%20Minimum%20Replacements%20to%20Sort%20the%20Array.md) | H |
| 2444 | Count Subarrays With Fixed Bounds | [go](/Hard/2444%20Count%20Subarrays%20With%20Fixed%20Bounds.md) | H |
| 2742 | Paiting the Walls | [go](/Hard/2742%20Painting%20the%20Walls.md) | H |
Expand Down
Binary file added Week14/2071 AC.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading