《剑指 Offer(第 2 版)》队列部分 JavaScript 题解

简介: 《剑指 Offer(第 2 版)》队列部分 JavaScript 题解

《剑指 Offer (第 2 版)》队列部分 JavaScript 题解

《剑指 Offer(第 2 版)》通行全球的程序员经典面试秘籍。剖析典型的编程面试题,系统整理基础知识、代码质量、解题思路、优化效率和综合能力这 5 个面试要点。

最近,把「队列」部分的题刷完了。本文来分享下这些题的解法

09. 用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTaildeleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

「示例 1:」

输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]

「示例 2:」

输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]

「提示:」

  • 1 <= values <= 10000
  • 最多会对 appendTail、deleteHead 进行 10000 次调用

题目地址:https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/

「解题思路」

首先我们知道,队列是先入先出,栈是后入先出,所以知道了这两个东西的特性,我们很容易就能根据题意使用两个栈来模拟队列。

首先,两个栈分工不同,一个为入队栈,一个为出队栈,各自负责入队和出队。

入队操作,直接压入入队栈即可,出队操作需要优先检查出队栈是否有数据,若无,需要从入队栈倒入后再操作。

var CQueue = function() {
    this.enterStack = [];
    this.leaveStack = [];
};
/** 
 * @param {number} value
 * @return {void}
 */
CQueue.prototype.appendTail = function(value) {
    this.enterStack.push(value);
};
/**
 * @return {number}
 */
CQueue.prototype.deleteHead = function() {
    if (this.leaveStack.length) {
        return this.leaveStack.pop();
    } else {
        while(this.enterStack.length) {
            this.leaveStack.push(this.enterStack.pop());
        }
        return this.leaveStack.length ? this.leaveStack.pop() : -1;
    }
};
/**
 * Your CQueue object will be instantiated and called as such:
 * var obj = new CQueue()
 * obj.appendTail(value)
 * var param_2 = obj.deleteHead()
 */

50. 第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。s 只包含小写字母。

「示例 1:」

输入:s = "abaccdeff"
输出:'b'

「示例 2:」

输入:s = "" 
输出:' '

「限制:」

  • 0 <= s 的长度 <= 50000

题目地址:https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/

「解题思路」

利用循环找出第一个数的位置,再次查询这个数位置之后是否还存在相同的数字,存在则继续,不存在则输出。

/**
 * @param {string} s
 * @return {character}
 */
var firstUniqChar = function(s) {
    for(let i = 0;i < s.length; i++) {
        const k = s.indexOf(s[i]);
        const e = s.indexOf(s[i], k+1);
        if(e === -1) return s[i];
    }
    return " ";
};

9 - I. 滑动窗口的最大值

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

「示例:」

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 
  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

「提示:」

你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。

题目地址:https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/

「解题思路」

由于我们需要求出的是滑动窗口的最大值,如果当前的滑动窗口中有两个下标 i 和 j,其中 i 在 j 的左侧(i<j),并且 i 对应的元素不大于 j 对应的元素(nums[i]≤nums[j]),那么会发生什么呢?

当滑动窗口向右移动时,「只要 i 还在窗口中,那么 j 一定也还在窗口中」,这是 i 在 j 的左侧所保证的。因此,由于 nums[j] 的存在,nums[i] 一定不会是滑动窗口中的最大值了,我们可以将 nums[i] 永久地移除。

因此我们可以使用一个队列存储所有还没有被移除的下标。在队列中,这些下标按照从小到大的顺序被存储,并且它们在数组 nums 中对应的值是严格单调递减的。因为如果队列中有两个相邻的下标,它们对应的值相等或者递增,那么令前者为 i,后者为 j,就对应了上面所说的情况,即 nums[i] 会被移除,这就产生了矛盾。

当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。为了保持队列的性质,我们会不断地将新的元素与队尾的元素相比较,如果前者大于等于后者,那么队尾的元素就可以被永久地移除,我们将其弹出队列。我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素。

由于队列中下标对应的元素是严格单调递减的,因此此时队首下标对应的元素就是滑动窗口中的最大值。但与方法一中相同的是,此时的最大值可能在滑动窗口左边界的左侧,并且随着窗口向右移动,它永远不可能出现在滑动窗口中了。因此我们还需要不断从队首弹出元素,直到队首元素在窗口中为止。

为了可以同时弹出队首和队尾的元素,我们需要使用双端队列。满足这种单调性的双端队列一般称作「单调队列」。

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSlidingWindow = function(nums, k) {
    if (!k) return [];
    const n = nums.length;
    const q = [];
    for (let i = 0; i < k; i++) {
        while (q.length && nums[i] >= nums[q[q.length - 1]]) {
            q.pop();
        }
        q.push(i);
    }
    const ans = [nums[q[0]]];
    for (let i = k; i < n; i++) {
        while (q.length && nums[i] >= nums[q[q.length - 1]]) {
            q.pop();
        }
        q.push(i);
        while (q[0] <= i - k) {
            q.shift();
        }
        ans.push(nums[q[0]]);
    }
    return ans;
}

