第二节:栈相关(二叉树展开为链表、逆波兰表达式、两栈实现队列结构)

一. 二叉树展开为链表

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 )

   (通俗的说就是实现一个标准队列插入和删除而已,但要用两个栈实现)

     leetcode:不见了

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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2024-03-04 09:01  Yaopengfei  阅读(21)  评论(0编辑  收藏  举报