时间/空间复杂度
时间复杂度和空间复杂度是用于衡量算法性能的重要指标。
时间复杂度描述了算法解决问题所需的时间,通常使用大O符号(O(n))表示。常见的时间复杂度包括:
- O(1):常数时间复杂度,表示算法的执行时间不随输入规模增大而增加。
- O(log n):对数时间复杂度,通常出现在使用了分治或二分思想的算法中。
- O(n):线性时间复杂度,算法的执行时间与输入规模成正比。
- O(n log n):线性对数时间复杂度,通常出现在快速排序、归并排序等排序算法中。
- O(n^2):平方时间复杂度,通常出现在简单的嵌套循环算法中。
- O(2^n):指数时间复杂度,通常出现在穷举搜索等算法中。
空间复杂度描述了算法在运行过程中所需的内存空间,也通常使用大O符号(O(n))表示。常见的空间复杂度包括:
- O(1):常数空间复杂度,表示算法的空间占用是固定的,与输入规模无关。
- O(n):线性空间复杂度,算法的空间占用与输入规模成正比。
- O(n^2):平方空间复杂度,空间占用与输入规模的平方成正比。
在比较时间复杂度和空间复杂度之间的大小关系时,通常可以得出以下结论:
- 如果一个算法的时间复杂度较低,但空间复杂度较高,那么它可能会更快地执行,但会占用更多的内存空间。
- 如果一个算法的空间复杂度较低,但时间复杂度较高,那么它可能会占用较少的内存空间,但执行速度可能会较慢。
栈
栈是一种先进后出的线性数据结构。
栈具有两个主要操作:压入(push)和弹出(pop)。压入操作将元素添加到栈顶,而弹出操作则移除栈顶的元素。js中没有栈结构,但是可以用数组来模拟实现栈的所有功能。
常见需要使用栈的场景包括:
- 括号匹配:在编程中,栈常用于检查表达式中的括号是否匹配。遍历表达式时,遇到左括号则压入栈,遇到右括号则弹出栈顶元素进行匹配。
- 浏览器前进后退:浏览器的前进和后退功能可以通过两个栈来实现,一个栈用于存储前进的页面,另一个栈用于存储后退的页面。
- 函数调用:在计算机内部,函数调用时使用栈来保存调用信息。每次函数调用时,相关信息会被压入栈中,函数返回时再从栈中弹出信息。
- 表达式求值:中缀表达式转换为后缀表达式时,可以使用栈来存储操作符,以便进行求值。
题目一 有效的括号
解题思路:
- 新建一个栈
- 扫描字符串,遇到左括号则入栈,遇到右括号则和栈顶括号类型进行匹配,匹配成功则出栈;不匹配则判断为不合法
- 最后栈空了则合法,否则也是不合法
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function (s) {
const stack = [];
if (s.length % 2 === 1) return false;
const len = s.length;
console.log(len);
for (let i = 0; i < len; i++) {
const c = s[i]
if ('({['.includes(c)) {
stack.push(c)
console.log(stack,'push');
} else {
const t = stack[stack.length - 1]
console.log(t,'t------');
if (
(t === '{' && c === '}') ||
(t === '[' && c === ']') ||
(t === '(' && c === ')')
) {
stack.pop()
console.log(stack,'pop');
} else {
return false
}
}
}
return stack.length === 0
};
时间复杂度为O(N)
空间复杂度为O(N)
题目二 浏览器前进后退
浏览器的前进和后退功能可以通过两个栈来实现。一个栈用于存储前进的页面,另一个栈用于存储后退的页面。当用户进行页面导航时,将页面压入相应的栈中,实现前进和后退的功能。注意,当用户进入到一个新的页面时,此时要清空前进页面的栈。
class BrowserHistory {
constructor() {
this.backStack = []; // 存储后退的页面
this.forwardStack = []; // 存储前进的页面
this.currentURL = null; // 当前页面
}
visit(url) {
if (this.currentURL !== null) {
this.backStack.push(this.currentURL); // 将当前页面压入后退栈
this.forwardStack = []; // 清空前进栈
}
this.currentURL = url; // 设置当前页面为新访问的页面
}
back() {
if (this.backStack.length > 0) {
this.forwardStack.push(this.currentURL); // 将当前页面压入前进栈
this.currentURL = this.backStack.pop(); // 弹出后退栈的页面作为当前页面
}
}
forward() {
if (this.forwardStack.length > 0) {
this.backStack.push(this.currentURL); // 将当前页面压入后退栈
this.currentURL = this.forwardStack.pop(); // 弹出前进栈的页面作为当前页面
}
}
}
visit、back和forward函数的时间复杂度都为 O(1),而空间复杂度为 O(n)
题目三 二叉树的前序遍历
解题思路:
- 使用栈的方式来遍历,第一步将根节点放入栈中
- 将栈中的节点取出,将当前节点的值放入res中,并且先将取出节点的右节点再放入栈中,再将节点的左节点放入栈中(这样做是为了能先访问节点的左节点,即完成前序遍历)
- 重复第二个步骤,直到栈中没有节点了,即表示遍历完了二叉树
/**
* 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[]}
*/
const preorderTraversal = function(root) {
const res = [];
const stack = [];
if (root) stack.push(root)
while(stack.length) {
const n = stack.pop()
res.push(n.val)
if (n.right) stack.push(n.right)
if (n.left) stack.push(n.left)
}
return res;
};
时间复杂度:
- 遍历整棵树的时间复杂度为 O(n),其中 n 为二叉树中节点的数量。因为需要访问每个节点一次,所以时间复杂度为 O(n)。
空间复杂度:
- 使用了一个栈来辅助遍历,栈中最多同时存储树的高度个节点,因此空间复杂度为 O(h),其中 h 为二叉树的高度。在最坏的情况下,当二叉树退化为链表时,树的高度为 n,此时空间复杂度为 O(n)。
队列
队列是一种先进先出的数据结构。
在队列中,新元素被添加到队尾,而从队列中移除元素时则从队头开始。
队列通常用于需要按顺序处理数据的场景,以下是一些常见的队列应用场景:
- 任务调度:操作系统中的任务调度通常使用队列来管理待执行的任务,确保任务按照先后顺序得到执行。
- 消息队列:在分布式系统中,消息队列被用于异步通信和解耦,允许不同组件之间通过发送和接收消息来进行通信。
- 广度优先搜索(BFS):在图论和树结构中,广度优先搜索算法通常使用队列来管理待访问的节点,以确保按层级顺序进行搜索。
- 缓冲区管理:在计算机网络中,队列被用于管理数据包的传输和处理,以平衡发送和接收数据的速率。
- 线程池任务队列:在多线程编程中,线程池通常使用队列来管理待执行的任务,以实现任务的异步执行和资源的复用。
题目一 最近的请求次数
思路:
- 有新请求就入队,3000ms前发出的请求出队
- 队列的长度就是最近请求次数
var RecentCounter = function() {
this.q = []
};
/**
* @param {number} t
* @return {number}
*/
RecentCounter.prototype.ping = function(t) {
this.q.push(t);
while(this.q[0] < t -3000) {
this.q.shift()
}
return this.q.length
};
这个函数的时间复杂度为O(n),其中n是ping方法被调用的次数。在每次调用ping方法时,都会执行一个while循环,该循环的迭代次数取决于队列中满足条件的元素个数,最坏情况下需要遍历整个队列,因此时间复杂度为O(n)。
空间复杂度为O(n),因为队列q会随着每次调用ping方法而增长,最坏情况下需要存储n个元素,因此空间复杂度为O(n)。
每周末更新一两个知识点 💪