【路飞】算法-任务调度器

351 阅读3分钟

记录一道算法题

任务调度器

621. 任务调度器 - 力扣(LeetCode) (leetcode-cn.com)


要求:

    1. 每个任务执行一个单位时间
    2. 同一个任务相隔 n 个单位
    3. 求最短时间

先举些例子模拟一下。摸索一下流程,下面会提供两个解法,一个是暴力求解,一个是运用了规律。

  • ["A","A","A","B","B","B","C","C"], n = 2

      如果要执行的话就是 A -> B -> C -> A -> B -> C -> A -> BA和下一个A之间至少间隔两个任务。
    
  • 如果是 ["A","A","A","B","B","B"], n = 2

      执行顺序就是 A -> B ->-> A -> B ->-> A -> B
    
  • 如果是 ["A","A","A","B","B","B","C","C", "D", "E"], n = 2

      执行顺序就是 A -> B -> C -> D -> A -> B -> C -> E -> A -> B
    

基本上是从次数高到次数低依次执行。

首先先算次数吧

    function leastInterval(tasks, n) {
        const countObj = tasks.reduce((total, curr) => {
            if (total[curr]) {
                total[curr]++
            } else {
                total[curr] = 1
            }
        }, {})
        
        // 因为是优先用掉次数多的,所以排序一下方便后续操作
        const arr = Object.values(countObj).sort((a, b) => b - a)
    }

假设是 ["A","A","A","B","B","B","C","C"], n = 2, 这时已经获取到了各个种类数量的数组 [3, 3, 2], 接下来观察 n 和次数的关系。转化成立体图的话如下;

     A B
     A B C
     A B C

我们会发现任务的周期是 n + 1, 这里就是3个一个周期。接下来可以写一个循环进行处理。

    // arr = [3, 3, 2]
    let count = 0
    for(let i = 0; i < n + 1; n++) {
        arr[i]--
        count++
    }

这样数组的前3个任务就完成了记录,但是还需要考虑像上面 最后是 A -> B, 只执行两个任务,所以这个循环还需要加上条件判断。需要有值才可以计数。(注意个细节,先计数再减)

    // arr = [1, 1, 0]
    let count = 0
    for(let i = 0; i < n + 1; n++) {
        if (arr[i]) {
            count++
        }
        arr[i]--
    }

当种类多起来的时候,我们需要让它顺序执行。[3, 2, 1, 1, 1], n = 2, 很明显需要用一个循环再包一层,那循环的终止条件是什么。

    let count = 0
    while(?) {
        for(let i = 0; i < n + 1; n++) {
            if (arr[i]) {
                count++
            }
            arr[i]--
        }
    }
     A B
     A B
     A A C
     
     任务是有间隔的,在间隔是如果不执行其他任务的话,那就是要等待
     所以至少是 最高次数的 A 执行完。任务调度才结束。所以当 A 的次数
     是 0 的时候,就可以终止了。
     
     同时因为是每次处理前3个,所以还需要在每个周期结束时进行重新排序。
     因为是暴力解法,所以就不考虑性能了。
     
     但是有一个小坑,[1, 0, 0] 时,我们会发现arr只计算了一次,实际上需要3次,
     而且不能用arr[0], 因为当[1,1,0] 时只计算一次.
     
    let count = 0
    while(arr[0]) {
        for(let i = 0; i < n + 1; n++) {
            if (arr.filter(_ => _ > 0).length) {
                count++
            }
            arr[i]--
        }
        
        arr = arr.sort((a, b) => b - a)
    }

这样次数就收集好了。暴力解法完整的代码如下

   function leastInterval(tasks, n) {
       const obj = tasks.reduce((total, curr) => {
         if (total[curr]) {
           total[curr]++
         } else {
           total[curr] = 1
         }

         return total
       }, {})

       let arr = Object.values(obj).sort((a, b) => b - a)
       // 获取最大次数
       const max = arr[0]
       // 后面的 n 都是用 n+1 所以这里重新赋值
       n = n + 1
       
       // 需要保证长度大于等于 n
       // 手动填充等待任务
       if (arr.length < n) {
         while (arr.length !== n) {
           arr.push(max - 1)
         }
       }

       let count = 0
       while (arr[0]) {
         for (let i = 0; i < n; i++) {
           if (arr.filter(_ => _ > 0).length) {
             count++
           }

           arr[i]--
         }

         arr = arr.sort((a, b) => b - a)
       }

       return count
     }

其实规律已经基本上出来了。

  1. 当有等待任务的时候,至少执行 (max - 1) * n 次,然后加上最高次数的个数

     A B
     A B C
     A B C
       
     可以分成
     A B
     -----下面是必定的个数
     A B C
     A B C
     
    
  2. 当然也可能没有等待任务,我们会用其他任务进行填充

     A
     A B
     A B C D E
     
     可以变成
     
     A E
     ----依然可以拆
     A B D
     A B C 
     
    
  3. 有可能填充之后长度超出了 n + 1

     A E F
     A B D H
     A B C G
     
     这时候,其实是任务不需要等待, 相同任务之间可以隔着 n+1 或以上数量的任务
     也就是刚好等于任务的总数。 tasks.length
     
     
    

所以是填充了之后分为 (max - 1) * n + maxCount 或者 任务总数(maxCount是指数量等于max的个数。)

前者会缺少一些填充后多余出来的任务,所以是取两者中的最大值作为结果返回。完整代码如下:

    function leastInterval(tasks, n) {
        const obj = tasks.reduce((total, curr) => {
          if (total[curr]) {
            total[curr]++
          } else {
            total[curr] = 1
          }

          return total
        }, {})

        const arr = Object.values(obj).sort((a, b) => b - a)

        const max = arr[0]
        n = n + 1
        
        // 上面都是一样的, 下面和暴力解法比起来简洁了很多。
        let maxCount = 0
        for (; maxCount < n; maxCount++) {
          if (arr[maxCount] !== max) {
            break
          }
        }

        return Math.max((max - 1) * n + maxCount, tasks.length)
      }

结束