数据结构在学校的教材、网上的课程中大部分都是以 C、C++语言为例子介绍,今天我们就用 JavaScript 重新介绍一下基础数据结构:栈、队列、链表、树。
每种数据结构都会简单介绍一下其概念,并使用 JavaScript 实现,同时附上 leetcode 题目加深理解
栈
概念
- 遵从后进先出原则的有续集合
- 添加新元素的一端称为栈顶,另一端称为栈底
- 操作栈的元素时,只能从栈顶操作,如:添加元素、移除或取值
实现
需要实现的几个基本功能如下
- push:入栈方法
- pop: 出栈方法
- top:获取栈顶值
- size: 获取栈的元素个数
- clear: 清空栈
js
class Stack {
constructor() {
this.data = {};
this.count = 0;
}
push(item) {
this.data[this.count++] = item;
}
pop() {
if (!this.size()) {
return;
}
const top = this.data[this.count - 1];
delete this.data[--this.count];
return top;
}
top() {
if (!this.size()) {
return;
}
return this.data[this.count - 1];
}
size() {
return this.count;
}
clear() {
this.data = {};
this.count = 0;
}
}
栈的最小值
题目
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
js
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
提示:各函数的调用总次数不超过 20000 次
题解
js
var MinStack = function () {
this.data = {};
this.count = 0;
this.minData = {};
this.minCount = 0;
};
/**
* @param {number} x
* @return {void}
*/
MinStack.prototype.push = function (x) {
this.data[this.count++] = x;
if (!this.minCount || this.min() >= x) {
this.minData[this.minCount++] = x;
}
};
/**
* @return {void}
*/
MinStack.prototype.pop = function () {
if (this.top() === this.min()) {
delete this.minData[--this.minCount];
}
delete this.data[--this.count];
};
/**
* @return {number}
*/
MinStack.prototype.top = function () {
return this.data[this.count - 1];
};
/**
* @return {number}
*/
MinStack.prototype.min = function () {
return this.minData[this.minCount - 1];
};
每日温度
题目
请根据每日 气温 列表 temperatures ,重新生成一个列表,要求其对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]
提示:
1 <= temperatures.length <= 105
30 <= temperatures[i] <= 100
题解
常规方法:遍历,双层循环
单调栈:
- 因为要找升温的,所以我们这里反着来,规定栈是递减的
- 一开始栈是空的,第一个元素入栈;
- 第二个元素与栈顶对比,如果比栈顶值大,说明找到了升温的一天,当前的索引减去栈顶的索引就是需要等待的时间;同时栈顶值出栈,因为已经找到栈顶值升温等待的天数了,无需再处理它了;
- 这时候如果栈中还有值,继续拿当前值去与栈顶值比较,如果还是大于栈顶值,就重复上一步,如果小于栈顶值,就将当前值入栈,循环取下一个值来继续比较
js
/**
* @param {number[]} temperatures
* @return {number[]}
*/
var dailyTemperatures = function (temperatures) {
const stack = [temperatures[0]];
const len = temperatures.length;
const arr = new Array(len).fill(0);
for (let i = 0; i < len; i++) {
const curtemp = temperatures[i];
while (stack.length && curtemp > stack[stack.length - 1]) {
// 也可以将 stack 改成存索引,这里就不用再 findIndex 了
const stackIndex = temperatures.findIndex(
(cur) => cur === stack[stack.length - 1]
);
arr[stackIndex] = i - stackIndex;
stack.pop();
}
stack.push(curtemp);
}
return arr;
};
队列
概念
- 遵循先进先出的有序集合
- 添加新元素的一端为队尾,另一端为队首
实现
基于数组实现
js
class Queue {
constructor() {
this.queue = [];
this.count = 0;
}
// 入队
enQueue(item) {
this.queue[this.count++] = item;
}
// 出队
deQueue() {
if (this.isEmpty()) {
return;
}
this.count--;
return this.queue.shift();
}
// 是否为空
isEmpty() {
return this.count === 0;
}
// 队首元素
top() {
if (this.isEmpty()) {
return;
}
return this.queue[0];
}
length() {
return this.count;
}
clear() {
this.count = 0;
this.queue = [];
}
}
基于对象实现
js
class Queue {
constructor() {
this.queue = {};
this.count = 0;
this.head = 0;
}
// 入队
enQueue(item) {
this.queue[this.count++] = item;
}
// 出队
deQueue() {
if (this.isEmpty()) {
return;
}
const headData = this.queue[this.head];
delete this.queue[this.head];
this.head++;
return headData;
}
length() {
return this.count - this.head;
}
// 是否为空
isEmpty() {
return this.length() === 0;
}
clear() {
this.queue = [];
this.count = 0;
this.head = 0;
}
}
双端队列
双端队列(double-ended queue)指的是允许同时从队尾与队首两端进行存取操作的队列,操作更加灵活
基本功能如下:
- addFront:从头添加元素
- addBack:从尾部添加元素
- removeFront:删除头部元素
- removeBack:删除末尾元素
- frontTop:获取头部元素
- backTop:获取末尾元素
js
class Deque {
constructor() {
this.queue = {};
this.count = 0;
this.head = 0;
}
addFront(item) {
this.queue[--this.head] = item;
}
addBack(item) {
this.queue[this.count++] = item;
}
removeFront() {
if (this.isEmpty()) {
return;
}
const front = this.queue[this.head];
delete this.queue[this.head++];
return front;
}
removeBack() {
if (this.isEmpty()) {
return;
}
const back = this.queue[this.count - 1];
delete this.queue[--this.count];
return back;
}
frontTop() {
if (this.isEmpty()) {
return;
}
return this.queue[this.head];
}
backTop() {
if (this.isEmpty()) {
return;
}
return this.queue[this.count - 1];
}
length() {
return this.count - this.head;
}
isEmpty() {
return this.length() === 0;
}
}
队列的最大值
题目
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数 max_value、push_back 和 pop_front 的均摊时间复杂度都是 O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
less
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:
less
输入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
限制:
1 <= push_back,pop_front,max_value 的总操作数 <= 10000
1 <= value <= 10^5
题解
js
var MaxQueue = function () {
this.queue = {};
this.count = 0;
this.head = 0;
this.dque = {};
this.dcount = 0;
this.dhead = 0;
};
/**
* @return {number}
*/
MaxQueue.prototype.max_value = function () {
return this.dque[this.dhead];
};
/**
* @param {number} value
* @return {void}
*/
MaxQueue.prototype.push_back = function (value) {
this.queue[this.count++] = value;
if (!this.dque[this.dhead]) {
this.dque[this.dhead] = value;
} else if (value > this.dque[this.dhead]) {
this.dque[this.dhead--] = value;
}
};
/**
* @return {number}
*/
MaxQueue.prototype.pop_front = function () {
if (this.count === this.head) {
return -1;
}
const front = this.queue[this.head];
delete this.queue[this.head++];
return front;
};
滑动窗口的最大值
题目
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
css
滑动窗口的位置 最大值
--------------- -----
[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 ≤ nums.length
。
题解
js
/**
* @param {number[]} nums 传入数组
* @param {number} k 滑动窗口宽度
* @return {number[]}
*/
var maxSlidingWindow = function (nums, k) {
if (k <= 1) {
return nums;
}
const result = [];
const deque = [];
// 1、将窗口第一个位置的数据添加到 deque 中,保持递减
deque.push(nums[0]);
let i = 1;
for (; i < k; i++) {
// deque存在数据
// 当前数据大于队尾值
// 出队,再重复比较
while (deque.length && nums[i] > deuqe[deque.length - 1]) {
deque.pop();
}
deque.push(nums[i]);
}
// 将第一个位置的最大值添加到result
result.push(deque[0]);
// 2、遍历后续数据
for (; i < nums.length; i++) {
while (deque.length && nums[i] > deuqe[deque.length - 1]) {
deque.pop();
}
deque.push(nums[i]);
// 检测当前最大值是否位于窗口外
if (deque[0] === nums[i - k]) {
deque.shift();
}
// 添加最大值到result
result.push(deque[0]);
}
};
链表
链表是有序的数据结构
与栈、队列的区别就是:链表可以从首、尾、中间任一位置进行数据存取,灵活度更高一些
到这里你可能就会有疑问了,这直接用数组不就完了?
为什么不直接用数组?
原因很简单,在做某些操作时,链表的性能优于数组
数组是用来存有序数据的一种数据类型,在内存中需要占用一段连续的空间;也因为它占用的空间是连续的,所以我们可以直接使用索引快速找到对应的元素;当我们对数组进行添加或者移除操作时,就可能出现性能开销比较大的情况,例如在非末尾的位置添加、移除元素,就会导致后续的元素出现位移。
直接用数据说话,这是两段操作数组的代码,
js
const arr = [];
for (let i = 0; i < 100000; i++) {
arr.push(i);
}
js
const arr = [];
for (let i = 0; i < 100000; i++) {
arr.unshift(i);
}
借助 jsbench 工具,得到两段代码的执行效率如下:
- push 操作:1112 ops/s
- unshift 操作:2.9 ops/s
(JSBench 的使用欢迎看我这一篇文章 ☞ js 性能优化(实战篇))
概念
- 链表是有序的数据结构
- 链表中的每个部分称为节点
- 可以从首、尾、中间进行数据存取
- 链表的元素在内存中不必是连续的空间
优势
添加与删除操作不会导致其他元素的位移,主要使用场景是一些经常需要添加、删除元素的数据
劣势
无法根据索引快速定位元素
实现
因为链表在内存中并不需要是连续的,所以我们这里使用 next 标记链表中的下一个元素
这里我们实现两个类:
- 节点类:value、next
- 链表类
- addAtTail 尾部添加节点
- addAtHead 头部添加节点
- addAtIndex 指定位置添加节点
- get 读取节点
- removeAtIndex 删除指定位置的节点
ts
// 节点类
class LinkedNode {
value: unknown;
next: null | LinkedNode;
constructor(val: unknown) {
this.value = val;
this.next = null;
}
}
ts
// 链表类
class LinkedList {
count: number;
head: null | LinkedNode;
constructor() {
// 节点个数
this.count = 0;
// 头部"指针"
this.head = null;
}
// 尾部添加节点
addAtTail(val: unknown) {
const node = new LinkedNode(val);
if (!this.count || !this.head) {
// 链表为空
this.head = node;
this.count++;
return;
}
// 链表有数据
let cur = this.head;
// 循环查找尾部节点
while (cur.next) {
cur = cur.next;
}
// 将新节点添加到末尾
cur.next = node;
this.count++;
}
// 头部添加节点
addAtHead(val: unknown) {
const node = new LinkedNode(val);
if (this.count) {
// 链表不为空
node.next = this.head;
}
this.head = node;
this.count++;
}
// 指定位置添加节点
addAtIndex(index: number, val: unknown) {
const node = new LinkedNode(val);
const target = this.get(index);
if (!target) {
return false;
}
node.next = target;
const prev = this.get(index - 1);
if (prev) {
prev.next = node;
}
return true;
}
// 根据索引获取节点
get(index: number) {
if (!this.count || index < 0 || index >= this.count || !this.head) {
/**
* 异常数据处理
* 1. 链表为空
* 2. index 为负数
* 3. index 超出链表范围
* 4. head 为 null
*/
return null;
}
// 从首部开始向后获取节点
let target = this.head;
let count = 0;
while (target.next && count < index) {
/**
* 存在下一个节点
* 且还未到达指定位置
*/
target = target.next;
count++;
}
if (index === count) {
return target;
}
return null;
}
// 删除指定位置的节点
removeAtIndex(index: number) {
const target = this.get(index);
if (!target) {
return false;
}
const prev = this.get(index - 1);
if (prev) {
prev.next = target.next;
}
return true;
}
}
链表类型
双向链表
指在普通链表的基础上,增加一个用于记录上一个节点的属性 prev,可进行双向访问。
首节点的 prev 指向空,末尾节点的 next 指向空。
循环链表
又称 "环形链表",指的是链表最后一个节点的 next 指向第一个节点,形成首尾相连的循环结构。
实际使用中,最后一个节点的 next 不一定要指向第一个节点,可能是链表中任一位置的节点。
反转链表
题目
反转一个单链表。
示例:
rust
输入:1->2->3->4->5->NULL
输出:5->4->3->2->1->NULL
进阶:你可以迭代或递归地反转链表。你能否用两种方式解决这道题?
题解
js
/**
* Definition for singly-llinked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null :: next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function (head) {
let cur = head;
let prev = null;
while (cur) {
const next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
};
递归解法
js
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function (head) {
if (!head || !head.next) {
return head;
}
const newHead = reverseList(head.next);
// 第一次执行这里的节点是倒数第二个节点
head.next.next = head;
// head 的 next 在下一次递归时会被重新设置
// 最后一次的 next 会被设置为 null
head.next = null;
return newHead;
};
环路检测
题目
给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点
。若环不存在,请返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。
进阶:
你是否可以不用额外空间解决此题?
题解:哈希表
js
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function (head) {
const visited = new Set();
while (head !== null) {
if (visited.has(head)) {
return head;
}
visited.add(head);
head = head.next;
}
return null;
};
题解:快慢指针
- slow 指针,每次移动一位
- fast 指针,每次移动两位
fast 移动的距离是 slow 的两倍,因此如果链表中存在环,那么 fast 与 slow 必定会相遇
js
var detectCycle = function (head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (!fast) {
return null;
}
if (slow === fast) {
// 存在环路
let ptr = head;
/**
* 新指针 ptr 从 head 开始移动
* slow 继续移动
* ptr 与 slow 相遇的位置即为环的起点
*/
while (ptr.next) {
if (ptr === slow) {
return ptr;
}
ptr = ptr.next;
slow = slow.next;
}
}
}
return null;
};
树与二叉树
树的概念
栈、队列都属于线性结构,而树属于非线性结构
树中每个部分被称为结点
,节点之间存在分支结构与层次关系
每个树形结构都具有一个根结点
,如下图中的 A 结点
根据节点之间的关系,还存在 父结点
、子结点
、兄弟结点
不含子结点的结点称为 叶结点
,如上图中的 D、E、F
以树的角度,还存在 子树
的概念,指的是对某个结点与其后代结点的统称
B、D、H 就可以看作是子树
由于树存在父子关系,树的结点形成了多级结构,每一层被称为 层级
从根结点开始,根结点的层级为 1,向下依次递增
最深结点的层级被称为树的 高度
,上图中树的高度就是 3
二叉树的概念
树结构中的一种,二叉树的每个结点最多只能存在两个子结点
二叉树中子结点有更明确的称呼,左子结点
与 右子结点
还有一些特殊形式的二叉树,如:满二叉树
、完全二叉树
- 满二叉树:每层结点都达到最大值
- 完全二叉树:除了最后一层外,每层结点都达到最大值,且最后一层结点都位于左侧
二叉树的存储形式
主要有两种:顺序存储方式
、链式存储方式
顺序存储方式主要是完全二叉树和满二叉树使用,因为它的结构是连续的,可以按照从左到右、从上到下的顺序将结点存储在数组中;
对于普通二叉树也可以使用顺序存储,不存在的结点可以使用 null 等空值代替,但是对存储空间是一种浪费;
因此,普通二叉树通常采用链式存储方式,记录结点间的关系,每个结点通过 value
表示值,left
、right
表示左右子结点
二叉树的遍历
根据访问顺序的不同,有三种不同的遍历方式:前序遍历
、中序遍历
、后序遍历
- 前序遍历:根结点 -> 左子树 -> 右子树
- 中序遍历:左子树 -> 根结点 -> 右子树
- 后序遍历:左子树 -> 右子树 -> 根结点
二叉树的前序遍历
题目
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
示例 4:
输入:root = [1,2]
输出:[1,2]
示例 5:
输入:root = [1,null,2]
输出:[1,2]
提示:
- 树中节点数目在范围
[0, 100]
内 -100 <= Node.val <= 100
进阶:递归算法很简单,你可以通过迭代算法完成吗?
题解:递归方式
js
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function (root) {
if (!root) {
return [];
}
return [
root.val,
...preorderTraversal(root.left),
...preorderTraversal(root.right),
];
};
题解:迭代方式
js
var preorderTraversal = function (root) {
const result = [];
const stack = [];
while (root || stack.length) {
while (root) {
stack.push(root.right);
result.push(root.val);
root = root.left;
}
root = stack.pop();
}
return result;
};
二叉树的最大深度
题目
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:3
示例 2:
输入:root = [1,null,2]
输出:2
提示:
- 树中节点的数量在
[0, 104]
区间内。 -100 <= Node.val <= 100
题解:深度优先搜索(DFS)
js
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function (root) {
if (!root) {
return 0;
}
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
};
二叉树的层序遍历
题目
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = [] 输出:[]
提示:
- 树中节点数目在范围
[0, 2000]
内 -1000 <= Node.val <= 1000
题解:广度优先搜索(BFS)
js
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function (root) {
const result = [];
if (!root) {
return result;
}
// 声明队列,缓存一下同层的结点
const q = [root];
while (q.length) {
result.push([]);
const len = q.length;
// 遍历队列,读取同层结点
for (let i = 0; i < len; i++) {
const node = q.shift();
result[result.length - 1].push(node.val);
// 如果还存在左右子结点,存入队列中
if (node.left) {
q.push(node.left);
}
if (node.right) {
q.push(node.right);
}
}
}
return result;
};
二叉搜索树
一种特殊的二叉树形式,简称 BST
左子树的结点小于根结点,右子树的结点大于根结点
子树也是二叉搜索树
验证二叉搜索树
题目
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3]
输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
- 树中节点数目范围在
[1, 104]
内 -231 <= Node.val <= 231 - 1
题解:递归
js
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isValidBST = function (root) {
return helper(root, -Infinity, Infinity);
};
const helper = (root, lower, upper) => {
if (!root) {
return true;
}
if (root.val >= upper || root.val <= lower) {
return false;
}
return (
helper(root.left, lower, root.val) && helper(root.right, root.val, upper)
);
};
题解:中序遍历
以上图的二叉搜索树为例,如果是用中序遍历,遍历的结果如下:
[3, 7, 9, 10, 12, 15]
可以明显看出,得到的结果就是递增数组
先来个中序遍历算法
js
var inorderTraversal = function (root) {
const stack = [];
const result = [];
while (root || stack.length) {
while (root) {
// 先将根结点入栈
stack.push(root);
// 移动到左子结点,继续入栈
root = root.left;
}
/**
* 走到这
* stack 中缓存了左子结点和根结点
* 3、7、10
*/
root = stack.pop();
result.push(root.val);
// 移动到右结点
root = root.right;
}
return result;
};
通过中序遍历得到了 result,接下来只要判断一下 result 是不是递增
js
var isValidBST = function (root) {
const stack = [];
const result = [];
while (root || stack.length) {
while (root) {
stack.push(root);
root = root.left;
}
root = stack.pop();
if (result[result.length - 1] >= root.val) {
/**
* 判断下一个结点是否大于上一个结点
* 为 false 就代表 result 不是递增的
*/
return false;
}
result.push(root.val);
root = root.right;
}
return true;
};
我们看到这个时候 result 就显的有点多余了,我们只需要对比上一个结点而已,不需要记录所有结点的值,再改造一下
js
var isValidBST = function (root) {
const stack = [];
let last = -Infinity;
while (root || stack.length) {
while (root) {
stack.push(root);
root = root.left;
}
root = stack.pop();
if (last >= root.val) {
return false;
}
last = root.val;
root = root.right;
}
return true;
};
以上,就是前端视角下的基础数据结构;今年来行业不景气,趁此机会好好充实一下自己吧