前端算法入门之路(二)(线程池与任务队列)

136 阅读3分钟

船长表情包镇楼

图片名称

队列

  • 队列是连续的存储区,可以存储一系列的元素。是FIFO(先入先出,FirstIn-First-Out)结构
  • 队列通常具有头尾指针(左闭右开区间),头指针指向第一个元素,尾指针指向最后一个元素的下一位
  • 队列支持(从队尾)入队(enqueue)、(从队首)出队(dequeue)操作
  • 循环队列可以通过取模操作更充分地利用空间

典型应用场景

  • CPU的超线程技术
  • 线程池的任务队列

LeetCode肝题

    1. 设计循环队列
// 设计队首、队尾指针,定义一个变量记录队列元素数量
var MyCircularQueue = function(k) {
    this.arr = new Array(k)
    this.head = 0
    this.tail = 0
    this.cut = 0
};
// 新增操作将队尾指针向后移动一位(加1后对长度取模)
MyCircularQueue.prototype.enQueue = function(value) {
    if (this.isFull()) return false
    this.arr[this.tail] = value
    this.tail = (this.tail + 1) % this.arr.length
    this.cut ++
    return true
};
// 删除操作将队首指针向后移动一位(加1后对长度取模)
MyCircularQueue.prototype.deQueue = function() {
    if (this.isEmpty()) return false
    this.head = (this.head + 1) % this.arr.length
    this.cut --
    return true
};
MyCircularQueue.prototype.Front = function() {
    if (this.isEmpty()) return -1
    return this.arr[this.head]
};
MyCircularQueue.prototype.Rear = function() {
    if (this.isEmpty()) return -1
    return this.arr[(this.tail - 1 + this.arr.length) % this.arr.length]
};
MyCircularQueue.prototype.isEmpty = function() {
    if (this.cut == 0) return true
    return false
};
MyCircularQueue.prototype.isFull = function() {
    if (this.cut == this.arr.length) return true
    return false
};
    1. 设计循环双端队列
// 设计队首、队尾指针,定义一个变量记录队列元素数量,基本思路同上
var MyCircularDeque = function(k) {
    this.arr = new Array(k)
    this.head = 0
    this.tail = 0
    this.cut = 0
};
MyCircularDeque.prototype.insertFront = function(value) {
    if (this.isFull()) return false
    this.head = (this.head - 1 + this.arr.length) % this.arr.length
    this.arr[this.head] = value
    this.cut ++
    return true
};
MyCircularDeque.prototype.insertLast = function(value) {
    if (this.isFull()) return false
    this.arr[this.tail] = value
    this.tail = (this.tail + 1) % this.arr.length
    this.cut ++ 
    return true
};
MyCircularDeque.prototype.deleteFront = function() {
    if (this.isEmpty()) return false
    this.head = (this.head + 1) % this.arr.length
    this.cut --
    return true
};
MyCircularDeque.prototype.deleteLast = function() {
    if (this.isEmpty()) return false
    this.tail = (this.tail - 1 + this.arr.length) % this.arr.length
    this.cut --
    return true
};
MyCircularDeque.prototype.getFront = function() {
    if (this.isEmpty()) return -1
    return this.arr[this.head]
};
MyCircularDeque.prototype.getRear = function() {
    if (this.isEmpty()) return -1
    return this.arr[(this.tail - 1 + this.arr.length) % this.arr.length]
};
MyCircularDeque.prototype.isEmpty = function() {
    return this.cut == 0
};
MyCircularDeque.prototype.isFull = function() {
    return this.arr.length == this.cut
};
    1. 设计前中后队列
