Facebook Pixel

3408. Design Task Manager

MediumDesignHash TableOrdered SetHeap (Priority Queue)
Leetcode Link

Problem Description

The task management system allows users to administer tasks, each with a priority level. The operations on the task manager include:

  • Initializing the system with a list of user-task-priority triples.
  • Adding tasks to a user with a specified priority.
  • Editing the priority of an existing task.
  • Removing a specified task.
  • Executing the task with the highest priority across all users, with a tie-breaking condition of choosing the task with the highest taskId. After execution, the task is removed, and the function returns the userId associated with the executed task. If no tasks are available, it returns -1.

Intuition

To efficiently manage tasks and their priorities, we need a data structure that maintains tasks sorted by their priorities and allows quick updates for adding, editing, and removing tasks. A combination of a dictionary to map taskIds to their task details, and a sorted list to maintain priorities for execution, provides an optimal solution. The dictionary gives us fast access and modification capabilities for task details, while the sorted list ensures that we can always retrieve the highest priority task in logarithmic time. The sorted list stores negative priorities and taskIds to mimic max-heap behavior, facilitating the retrieval of tasks with higher priorities and resolving ties by favoring higher taskIds.

Learn more about Heap (Priority Queue) patterns.

Solution Approach

The solution involves creating a TaskManager class that utilizes a dictionary and a sorted list to efficiently manage tasks.

  1. Data Structures Used:

    • self.d: A dictionary to store task details with taskId as the key. Each entry maps the taskId to a tuple (userId, priority), allowing quick lookup and modification of tasks.
    • self.st: A sorted list (SortedList) that maintains tasks in a sorted order based on their priority and taskId. By storing tuples as (-priority, -taskId), it mimics max-heap functionality, allowing us to efficiently retrieve tasks with the highest priority and resolve priority ties by using the highest taskId.
  2. Initialization:

    • The constructor, __init__, accepts a list of tasks where each task is described as [userId, taskId, priority]. It populates self.d and self.st using the add method.
  3. Adding a Task:

    • The add method takes userId, taskId, and priority. It updates the dictionary self.d by mapping taskId to (userId, priority).
    • It then adds the tuple (-priority, -taskId) to the sorted list self.st.
  4. Editing a Task's Priority:

    • The edit method changes the priority of an existing task by first removing the old priority entry from self.st.
    • It updates self.d with the new priority and adds the updated task to self.st.
  5. Removing a Task:

    • The rmv method retrieves and removes the task from self.d.
    • It also removes the corresponding entry from self.st.
  6. Executing the Task with Highest Priority:

    • execTop checks if self.st is empty. If so, it returns -1 indicating no tasks are available.
    • If tasks are available, it retrieves and removes the task with the highest priority from self.st.
    • It extracts the taskId, finds the corresponding userId from self.d, removes the task, and returns the userId of the executed task.

The approach ensures that all operations, such as adding, editing, and removing tasks, are efficiently performed while maintaining the necessary task ordering for execution.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a simple example to illustrate the solution approach for the task management system.

Initial Setup

Consider a scenario where the system is initialized with the following list of user-task-priority triples:

  • User 1 with Task 101 and Priority 5
  • User 2 with Task 102 and Priority 3
  • User 3 with Task 103 and Priority 4

We initialize the TaskManager with these tasks. After initialization:

  • The dictionary self.d will have:

    {
      101: (1, 5),  // Task 101 belongs to User 1 with Priority 5
      102: (2, 3),  // Task 102 belongs to User 2 with Priority 3
      103: (3, 4)   // Task 103 belongs to User 3 with Priority 4
    }
  • The sorted list self.st will store tuples:

    [(-5, -101), (-3, -102), (-4, -103)]

Adding a Task

Now, add a new task 104 for User 1 with Priority 6:

  1. Update self.d:

    {
      101: (1, 5),
      102: (2, 3),
      103: (3, 4),
      104: (1, 6)   // New task entry added
    }
  2. Add to self.st:

    [(-6, -104), (-5, -101), (-3, -102), (-4, -103)]

Editing a Task's Priority

