|
| 1 | +from typing import List |
| 2 | +from collections import Counter |
| 3 | +from heapq import heapify, heappop, heappush |
| 4 | + |
| 5 | + |
| 6 | +def least_intervals_mathematical(tasks: List[str], n: int) -> int: |
| 7 | + """ |
| 8 | + The time complexity of is O(N), where N is the number of tasks: |
| 9 | + Counter(tasks) takes O(N) time. |
| 10 | + The rest of the calculation is constant time, O(1), because the number of unique tasks (A-Z) is fixed at 26, meaning |
| 11 | + task_count.values() is at most 26 elements long. |
| 12 | +
|
| 13 | + This greedy approach, which relies on the mathematical structure imposed by the most frequent task, is the most |
| 14 | + efficient way to solve the Task Scheduler problem. |
| 15 | + """ |
| 16 | + # get the total number of tasks |
| 17 | + total_tasks = len(tasks) |
| 18 | + |
| 19 | + # Step 1: Use Counter to get all frequencies |
| 20 | + task_count = Counter(tasks) |
| 21 | + if not task_count: |
| 22 | + return 0 |
| 23 | + |
| 24 | + # most_common() returns a list of (task, frequency) tuples |
| 25 | + # most_common(1) gives the highest frequency task: [('A', 3)] -> max_freq = 3 |
| 26 | + max_freq = task_count.most_common(1)[0][1] |
| 27 | + |
| 28 | + # Step 2: Calculate how many tasks share the max frequency |
| 29 | + # This is slightly more efficient than looping through the full dictionary |
| 30 | + # because it stops checking once frequencies drop below max_freq. |
| 31 | + max_freq_count = sum(1 for freq in task_count.values() if freq == max_freq) |
| 32 | + |
| 33 | + # Step 3: Calculate the least number of intervals |
| 34 | + result = (max_freq - 1) * (n + 1) + max_freq_count |
| 35 | + |
| 36 | + # Step 4: Return the maximum of result and total tasks |
| 37 | + return max(result, total_tasks) |
| 38 | + |
| 39 | + |
| 40 | +def least_intervals_with_max_heap(tasks: List[str], n: int) -> int: |
| 41 | + """ |
| 42 | + Calculates the minimum CPU intervals required using an iterative Max Heap approach. |
| 43 | +
|
| 44 | + This Max Heap solution is slightly less performant than the mathematical one, but it explicitly models the idle time |
| 45 | + (slots where max_heap is empty mid-cycle). |
| 46 | +
|
| 47 | + Max Heap Complexity: O(T⋅KlogK), where T is the total number of intervals (can be up to N⋅n), and K is the number |
| 48 | + of unique tasks (at most 26). |
| 49 | + """ |
| 50 | + # 1. Count Frequencies |
| 51 | + task_count = Counter(tasks) |
| 52 | + if not task_count: |
| 53 | + return 0 |
| 54 | + |
| 55 | + time = 0 |
| 56 | + # 2. Setup Max Heap (store negative frequencies for a Python Min Heap) |
| 57 | + # The task with the highest frequency will have the smallest negative value (e.g., -3). |
| 58 | + max_heap = [-freq for freq in task_count.values()] |
| 59 | + heapify(max_heap) |
| 60 | + |
| 61 | + # Loop continues until all tasks are executed (heap is empty) |
| 62 | + while max_heap: |
| 63 | + temp_list = [] |
| 64 | + cycle_len = 0 |
| 65 | + |
| 66 | + # Execute tasks for one cooling cycle (n + 1 slots) |
| 67 | + # We process up to n + 1 tasks, always picking the highest frequency one available. |
| 68 | + for _ in range(n + 1): |
| 69 | + if max_heap: |
| 70 | + # Pop the highest frequency task |
| 71 | + frequency = -heappop(max_heap) |
| 72 | + |
| 73 | + if frequency > 1: |
| 74 | + # If the task needs to run again, store the remaining count |
| 75 | + temp_list.append(frequency - 1) |
| 76 | + |
| 77 | + cycle_len += 1 |
| 78 | + else: |
| 79 | + # Stop iterating slots if the heap is empty, though time will still advance. |
| 80 | + break |
| 81 | + |
| 82 | + # 3. Re-insert Tasks |
| 83 | + # Push the remaining counts back onto the heap (as negative values) |
| 84 | + for freq in temp_list: |
| 85 | + heappush(max_heap, -freq) |
| 86 | + |
| 87 | + # 4. Time Calculation |
| 88 | + if max_heap: |
| 89 | + # If the heap is NOT empty, it means there are still tasks remaining. |
| 90 | + # We must wait for the full cooling period, so we advance time by n + 1. |
| 91 | + time += n + 1 |
| 92 | + else: |
| 93 | + # If the heap IS empty, it means all tasks were completed in this cycle. |
| 94 | + # We only count the intervals actually used (cycle_len). |
| 95 | + time += cycle_len |
| 96 | + |
| 97 | + return time |
0 commit comments