// 使用链表实现一个队列
// 前中后队列使用两个普通队列实现
// 保持两个队列的数量平衡,用以实现从正中间插入的功能
var Node = function(val) {
    this.val = val
    this.next = null
    this.pre = null
};
Node.prototype.insert_pre = function(p) {
    p.next = this
    p.pre = this.pre
    this.pre && (this.pre.next = p)
    this.pre = p
    return
};
Node.prototype.insert_next = function(p) {
    p.next = this.next
    p.pre = this
    this.next && (this.next.pre = p)
    this.next = p
    return
};
Node.prototype.delete_pre = function() {
    if (!this.pre) return null
    let p = this.pre
    this.pre = p.pre
    p.pre && (p.pre.next = this)
    return
}
Node.prototype.delete_next = function() {
    if (!this.next) return null
    let p = this.next
    this.next = p.next
    p.next && (p.next.pre = this)
    return
}
var deQueue = function() {
    this.cut = 0
    this.head = new Node(null)
    this.tail = new Node(null)
    this.head.next = this.tail
    this.tail.pre = this.head
};
deQueue.prototype.push_back = function(val) {
    this.tail.insert_pre(new Node(val))
    this.cut ++
}
deQueue.prototype.push_front = function(val) {
    this.head.insert_next(new Node(val))
    this.cut ++
}
deQueue.prototype.pop_back = function() {
    if (this.isEmpty()) return -1
    const ret = this.tail.pre.val
    this.tail.delete_pre()
    this.cut --
    return ret
}
deQueue.prototype.pop_front = function() {
    if (this.isEmpty()) return -1
    const ret = this.head.next.val
    this.head.delete_next()
    this.cut --
    return ret
}
deQueue.prototype.front = function() {
    return this.head.next.val
}
deQueue.prototype.pop = function() {
    return this.tail.pre.val
}
deQueue.prototype.isEmpty = function() {
    return this.cut == 0
}
deQueue.prototype.size = function() {
    return this.cut
}

var FrontMiddleBackQueue = function() {
    this.p = new deQueue()
    this.q = new deQueue()
};

FrontMiddleBackQueue.prototype.pushFront = function(val) {
    this.p.push_front(val)
    this.update()
};

FrontMiddleBackQueue.prototype.pushMiddle = function(val) {
    
    this.p.push_back(val)
    this.update()
};

FrontMiddleBackQueue.prototype.pushBack = function(val) {
    this.q.push_back(val)
    this.update()
};

FrontMiddleBackQueue.prototype.popFront = function() {
    let ret
    if (this.p.size()) {
        ret = this.p.pop_front()
    } else {
        ret = this.q.pop_back()
    }
    this.update()
    return ret
};

FrontMiddleBackQueue.prototype.popMiddle = function() {
    let ret
    if (this.p.size() == this.q.size()) {
        ret = this.p.pop_back()
    } else {
        ret = this.q.pop_front()
    }
    this.update()
    return ret
};

FrontMiddleBackQueue.prototype.popBack = function() {
    const ret = this.q.pop_back()
    this.update()
    return ret
};

FrontMiddleBackQueue.prototype.update = function() {
    if (this.p.size() > this.q.size()) {
        this.q.push_front(this.p.pop_back())
    }
    if (this.q.size() == this.p.size() + 2) {
        this.p.push_back(this.q.pop_front())
    }
    
};
    1. 最近的请求次数
// 把时间t存放到队列里,如果t与队首元素的差大于3000则弹出队首元素
var RecentCounter = function() {
    this.arr = []
};
RecentCounter.prototype.ping = function(t) {
    this.arr.push(t)
    while(t - this.arr[0] > 3000) this.arr.shift()
    return this.arr.length
};
  1. 面试题 17.09. 第 k 个数
// 设置一个数组,初始化第一个元素为1,定义三个指针指向数组下标
// 让3、5、7分别乘数组的最后指针对应的下标,取最小值
// 去重,结果等于最小值的下标分别加1
var getKthMagicNumber = function(k) {
    const arr = new Array(k)
    let p3 = 0, p5 = 0, p7 = 0
    arr[0] = 1
    for(let i = 1; i < k; i++) {
        let num1 = 3 * arr[p3]
        let num2 = 5 * arr[p5]
        let num3 = 7 * arr[p7]
        let min = Math.min(num1,num2,num3)
        if (min == num1) p3 ++
        if (min == num2) p5 ++
        if (min == num3) p7 ++
        arr[i] = min
    }
    return arr[k-1]
};
    1. 亲密字符串