Edit Task 102 to have Priority 7:

  1. Remove the old tuple (-3, -102) from self.st.

  2. Update self.d:

    {
      101: (1, 5),
      102: (2, 7),  // Updated Priority for Task 102
      103: (3, 4),
      104: (1, 6)
    }
  3. Add the updated tuple to self.st:

    [(-7, -102), (-6, -104), (-5, -101), (-4, -103)]

Removing a Task

Remove Task 101:

  1. Remove task from self.d:

    {
      102: (2, 7),
      103: (3, 4),
      104: (1, 6)
    }
  2. Remove tuple from self.st:

    [(-7, -102), (-6, -104), (-4, -103)]

Executing the Task with Highest Priority

Execute the task with the highest priority:

  1. Check self.st. The highest priority task is (-7, -102).

  2. Retrieve and remove this task.

  3. Get userId from self.d for Task 102 which is User 2.

  4. Remove Task 102 from self.d:

    {
      103: (3, 4),
      104: (1, 6)
    }
  5. Return the userId: 2

This complete walk-through helps in understanding how tasks are managed using the TaskManager system.

Solution Implementation

1from typing import List
2from sortedcontainers import SortedList
3
4class TaskManager:
5    def __init__(self, tasks: List[List[int]]):
6        # Dictionary to map taskId to a tuple of (userId, priority)
7        self.task_map = {}
8      
9        # Sorted list to efficiently fetch the highest priority task
10        self.sorted_tasks = SortedList()
11      
12        # Add initial tasks
13        for task in tasks:
14            self.add(*task)
15
16    def add(self, userId: int, taskId: int, priority: int) -> None:
17        # Store the task data in the dictionary
18        self.task_map[taskId] = (userId, priority)
19      
20        # Add task to the sorted list using negative values to mimic max-heap
21        self.sorted_tasks.add((-priority, -taskId))
22
23    def edit(self, taskId: int, newPriority: int) -> None:
24        # Fetch the current userId and priority for the task
25        userId, current_priority = self.task_map[taskId]
26      
27        # Remove the task from the sorted list with its old values
28        self.sorted_tasks.discard((-current_priority, -taskId))
29      
30        # Update the task's priority in the dictionary
31        self.task_map[taskId] = (userId, newPriority)
32      
33        # Re-add the task to the sorted list with the new priority
34        self.sorted_tasks.add((-newPriority, -taskId))
35
36    def rmv(self, taskId: int) -> None:
37        # Retrieve the priority of the task to remove
38        _, priority = self.task_map[taskId]
39      
40        # Remove from the dictionary
41        del self.task_map[taskId]
42      
43        # Remove the task entry from the sorted list
44        self.sorted_tasks.remove((-priority, -taskId))
45
46    def execTop(self) -> int:
47        # Return -1 if no tasks are available
48        if not self.sorted_tasks:
49            return -1
50      
51        # Extract the highest priority taskId 
52        top_task_id = -self.sorted_tasks.pop(0)[1]
53      
54        # Retrieve the userId associated with this task
55        userId, _ = self.task_map[top_task_id]
56      
57        # Remove the task from the dictionary
58        del self.task_map[top_task_id]
59      
60        # Return the userId of the executed task
61        return userId
62
63# Your TaskManager object will be instantiated and called as such:
64# obj = TaskManager(tasks)
65# obj.add(userId, taskId, priority)
66# obj.edit(taskId, newPriority)
67# obj.rmv(taskId)
68# user_id_of_executed_task = obj.execTop()
69
1import java.util.*;
2
3class TaskManager {
4    // Map to store taskId as key and an array [userId, priority] as value
5    private final Map<Integer, int[]> taskData = new HashMap<>();
6  
7    // TreeSet to maintain tasks sorted primarily by priority in descending order, 
8    // and by taskId in descending order when priorities are the same
9    private final TreeSet<int[]> taskSet = new TreeSet<>((a, b) -> {
10        if (a[0] == b[0]) {
11            return b[1] - a[1];
12        }
13        return b[0] - a[0];
14    });
15
16    // Constructor to initialize the TaskManager with a list of tasks
17    public TaskManager(List<List<Integer>> tasks) {
18        for (List<Integer> task : tasks) {
19            add(task.get(0), task.get(1), task.get(2));
20        }
21    }
22
23    // Method to add a new task
24    public void add(int userId, int taskId, int priority) {
25        taskData.put(taskId, new int[] {userId, priority});
26        taskSet.add(new int[] {priority, taskId});
27    }
28
29    // Method to edit the priority of an existing task
30    public void edit(int taskId, int newPriority) {
31        int[] taskDetails = taskData.get(taskId); // Get the current details for the task
32        int userId = taskDetails[0];
33        int oldPriority = taskDetails[1];
34
35        // Remove the old entry and add the updated one
36        taskSet.remove(new int[] {oldPriority, taskId});
37        taskSet.add(new int[] {newPriority, taskId});
38      
39        // Update the task details in the map
40        taskData.put(taskId, new int[] {userId, newPriority});
41    }
42
43    // Method to remove a task from the manager
44    public void rmv(int taskId) {
45        int[] taskDetails = taskData.remove(taskId); // Retrieve and remove task data
46        int priority = taskDetails[1];
47
48        // Remove the task from the TreeSet
49        taskSet.remove(new int[] {priority, taskId});
50    }
51
52    // Method to execute and remove the top-priority task
53    public int execTop() {
54        if (taskSet.isEmpty()) {
55            return -1; // Return -1 if no tasks are available
56        }
57        int[] topTask = taskSet.pollFirst(); // Retrieve and remove the highest-priority task
58        int[] taskDetails = taskData.remove(topTask[1]); // Remove task from map as well
59        return taskDetails[0]; // Return the userId associated with the executed task
60    }
61}
62
63/**
64 * Your TaskManager object will be instantiated and called as such:
65 * TaskManager obj = new TaskManager(tasks);
66 * obj.add(userId,taskId,priority);
67 * obj.edit(taskId,newPriority);
68 * obj.rmv(taskId);
69 * int param_4 = obj.execTop();
70 */
71
1#include <unordered_map>
2#include <set>
3#include <vector>
4
5using namespace std;
6
7class TaskManager {
8private:
9    // Map to store information about tasks
10    // Key: taskId, Value: pair of userId and priority
11    unordered_map<int, pair<int, int>> taskData;
12
13    // Set to maintain tasks ordered by priority and taskId
14    // The use of negative values is to simulate a max-heap behavior with a set
15    set<pair<int, int>> sortedTasks;
16
17public:
18    // Constructor that initializes the TaskManager with the given tasks
19    TaskManager(vector<vector<int>>& tasks) {
20        // Add each task from the input vector to the system
21        for (const auto& task : tasks) {
22            add(task[0], task[1], task[2]);  // userId, taskId, priority
23        }
24    }
25
26    // Method to add a new task to the system
27    void add(int userId, int taskId, int priority) {
28        // Store task details in the map
29        taskData[taskId] = {userId, priority};
30        // Insert task into the set, using negative priority and taskId for sorting
31        sortedTasks.insert({-priority, -taskId});
32    }
33
34    // Method to edit an existing task's priority
35    void edit(int taskId, int newPriority) {
36        auto [userId, oldPriority] = taskData[taskId];
37        // Remove old task entry from the set
38        sortedTasks.erase({-oldPriority, -taskId});
39        // Insert updated task entry into the set
40        sortedTasks.insert({-newPriority, -taskId});
41        // Update task details in the map
42        taskData[taskId] = {userId, newPriority};
43    }
44
45    // Method to remove a task from the system
46    void rmv(int taskId) {
47        auto [userId, priority] = taskData[taskId];
48        // Remove task entry from the set
49        sortedTasks.erase({-priority, -taskId});
50        // Erase task details from the map
51        taskData.erase(taskId);
52    }
53
54    // Method to execute the task with the highest priority
55    int execTop() {
56        // Check if there are any tasks to execute
57        if (sortedTasks.empty()) {
58            return -1;  // Return -1 if no tasks are available
59        }
60        // Get the task with the highest priority
61        auto topTask = *sortedTasks.begin();
62        sortedTasks.erase(sortedTasks.begin());
63        int taskId = -topTask.second;
64        int userId = taskData[taskId].first;
65        // Remove executed task from the map
66        taskData.erase(taskId);
67        // Return the userId associated with the executed task
68        return userId;
69    }
70};
71
72/**
73 * Your TaskManager object will be instantiated and called as such:
74 * TaskManager* obj = new TaskManager(tasks);
75 * obj->add(userId,taskId,priority);
76 * obj->edit(taskId,newPriority);
77 * obj->rmv(taskId);
78 * int result = obj->execTop();
79 */
80
1type TaskData = {
2    userId: number;
3    priority: number;
4};
5
6// Map to store information about tasks
7// Key: taskId, Value: pair of userId and priority
8const taskData: Map<number, TaskData> = new Map();
9
10// Set to maintain tasks ordered by priority and taskId
11// Using a collection of arrays to simulate sorting by negative priority and negative taskId
12const sortedTasks: Set<[number, number]> = new Set();
13
14// Initialize the task manager with the given list of tasks
15function taskManager(tasks: number[][]): void {
16    // Add each task from the input array to the system
17    tasks.forEach(task => {
18        add(task[0], task[1], task[2]);  // userId, taskId, priority
19    });
20}
21
22// Add a new task to the system
23function add(userId: number, taskId: number, priority: number): void {
24    // Store task details in the map
25    taskData.set(taskId, { userId, priority });
26    // Insert task into the set, using negative priority and taskId for sorting
27    sortedTasks.add([-priority, -taskId]);
28}
29
30// Edit an existing task's priority
31function edit(taskId: number, newPriority: number): void {
32    const taskInfo = taskData.get(taskId);
33    if (taskInfo) {
34        const { userId, priority } = taskInfo;
35        // Remove old task entry from the set
36        sortedTasks.delete([-priority, -taskId]);
37        // Insert updated task entry into the set
38        sortedTasks.add([-newPriority, -taskId]);
39        // Update task details in the map
40        taskData.set(taskId, { userId, newPriority });
41    }
42}
43
44// Remove a task from the system
45function rmv(taskId: number): void {
46    const taskInfo = taskData.get(taskId);
47    if (taskInfo) {
48        const { priority } = taskInfo;
49        // Remove task entry from the set
50        sortedTasks.delete([-priority, -taskId]);
51        // Erase task details from the map
52        taskData.delete(taskId);
53    }
54}
55
56// Execute the task with the highest priority
57function execTop(): number {
58    // Check if there are any tasks to execute
59    if (sortedTasks.size === 0) {
60        return -1;  // Return -1 if no tasks are available
61    }
62    // Get the task with the highest priority
63    const topTask = Array.from(sortedTasks)[0];
64    sortedTasks.delete(topTask);
65    const taskId = -topTask[1];
66    const userId = taskData.get(taskId)?.userId || -1;
67    // Remove executed task from the map
68    taskData.delete(taskId);
69    // Return the userId associated with the executed task
70    return userId;
71}
72
73// Example usage to demonstrate how to call these functions
74const tasks = [
75    [1, 100, 5],
76    [2, 200, 10],
77];
78
79taskManager(tasks);
80add(3, 300, 8);
81console.log(edit(200, 7));
82console.log(rmv(100));
83console.log(execTop());
84

Time and Space Complexity

  • Initialization (__init__ method):

    • Time Complexity: O(n log n), where n is the number of tasks. Each task insertion in the SortedList takes O(log n).
    • Space Complexity: O(n), as both the dictionary d and the SortedList st store up to n tasks.
  • Add Task (add method):

    • Time Complexity: O(log n) for adding an entry to SortedList and updating the dictionary.
    • Space Complexity: O(1), as no additional space beyond updates to existing structures is used.
  • Edit Task (edit method):

    • Time Complexity: O(log n). Removing and adding elements to SortedList both take O(log n).
    • Space Complexity: O(1), with updates taking place in-place.
  • Remove Task (rmv method):

    • Time Complexity: O(log n), for removing an entry from SortedList and dictionary.
    • Space Complexity: O(1), since again only updates to existing structures are made.
  • Execute Top Task (execTop method):

    • Time Complexity: O(log n), due to popping the first element of SortedList.
    • Space Complexity: O(1), with no additional space requirements beyond updates.

In summary, the overall essence is that operations are efficiently managed with logarithmic time complexities thanks to the SortedList, while space complexity remains linear within the number of tasks.

Learn more about how to find time and space complexity quickly.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

Which of the following is a good use case for backtracking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!


Load More