第二节:栈相关(二叉树展开为链表、逆波兰表达式、两栈实现队列结构)
一. 二叉树展开为链表
1. 题目描述
给你二叉树的根结点 root ,请你将它展开为一个单链表: 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。(补充:先序遍历指的是 访问根节点→先序遍历左子树→先序遍历右子树)
进阶:你可以使用原地算法(O(1) 额外空间)展开这棵树吗? 即不额外创建空间
详见: https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/description/
难度:【中等】
2. 思路分析
(经典的栈解决问题,后进先出, 用数组模拟: push尾部插入 pop尾部删除)
(1). 默认root节点入栈
(2). while遍历栈,出栈,并设置左右节点
(3). 将当前节点的右 左节点依次入栈, 前提要判空 (先右,后左)
(4). 声明一个previousNode节点, 用来承上启下进行设置左右节点
3. 代码实操
function flatten(root: TreeNode | null): void {
//边界判断
if (root == null) return;
//1.用数组模拟栈
let stack: TreeNode[] = [root];
//2. 遍历栈执行相关业务
let previousNode: TreeNode | null = null;
while (stack.length > 0) {
let currentNode: TreeNode = stack.pop()!;
if (previousNode) {
previousNode.left = null;
previousNode.right = currentNode;
}
//将右子树和左子树分别依次入栈
if (currentNode.right) {
stack.push(currentNode.right);
}
if (currentNode.left) {
stack.push(currentNode.left);
}
//存储当前节点, 便于后续设置左右节点
previousNode = currentNode;
}
}
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = val === undefined ? 0 : val;
this.left = left === undefined ? null : left;
this.right = right === undefined ? null : right;
}
}
二. 逆波兰表达式
1. 题目描述
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数(求的是结果!!)。
注意:
有效的算符为 '+'、'-'、'*' 和 '/' 。 (注意:/ 符号是除 不是除以!)
每个操作数(运算对象)都可以是一个整数或者另一个表达式。
两个整数之间的除法总是 向零截断 。
表达式中不含除零运算。
输入是一个根据逆波兰表示法表示的算术表达式。
答案及所有中间计算结果可以用 32 位 整数表示。
详见:https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/
难度:【中等】
2. 什么是逆波兰表达式?
(1).逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
(2).逆波兰表达式主要有以下两个优点:
去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
3. 思路分析
(利用数组模拟栈, 后进先出, push 和 pop方法)
适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
(1) 遍历 tokens 数组,遇到数字时将其压入栈中,遇到运算符时从栈中弹出两个数字并进行相应的计算,将计算结果再压入栈中。
(2) 最后栈中剩下的数字就是表达式的最终结果value。
特别注意:
A. 假设依次出栈的是num1、num2, 应该是 num2-num1 和 num2/num1 不能颠倒, 对于+和* 则没有影响。
B. math.trunc 和 math.floor 的主要区别:
a. 处理正数的时候,没有区别,都是取下整
b. 处理负数的时候,trunc直接截断,不进行四舍五入
print(math.floor(-3.7)) # 输出 -4
print(math.trunc(-3.7)) # 输出 -3
4. 代码实操
/**
* 求逆波兰表达式的值
* @param tokens 逆波兰表达式
* @returns 最后求的值
*/
function evalRPN(tokens: string[]): number {
let stack: number[] = []; //数组模拟栈
for (const item of tokens) {
if (item === '+') {
let num1 = stack.pop()!;
let num2 = stack.pop()!;
let res = num1 + num2;
stack.push(res);
} else if (item === '-') {
let num1 = stack.pop()!;
let num2 = stack.pop()!;
let res = num2 - num1; //注意:这里是 num2 - num1 ,而不是 num1-num2
stack.push(res);
} else if (item === '*') {
let num1 = stack.pop()!;
let num2 = stack.pop()!;
let res = num1 * num2;
stack.push(res);
} else if (item === '/') {
let num1 = stack.pop()!;
let num2 = stack.pop()!;
let res = Math.trunc(num2 / num1); //注意:这里是 num2/num1 ,而不是 num1/num2
stack.push(res);
} else {
stack.push(Number(item));
}
}
return stack.pop()!;
}
//测试
console.log(evalRPN(['2', '1', '+', '3', '*'])); //9
console.log(evalRPN(['4', '13', '5', '/', '+'])); //6
console.log(evalRPN(['10', '6', '9', '3', '+', '-11', '*', '/', '*', '17', '+', '5', '+'])); //22
三. 两栈实现队列结构
1. 题目描述
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。
(若队列中没有元素,deleteHead 操作返回 -1 )
(通俗的说就是实现一个标准队列插入和删除而已,但要用两个栈实现)
2. 思路分析
使用两个栈 s1 和 s2:s1 用来插入元素,s2 用来删除元素
(1) 其中插入元素只需要将元素插入 s1 即可
(2) 删除元素则需要分情况:
✓ 如果 s2 不为空,直接弹出 s2 的栈顶元素;
✓ 如果 s2 为空,将 s1 中的元素逐个弹出并压入 s2,然后弹出 s2 的栈顶元素;
3. 代码实操
class CQueue {
private stack1: number[] = []; //用于插入
private stack2: number[] = []; //用于删除
appendTail(val: number): void {
this.stack1.push(val);
}
deleteHead(): number {
if (this.stack2.length > 0) {
return this.stack2.pop()!;
} else if (this.stack1.length > 0) {
while (this.stack1.length > 0) {
this.stack2.push(this.stack1.pop()!);
}
return this.stack2.pop()!;
} else {
return -1;
}
}
}
//测试
let queue = new CQueue();
//插入
let test: number[] = [1, 2, 3, 4];
for (const item of test) {
queue.appendTail(item);
}
//删除
//下面依次输出 1,2,3,4
console.log(queue.deleteHead());
console.log(queue.deleteHead());
console.log(queue.deleteHead());
console.log(queue.deleteHead());
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。