《剑指Offer:专项突破版》 - 栈部分 JavaScript 题解
Posted 前端GoGoGo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《剑指Offer:专项突破版》 - 栈部分 JavaScript 题解相关的知识,希望对你有一定的参考价值。
《剑指Offer:专项突破版》是一个算法题集。该题单包含了程序员在准备面试过程中必备的数据结构与算法知识。具体包含:
本文来分享下栈部分题的解法~
栈介绍栈是操作受限的数组。栈的插入和删除操作都发生在数组的末尾(也叫栈顶)。它的特点是“后入先出”:后入栈的元素先出来。
基于 js 的数组,很容易实现栈:
class Stack
constructor()
this.data = [];
// 入栈
push(val)
this.data.push(val);
// 出栈
pop()
return this.data.pop();
// 查询栈顶元素
peek()
return this.data.length ? this.data[this.data.length - 1] : null;
题1 - 剑指 Offer II 036. 后缀表达式题目:后缀表达式是一种算术表达式,它的操作符在操作数的后面。输入一个用字符串数组表示的后缀表达式,请输出该后缀表达式的计算结果。假设输入的一定是有效的后缀表达式。例如,后缀表达式["2","1","3","","+"]对应的算术表达式是“2+13”,因此输出它的计算结果5。
题的力扣地址[1]
通过观察可以发现,适合用栈来计算后缀表达式。算法如下:
遍历整个表达式。 遇到数字,则入栈。 遇到运算符,则取出栈顶的两个数字进行计算,并将结果入栈。
代码如下:
const evalRPN = function (tokens)
var stack = new Stack();
for (let i = 0; i < tokens.length; i++)
let token = tokens[i];
const isOperate = [\'+\', \'-\', \'*\', \'/\'].includes(token);
if (isOperate)
const right = parseInt(stack.pop());
const left = parseInt(stack.pop());
let res;
switch (token)
case \'+\':
res = left + right;
break;
case \'-\':
res = left - right;
break;
case \'*\':
res = left * right;
break;
case \'/\':
res = left / right;
break;
console.log(`$left $token $right = $res`);
res = parseInt(res, 10); // 取整
stack.push(res);
else
stack.push(token);
return stack.pop();
;
题2 - 剑指 Offer II 037. 小行星碰撞题目:输入一个表示小行星的数组,数组中每个数字的绝对值表示小行星的大小,数字的正负号表示小行星运动的方向,正号表示向右飞行,负号表示向左飞行。如果两颗小行星相撞,那么体积较小的小行星将会爆炸最终消失,体积较大的小行星不受影响。如果相撞的两颗小行星大小相同,那么它们都会爆炸消失。飞行方向相同的小行星永远不会相撞。求最终剩下的小行星。例如,有6颗小行星[4,5,-6,4,8,-5],如下图所示(箭头表示飞行的方向),它们相撞之后最终剩下3颗小行星[-6,4,8]。
题的力扣地址[2]
小行星总是在数组的尾部碰撞,可以用栈来处理。
我们来分析下两个行星发生碰撞的情况。一共有四种情况:
第一个:往左飞。第二个:往左飞。 第一个:往左飞。第二个:往右飞。 第一个:往右飞。第二个:往左飞。 第一个:往右飞。第二个:往右飞。
第一种情况和第四种情况不会相撞,因为同方向飞不会相撞。第二种情况也不会相撞,因为是背着飞。只有第三种情况会撞。
基于上面的分析,算法如下:
用栈来存碰撞完的结果。 遍历数组。对当前的行星做如下处理。 当栈顶的行星向右飞,当前的行星向左飞时,进行碰撞。其他情况将当前的行星入栈。 碰撞逻辑是: 当前的行星小于栈顶行星。什么都不做。 当前的行星等于栈顶行星。栈顶行星出栈。 当前的行星大于栈顶行星。栈顶行星出栈。当前的行星继续和下一个栈顶行星做碰撞。直到不碰撞或栈为空。
代码如下:
const asteroidCollision = function (asteroids)
if (!asteroids)
return [];
asteroids = asteroids.filter((item) => item !== 0);
if (asteroids.length === 0)
return [];
const stack = [];
for (let i = 0; i < asteroids.length; i++)
const curr = asteroids[i];
if (stack.length === 0)
stack.push(curr);
continue;
// 碰撞
let isBig = false;
while (stack.length > 0)
const last = stack[stack.length - 1];
// 不碰撞
if(!(last > 0 && curr < 0))
stack.push(curr);
break;
if (last > -curr)
isBig = false;
break;
else if (last === -curr)
// 同归于尽
isBig = false;
stack.pop();
break;
else
// 继续碰撞
stack.pop();
isBig = true;
continue;
if(stack.length === 0 && isBig)
stack.push(curr);
return stack;
;
题3 - 剑指 Offer II 038. 每日温度题目:输入一个数组,它的每个数字是某天的温度。请计算每天需要等几天才会出现更高的温度。例如,如果输入数组[35,31,33,36,34],那么输出为[3,1,1,0,0]。由于第1天的温度是35℃,要等3天才会出现更高的温度36℃,因此对应的输出为3。第4天的温度是36℃,后面没有更高的温度,它对应的输出是0。其他的以此类推。
题的力扣地址[3]
容易想到的算法是:遍历温度数组,每个温度都往后去找更高的温度。
用栈实现的算法比上面算法的时间复杂度低。算法如下:
用栈来存当前没找到最更高值的温度下标。 遍历温度数组。用当前的温度做如下处理。 当前温度和栈顶元素比较,如果栈顶元素小于当前温度,则出栈,天数为下标的差值。当前温度不断和栈顶元素比较,直到当前温度不大于栈顶元素或栈为空。 将当前温度入栈。
代码如下:
const dailyTemperatures = function (temperatures)
if (!temperatures || !temperatures.length) return [];
let res = temperatures.map(() => 0);
let notFoundArr = [];
for (let currIndex = 0; currIndex < temperatures.length; currIndex++)
const currValue = temperatures[currIndex];
while(notFoundArr.length > 0)
const prevIndex = notFoundArr[notFoundArr.length - 1];
const prevValue = temperatures[prevIndex];
if (currValue > prevValue)
res[prevIndex] = currIndex - prevIndex;
notFoundArr.pop();
else
break;
notFoundArr.push(currIndex);
return res;
;
题4 - 剑指 Offer II 039. 直方图最大矩形面积题目:直方图是由排列在同一基线上的相邻柱子组成的图形。输入一个由非负数组成的数组,数组中的数字是直方图中柱子的高。求直方图中最大矩形面积。假设直方图中柱子的宽都为1。例如,输入数组[3,2,5,4,6,1,4,2],其对应的直方图如下图所示,该直方图中最大矩形面积为12,如阴影部分所示。
题的力扣地址[4]
我们知道,面积最大值包含的那矩形的高度,一定和某个柱子的高度相同。因此,找出最大的矩形面积,也就是找出以每个柱子的高度为高度的矩形中的最大值。
以某个柱子高度为高度的矩形面积为:宽度 * 柱子高度
。宽度 = 以某个柱子为中心往两边找相邻的,比它高的柱子的数量
。用 直接遍历 或 分治法 都很容易实现。
用栈来实现,时间复杂度比上面的算法低。具体算法如下:
用栈来存高度升序排列的柱子的下标。 遍历柱子数组。 如果当前柱子高度小于栈顶柱子高度。则出栈。并计算以出栈元素为顶的矩形面积。 重复上一步。直到当前柱子大于栈顶元素或栈为空。因此,栈中的柱子一定是按升序排列的。 当前柱子的下标入栈。 计算栈中以每个柱子为顶的矩形面积。
通过 2c 步 可知,所有柱子元素都会入栈。2a步计算了所有出栈元素的矩形面积,第3步计算了所有未出栈元素的矩形面积。因此,也就计算了所有柱子为顶的高度的矩形面积。
该算法效率高效的原因是,算宽度的时间复杂度低。
当前数组下标 - (当前元素出栈后的栈顶元素 + 1)
。算宽度的过程,不需要做遍历操作。数组长度 - (当前元素出栈后的栈顶元素 + 1)
。代码如下:
const largestRectangleArea = function (heights)
if (!heights || !heights.length)
return 0;
let maxArea = 0;
const stack = [];
for (let i = 0; i < heights.length; i++)
let currHeight = heights[i];
while (stack.length && currHeight < heights[stack[stack.length - 1]])
const heightIndex = stack.pop();
const height = heights[heightIndex];
const width = i - (stack.length ? stack[stack.length - 1] + 1 : 0);
const area = width * height;
maxArea = Math.max(maxArea, area);
stack.push(i);
// 计算栈中以每个柱子为顶的矩形面积。
while (stack.length)
const heightIndex = stack.pop();
const height = heights[heightIndex];
const width = heights.length - (stack.length ? stack[stack.length - 1] + 1 : 0);
const area = width * height;
maxArea = Math.max(maxArea, area);
return maxArea;
;
题5 - 剑指 Offer II 040. 矩阵中最大的矩形题目:请在一个由0、1组成的矩阵中找出最大的只包含1的矩形并输出它的面积。例如,在下图的矩阵中,最大的只包含1的矩阵如阴影部分所示,它的面积是6。
题的力扣地址[5]
将 1 看成柱子,将 0 看成是空白。将矩阵按行切成行的个数个子矩阵,计算所有的子矩阵中只包含1的矩形的面积,即可知道最大的面积。切后,每行最大面积就转化成了直方图最大矩形面积。可以上题的方式求解。
将上图的矩阵,按行可以切成下面的4个子矩阵:
代码如下:
const maximalRectangle = (matrix) =>
if (!matrix || !matrix.length)
return 0;
const cols = [];
const colsLength = matrix[0].length;
for (let col = 0; col < colsLength; col++)
cols[col] = [];
for (let row = 0; row < matrix.length; row++)
for (let col = 0; col < colsLength; col++)
cols[col][row] = matrix[row][col];
const maxArr = matrix.map((row, rowIndex) =>
const currCols = cols.map((c) =>
const currCol = c.slice(0, rowIndex + 1);
const height = getHeight(currCol);
return height;
);
return largestRectangleArea(currCols);
);
const max = Math.max(...maxArr);
return max;
;
function getHeight(arr)
let height = 0;
for (let i = arr.length - 1; i >= 0; i--)
if (arr[i] === \'1\')
height++;
else
break;
return height;
总结在符合栈特点的场景下,用栈来求解可以降低算法的时间复杂度。难点在于,判断当前场景是否适合用栈。
相关阅读题的力扣地址: https://leetcode-cn.com/problems/8Zf90G/
[2]题的力扣地址: https://leetcode-cn.com/problems/XagZNi/
[3]题的力扣地址: https://leetcode-cn.com/problems/iIQa4I/
[4]题的力扣地址: https://leetcode-cn.com/problems/0ynMMM/
[5]题的力扣地址: https://leetcode-cn.com/problems/PLYXKQ/
《剑指Offer:专项突破版》 - 哈希表部分 JavaScript 题解
《剑指Offer:专项突破版》是一个算法题集。该题单包含了程序员在准备面试过程中必备的数据结构与算法知识。具体包含:
本文来分享下哈希表部分题的解法~
和 最大值做比较,比如:["00: 00", "23:49", "23:59"]
的最小时间差不是 10 分钟(23:59 - 23:49
),而是 1 分钟(00: 00 + 24:00 - 23:59
)。代码如下:
const ONE_DAY_MINUTE = 24 * 60
const findMinDifference = function(timePoints)
if(timePoints.length <= 1)
return 0;
const minutes = timePoints.map(time => toMinute(time));
minutes.sort((a, b) => a - b);
const diff = minutes.map((m, i) =>
if(i === 0) // 最后一个和第一个的差值。
return m + ONE_DAY_MINUTE - minutes[minutes.length - 1];
return m - minutes[i - 1];
)
const min = Math.min(...diff);
return min;
;
function toMinute(time)
const [hour, minute] = time.split(\':\').map(Number);
return hour * 60 + minute;
相关阅读题的力扣地址: https://leetcode-cn.com/problems/FortPu/
[2]题的力扣地址: https://leetcode-cn.com/problems/OrIXps/
[3]题的力扣地址: https://leetcode-cn.com/problems/dKk3P7/
[4]题的力扣地址: https://leetcode-cn.com/problems/sfvd7V/
[5]题的力扣地址: https://leetcode-cn.com/problems/lwyVBB/
[6]题的力扣地址: https://leetcode-cn.com/problems/569nqc/
以上是关于《剑指Offer:专项突破版》 - 栈部分 JavaScript 题解的主要内容,如果未能解决你的问题,请参考以下文章
《剑指Offer:专项突破版》 - 哈希表部分 JavaScript 题解
《剑指Offer:专项突破版》 - 整数部分 JavaScript 题解