[Leetcode] 3408. Design Task Manager

RexiaN·2025년 9월 18일

태스크 매니저를 만들어야 한다. userId, taskId, priority 세 개의 값이 주어지며 execTop() 메서드를 사용하면 모든 사용자가 가진 작업 중 priority 가 가장 높은 유저의 id 가 반환되어야 한다. 다만, 가장 높은 priority 가 여러 개 라면 가장 높은 taskId 를 가진 유저의 id 가 반환되어야 한다.

이전 문제와 마찬가지로 최대힙을 사용해야 하는 문제. taskId 를 기반으로 추가, 수정, 삭제 연산이 같이 가능해야 한다. 힙은 특정한 자료를 찾아내는 데 최적화되어 있는 자료구조가 아니다. 힙은 가져온 값이 최댓값 또는 최솟값임을 보장하는데 최적화된 자료구조이다.

따라서 별도의 해시맵을 하나 만들어준다. 수정이나 삭제 연산에서는 해시맵만 업데이트하고, execTop() 메서드 안에서 힙에 든 값을 꺼냈을 때 valid 한 값인지 해시맵으로 검증 후 내보내면 된다(힙-해시맵 이 같이 쓰이는 경우는 정말 많다).

class MyMaxHeap {
  private heap: [number, number, number, number][];

  constructor() {
    this.heap = [];
  }

  push(item: [number, number, number, number]): void {
    this.heap.push(item);
    let current = this.heap.length - 1;
    let parent = Math.floor((current - 1) / 2);

    while (
      current > 0 &&
      (this.heap[current][0] > this.heap[parent][0] ||
        (this.heap[current][0] === this.heap[parent][0] &&
          this.heap[current][1] > this.heap[parent][1]))
    ) {
      [this.heap[parent], this.heap[current]] = [
        this.heap[current],
        this.heap[parent],
      ];
      current = parent;
      parent = Math.floor((current - 1) / 2);
    }
  }

  pop(): [number, number, number, number] | undefined {
    if (this.heap.length === 0) {
      return undefined;
    }
    if (this.heap.length === 1) {
      return this.heap.pop();
    }
    const item = this.heap[0];
    this.heap[0] = this.heap.pop()!;
    let current = 0;
    while (true) {
      const leftChild = 2 * current + 1;
      const rightChild = 2 * current + 2;
      let largest = current;

      if (
        leftChild < this.heap.length &&
        (this.heap[leftChild][0] > this.heap[largest][0] ||
          (this.heap[leftChild][0] === this.heap[largest][0] &&
            this.heap[leftChild][1] > this.heap[largest][1]))
      ) {
        largest = leftChild;
      }
      if (
        rightChild < this.heap.length &&
        (this.heap[rightChild][0] > this.heap[largest][0] ||
          (this.heap[rightChild][0] === this.heap[largest][0] &&
            this.heap[rightChild][1] > this.heap[largest][1]))
      ) {
        largest = rightChild;
      }

      if (largest !== current) {
        [this.heap[current], this.heap[largest]] = [
          this.heap[largest],
          this.heap[current],
        ];
        current = largest;
      } else {
        break;
      }
    }
    return item;
  }

  peek(): [number, number, number, number] | undefined {
    return this.heap.length > 0 ? this.heap[0] : undefined;
  }
}

class TaskManager {
  private taskInfo: Map<number, { userId: number; priority: number; timeStamp: number }>;
  private maxHeap: MyMaxHeap;
  private timeStamp: number;

  constructor(tasks: number[][]) {
    this.taskInfo = new Map();
    this.maxHeap = new MyMaxHeap();
    this.timeStamp = 0;

    for (const [userId, taskId, priority] of tasks) {
      this.add(userId, taskId, priority);
    }
  }

  add(userId: number, taskId: number, priority: number): void {
    this.taskInfo.set(taskId, { userId, priority, timeStamp: this.timeStamp });
    this.maxHeap.push([priority, taskId, userId, this.timeStamp]);
    this.timeStamp += 1;
  }

  edit(taskId: number, newPriority: number): void {
    const task = this.taskInfo.get(taskId);
    if (task) {
      task.priority = newPriority;
      this.maxHeap.push([newPriority, taskId, task.userId, task.timeStamp]);
    }
  }

  rmv(taskId: number): void {
    this.taskInfo.delete(taskId);
  }

  execTop(): number {
    if (this.maxHeap.peek() === undefined) {
      return -1;
    }

    while (this.maxHeap.peek() !== undefined) {
      const [priority, taskId, userId, timeStamp] = this.maxHeap.peek()!;
      const latestTaskInfo = this.taskInfo.get(taskId);

      if (
        latestTaskInfo &&
        latestTaskInfo.priority === priority &&
        latestTaskInfo.timeStamp === timeStamp
      ) {
        this.maxHeap.pop();
        this.taskInfo.delete(taskId);
        return userId;
      } else {
        this.maxHeap.pop();
      }
    }

    return -1;
  }
}

컴중간에 부득이하게 timeStamp 라는 값을 추가했는데, 하나의 태스크를 삭제한 뒤 다른 사용자에게 재부여한 경우 힙이 최신상태를 제대로 인지하지 못해서이다.

위의 경우 userId 는 다르지만 어쨌든 한번 삭제를 했었던 taskId 가 다시 해시맵에 추가되어 존재하고 있었고, 해시맵을 기반으로 priority 만 비교하는 연산에서는 userId 업데이트를 알아차릴 수 없기 때문이었다. 실제로 [8, 101, 1], [8, 101, 50] 두 개의 값이 있는 경우 앞의 값이 먼저 나왔다(순서는 priority, taskId, userId).

timeStamp 값을 추가해서 .add() 메서드가 실행될 때 마다 찍히는 타임스탬프를 도입해 정합성 검사를 하나 더 추가해서 해결했다.

profile
Don't forget Rule No.1

0개의 댓글