59 - II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

「示例 1:」

输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]

「示例 2:」

输入: 
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]

「限制:」

  • 1 <= push_back,pop_front,max_value的总操作数 <= 10000
  • 1 <= value <= 10^5

题目地址:https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/

「解题思路」为了解决上述问题,我们只需记住当前最大值出队后,队列里的下一个最大值即可。

具体方法是使用一个双端队列 deque,在每次入队时,如果 deque 队尾元素小于即将入队的元素 value,则将小于 value 的元素全部出队后,再将 value 入队;否则直接入队。

这时,辅助队列 deque 队首元素就是队列的最大值。

var MaxQueue = function() {
    this.queue = [];
    this.deque = [];
};
/**
 * @return {number}
 */
MaxQueue.prototype.max_value = function() {
    return this.deque.length ? this.deque[0] : -1;
};
/** 
 * @param {number} value
 * @return {void}
 */
MaxQueue.prototype.push_back = function(value) {
    while(this.deque.length && this.deque[this.deque.length - 1] < value) {
        this.deque.pop();
    }
    this.queue.push(value);
    this.deque.push(value);
};
/**
 * @return {number}
 */
MaxQueue.prototype.pop_front = function() {
    if (!this.queue.length) return -1;
    const front = this.queue.shift();
    if (this.deque[0] === front) {
        this.deque.shift();
    }
    return front;
}; 
/**
 * Your MaxQueue object will be instantiated and called as such:
 * var obj = new MaxQueue()
 * var param_1 = obj.max_value()
 * obj.push_back(value)
 * var param_3 = obj.pop_front()
 */
相关文章
|
2月前
|
存储 JSON JavaScript
「offer来了」保姆级巩固你的js知识体系(4.0w字)
该文章提供了JavaScript知识体系的全面复习资料,覆盖了从基础语法到高级特性如闭包、原型链、异步编程等多个方面,并通过大量的面试题和实例代码帮助巩固理解。
「offer来了」保姆级巩固你的js知识体系(4.0w字)
|
6月前
|
JavaScript 前端开发
JavaScript题解剑指offer : 09. 用两个栈实现队列
JavaScript题解剑指offer : 09. 用两个栈实现队列
42 0
|
JavaScript 前端开发 程序员
《剑指 Offer(第 2 版)》树部分JavaScript题解
《剑指 Offer(第 2 版)》树部分JavaScript题解
《剑指 Offer(第 2 版)》树部分JavaScript题解
|
存储 JavaScript 前端开发
《剑指 Offer (第 2 版)》链表部分 JavaScript 题解
《剑指 Offer (第 2 版)》链表部分 JavaScript 题解
《剑指 Offer (第 2 版)》链表部分 JavaScript 题解
|
JavaScript 前端开发 算法
剑指 Offer 03. 数组中重复的数字:JavaScript 的两种哈希解法
剑指 Offer 03. 数组中重复的数字:JavaScript 的两种哈希解法
82 0
剑指 Offer 03. 数组中重复的数字:JavaScript 的两种哈希解法
|
自然语言处理 JavaScript 前端开发
剑指 offer —— js闭包面试通杀秘籍
说到js闭包,早已不是什么新鲜事。 几乎是前端面试时必问的一道题,到如今已是烂大街的存在,但不可否认的是,仍然有一些人没有搞懂,只知其然不知其所以然,那么我们今天就好好的捋一捋。
95 0
剑指 offer —— js闭包面试通杀秘籍
|
缓存 移动开发 JavaScript
「offer来了」Vue.js篇,从vue2到vue3,48个知识点保姆级带你巩固vuejs知识体系(下)
在下面的这篇文章中,将从 vue2 的基础知识,到 vue2 的原理知识,再到 vue3 的基础知识和原理知识做一个归纳和总结。同时,周一也将整理出相关的面试题,以供大家可以有一个更好的参考。 下面开始进入本文的讲解~
「offer来了」Vue.js篇,从vue2到vue3,48个知识点保姆级带你巩固vuejs知识体系(下)
|
缓存 移动开发 JavaScript
「offer来了」Vue.js篇,从vue2到vue3,48个知识点保姆级带你巩固vuejs知识体系(上)
在下面的这篇文章中,将从 vue2 的基础知识,到 vue2 的原理知识,再到 vue3 的基础知识和原理知识做一个归纳和总结。同时,周一也将整理出相关的面试题,以供大家可以有一个更好的参考。 下面开始进入本文的讲解~
「offer来了」Vue.js篇,从vue2到vue3,48个知识点保姆级带你巩固vuejs知识体系(上)
|
JavaScript 前端开发
JavaScript题解剑指offer : 04. 二维数组中的查找
JavaScript题解剑指offer : 04. 二维数组中的查找
116 0