// A、B两个字符串有且仅有两处不同,并且这两处不同交叉相等才是亲密字符串
// A、B相等的情况下必须有重复字母才是亲密字符串
var buddyStrings = function(a, b) {
    if (a.length != b.length) return false
    if (b == a) return has_repeate(a)
    let i = 0, j
    while(a[i] == b[i]) i++
    j = i + 1
    while(j < a.length && a[j] == b[j]) j++
    if (j == a.length) return false
    if (a[i] != b[j] || a[j] != b[i]) return false
    j += 1
    while(j < a.length) {
        if (a[j] != b[j]) return false
        j++
    }
    return true
};
var has_repeate = function(str) {
    const obj = {}
    for(let i = 0; i < str.length; i++) {
        obj[str[i]] = null
    }
    return Object.keys(obj).length < str.length
}
    1. 柠檬水找零
// 简单粗暴枚举法,优先找大额的钞票
var lemonadeChange = function(bills) {
    let obj = {
        5: 0,
        10: 0
    }
    for(let i = 0; i < bills.length; i++) {
        if (bills[i] == 5) {
            obj[5] ++
        } else if (bills[i] == 10){
            obj[10] ++
            obj[5] --
        } else if (bills[i] == 20){
            if (obj[10]){
                obj[10]--
                obj[5] --
            } else {
                obj[5] -= 3
            }
        }
        if (obj[5] < 0) return false
    }
    return true
};
    1. 煎饼排序
// 找到最大值的下标,将其与第一位翻转,然后再翻转arr.length,最大值就到了最后一位,以此类推
// 定义一个数组存放arr每一项的下标,反向遍历arr,ind[i]就是当前需要翻转的下标
var pancakeSort = function(arr) {
    let ret = [], ind = Array(arr.length + 1)
    for(let i = 0; i < arr.length; i++) ind[arr[i]] = i
    for(let i = arr.length; i >= 1; i--) {
        if (ind[i] == i-1) continue // 判断当前值是否在正确的位置上,无需翻转
        if (ind[i]) {
            ret.push(ind[i] + 1)
            reverse(arr, ind[i] + 1, ind)
        }
        if (i != 1) {
            ret.push(i)
            reverse(arr, i, ind)
        }
    }
    return ret
};
// 翻转方法,翻转数组arr的前n位,值翻转过后值在ind里对应的下标也交换一下
var reverse = function(arr, n, ind) {
    for(let i = 0, j = n - 1; i < j; i++, j--) {
        [arr[i], arr[j]] = [arr[j], arr[i]]
        ind[arr[i]] = i
        ind[arr[j]] = j
    }
}
    1. 任务调度器 图解 66.png
// 1、所有冷却时间都填满,最短时间等于任务数量
// 2、填不满的情况下求最短时间其实就是求图中的图形面积
// 3、如果任务数量大于图形面积,就说明正好可以填满
// 图形面积=(A的数量-1)*(冷却时间n+1)+ 2
// A的数量是出现次数最多的任务数量,2是有几个任务出现次数最多
var leastInterval = function(tasks, n) {
    let obj = {}, max = 0, num = 0
    for(let i = 0; i < 26; i++) {
        obj[String.fromCharCode(i+65)] = 0
    }
    for(let i = 0; i < tasks.length; i++) {
        obj[tasks[i]] += 1
    }
    let arr = Object.keys(obj)
    for(let i = 0; i < arr.length; i++) {
        max = Math.max(obj[arr[i]], max)
    }
    for(let i = 0; i < arr.length; i++) {
        if (obj[arr[i]] == max) num ++
    }
    return Math.max((max - 1) * (n + 1) + num, tasks.length)
};