1057. Stack (30) - 树状数组

本文介绍了一种高效实现带有额外PeekMedian操作的栈的方法,该操作返回栈中所有元素的中位数。通过使用树状数组进行优化,文章详细解释了如何处理Push、Pop和PeekMedian命令,并提供了完整的C++实现代码。

题目如下:

Stack is one of the most fundamental data structures, which is based on the principle of Last In First Out (LIFO). The basic operations include Push (inserting an element onto the top position) and Pop (deleting the top element). Now you are supposed to implement a stack with an extra operation: PeekMedian -- return the median value of all the elements in the stack. With N elements, the median value is defined to be the (N/2)-th smallest element if N is even, or ((N+1)/2)-th if N is odd.

Input Specification:

Each input file contains one test case. For each case, the first line contains a positive integer N (<= 105). Then N lines follow, each contains a command in one of the following 3 formats:

Push  key
Pop
PeekMedian

where key is a positive integer no more than 105.

Output Specification:

For each Push command, insert key into the stack and output nothing. For each Pop or PeekMedian command, print in a line the corresponding returned value. If the command is invalid, print "Invalid" instead.

Sample Input:
17
Pop
PeekMedian
Push 3
PeekMedian
Push 2
PeekMedian
Push 1
PeekMedian
Pop
Pop
Push 5
Push 4
PeekMedian
Pop
Pop
Pop
Pop
Sample Output:
Invalid
Invalid
3
2
2
1
2
4
4
5
3
Invalid


这个题目我最初用的是string、stringstream和vector来做,发现会严重超时,后来在网上参考了和山米兰的解法,发现他的方法很有技巧,分析如下:

①对命令的解析,只看第二位,如果是o,说明是Pop,如果是e,说明是PeekMedian,否则是push,是push则应当再读入一次数字,这比用getline要好的多,因为getline还需要排除第一个输入的N。

②求中位数的思想,不是排序找中间的值,而是通过统计从1开始的每个元素的个数放到数组C中,这样从前到后,数组C的子列和为题目要求的位置时,拿到的就是中位数。

③求子列和的思想,因为是从前到后的前缀和,可以利用树状数组,下面的代码利用add实现了添加和删除两种操作,利用value的不同,1表示添加,2表示删除。树状数组的基本思想就是数组C中不同元素管辖不同的区域,如果要添加一个元素,则所有满足区域条件的位置都要+value,反之如果删除,所有满足条件的区域都要-value。本题要求的是统计1~100000的元素个数,因此value=+1或者-1。

④求子列和为题目要求的值,利用二分查找。

#include<stdio.h>
#include<cstring>
#include<iostream>
#include<string>
using namespace std;

const int N=100001;
int c[N];

int lowbit(int i){
	return i&(-i);
}

void add(int pos,int value){
	while(pos<N){
		c[pos]+=value;
		pos+=lowbit(pos);
	}
}

int sum(int pos){
	int res=0;
	while(pos>0){
		res+=c[pos];
		pos-=lowbit(pos);
	}
	return res;
}

int find(int value){
	int l=0,r=N-1,median,res;
	while(l<r-1){
		if((l+r)%2==0)
			median=(l+r)/2;
		else
			median=(l+r-1)/2;
		res=sum(median);
		if(res<value)
			l = median;
		else
			r = median;

	}
	return l+1;
}

int main(){
	char ss[20];
	int stack[N],top=0,n,pos;
	memset(c,0,sizeof(c));
	scanf("%d",&n);
	while(n--){
		scanf("%s",ss);
		if(ss[1]=='u'){
			scanf("%d",&pos);
			stack[++top]=pos;
			add(pos,1);
		}else if(ss[1]=='o'){
			if(top==0){
				printf("Invalid\n");
				continue;
			}
			int out=stack[top];
			add(out,-1); // 删除元素out
			printf("%d\n",stack[top--]);
		}else if(ss[1]=='e'){
			if(top==0){
				printf("Invalid\n");
				continue;
			}
			int res;
			if(top%2==0)
				res=find(top/2);
			else
				res=find((top+1)/2);
			printf("%d\n",res);

		}else{
			printf("Invalid\n");
		}
	}

	return 0;
}



转载于:https://www.cnblogs.com/aiwz/p/6154112.html

<think>我们有一个树形数组,每个节点可能有子节点(也是一个数组)。我们想为每个节点添加一个新属性。例如,假设原始树节点类型为:interfaceTreeNode{id:number;name:string;children?:TreeNode[];//可选,因为可能没有子节点}现在,我们想为每个节点添加一个属性,比如`depth`(表示节点在树中的深度),或者`isLeaf`(是否是叶子节点)等。我们可以使用递归或迭代的方式遍历树,并为每个节点添加属性。步骤:1.定义一个新的节点类型,包含新属性。2.编写一个函数,接收树数组和当前深度(或其他需要的信息),并返回添加了属性的新树数组(注意:为了避免修改原始数据,我们创建新对象)。例如,添加`depth`属性:新类型:interfaceEnhancedTreeNodeextendsTreeNode{depth:number;}函数实现:我们可以使用深度优先遍历(DFS)或广度优先遍历(BFS)。这里使用DFS递归。注意:由于树可能很大,递归可能导致栈溢出,但对于大多数情况递归是简单明了的。如果树非常大,可以考虑迭代方法。递归函数示例:functionaddDepthToTree(nodes:TreeNode[],currentDepth:number=0):EnhancedTreeNode[]{returnnodes.map(node=>{//为当前节点创建新对象,添加depth属性constenhancedNode:EnhancedTreeNode={...node,depth:currentDepth};//如果有子节点,递归处理子节点,深度加1if(node.children){enhancedNode.children=addDepthToTree(node.children,currentDepth+1);}returnenhancedNode;});}如果我们要添加`isLeaf`属性(叶子节点即没有子节点或子节点数组为空):我们可以修改为:interfaceEnhancedTreeNodeextendsTreeNode{isLeaf:boolean;}函数实现:functionaddIsLeafToTree(nodes:TreeNode[]):EnhancedTreeNode[]{returnnodes.map(node=>{//判断是否是叶子节点:没有children属性,或者children数组为空constisLeaf=!node.children||node.children.length===0;constenhancedNode:EnhancedTreeNode={...node,isLeaf};if(node.children){enhancedNode.children=addIsLeafToTree(node.children);}returnenhancedNode;});}如果我们想同时添加多个属性,可以定义一个包含所有新属性的接口,然后在遍历时计算并赋值。例如,同时添加depth和isLeaf:interfaceEnhancedTreeNodeextendsTreeNode{depth:number;isLeaf:boolean;}functionenhanceTree(nodes:TreeNode[],currentDepth:number=0):EnhancedTreeNode[]{returnnodes.map(node=>{constisLeaf=!node.children||node.children.length===0;constenhancedNode:EnhancedTreeNode={...node,depth:currentDepth,isLeaf};if(!isLeaf){enhancedNode.children=enhanceTree(node.children,currentDepth+1);}returnenhancedNode;});}注意:上面的递归函数每次都会创建一个新的节点对象,不会修改原始节点。调用示例:consttree:TreeNode[]=[...];//原始树constenhancedTree=enhanceTree(tree,0);//从深度0开始但是,如果树的结构非常大,递归可能导致栈溢出。我们可以使用迭代方式(使用栈)来避免。迭代实现(以添加depth为例):functionaddDepthToTreeIteratively(nodes:TreeNode[]):EnhancedTreeNode[]{//创建一个栈,初始节点和深度conststack:{node:TreeNode,depth:number}[]=nodes.map(node=>({node,depth:0}));constresult:EnhancedTreeNode[]=[];while(stack.length>0){const{node,depth}=stack.pop()!;//创建当前节点的增强节点constenhancedNode:EnhancedTreeNode={...node,depth};//如果有子节点,将子节点入栈(注意顺序,如果希望保持原有顺序,可能需要反转)if(node.children&&node.children.length>0){//注意:栈是后进先出,所以我们将子节点反向入栈,这样正序的子节点会先出栈(如果希望按原顺序处理,可以反向入栈)for(leti=node.children.length-1;i>=0;i--){stack.push({node:node.children[i],depth:depth+1});}}//由于我们是从根节点开始,然后子节点,所以结果数组需要按照遍历顺序构建,但通常我们希望得到的是树结构,所以这里我们只是演示如何遍历,并不构建树数组?//实际上,迭代方法构建树结构会复杂一些,因为我们处理节点时,其子节点可能还未处理(这里我们只是将节点转换为增强节点,但子节点数组需要重新构建)//所以迭代方法更适合于不需要构建树结构,而是进行其他操作(如求和、查找等)。对于构建完整的树结构,递归更直观。//但是,如果我们想要构建整个树,我们需要在遍历时重新构建节点和子节点数组。我们可以使用一个映射来存储处理过的节点(按id或其他唯一标识),但树节点可能没有唯一标识。//另一种方法是,我们先将每个节点转换为增强节点(不含子节点),然后再处理子节点关系。但这样需要两次遍历。//因此,对于添加属性并构建新树,递归方法更简单。如果必须使用迭代,我们可以考虑使用队列进行广度优先遍历,并逐层构建。//由于迭代构建树结构相对复杂,这里不展开。在大多数情况下,递归可以满足需求。//所以,我们推荐使用递归方法,除非树特别深(比如深度超过10000层,这种情况很少见)。}//注意:上面的迭代代码只是遍历了节点,但没有构建出树结构(因为节点之间的关系需要重新连接)。所以,我们仍然使用递归方法。returnresult;//这里只是示例,实际需要构建树结构}因此,我们通常选择递归方法。另外,如果树节点有循环引用,递归会陷入无限循环。但树结构通常不会有循环引用(除非是图)。所以确保输入是树。最后,根据用户的需求,可能只需要添加一个属性,也可能需要添加多个。上面的示例展示了如何添加多个属性。现在,假设用户想添加一个属性`path`,表示从根节点到当前节点的路径(节点id数组)。我们可以这样:interfaceEnhancedTreeNodeextendsTreeNode{path:number[];//假设id是number类型}递归实现:functionaddPathToTree(nodes:TreeNode[],parentPath:number[]=[]):EnhancedTreeNode[]{returnnodes.map(node=>{constcurrentPath=[...parentPath,node.id];constenhancedNode:EnhancedTreeNode={...node,path:currentPath};if(node.children){enhancedNode.children=addPathToTree(node.children,currentPath);}returnenhancedNode;});}注意:这里我们传递父节点的路径,然后当前节点的路径是父路径加上当前节点id。总结:根据要添加的属性,设计递归函数,在遍历过程中计算属性值,并创建新节点。由于TypeScript是结构类型系统,我们可以使用扩展(spread)操作符来复制原有属性,然后添加新属性。最后,注意:我们创建的是新树,不会修改原始树节点(符合函数式编程思想,避免副作用)。如果用户希望修改原树(而不是创建新树),那么可以直接在原节点上添加属性(但这样会改变原始数据,一般不推荐)。根据问题,用户要求使用TypeScript,所以我们要注意类型定义。现在,我们根据上述思路,提供一个通用的添加属性的函数示例(添加depth和isLeaf):</think>### 在 TypeScript 中为树形数组添加节点属性 在 TypeScript 中为树形数组的每个节点添加新属性,需要结合**递归遍历**和**类型扩展**。以下是完整解决方案: #### 1. 定义基础节点类型 ```typescript interface TreeNode { id: number; name: string; children?: TreeNode[]; // 可选子节点 } ``` #### 2. 扩展新属性的类型 ```typescript interface EnhancedTreeNode extends TreeNode { depth: number; // 新增属性示例:节点深度 isLeaf: boolean; // 新增属性示例:是否为叶子节点 } ``` #### 3. 递归添加属性的函数 ```typescript function addPropertiesToTree( nodes: TreeNode[], currentDepth: number = 0 ): EnhancedTreeNode[] { return nodes.map(node => { // 计算新属性值 const isLeaf = !node.children || node.children.length === 0; // 创建增强节点 const enhancedNode: EnhancedTreeNode = { ...node, depth: currentDepth, isLeaf }; // 递归处理子节点 if (node.children) { enhancedNode.children = addPropertiesToTree( node.children, currentDepth + 1 // 深度递增 ); } return enhancedNode; }); } ``` #### 4. 使用示例 ```typescript const treeData: TreeNode[] = [ { id: 1, name: "Root", children: [ { id: 2, name: "Child 1" }, { id: 3, name: "Child 2", children: [{ id: 4, name: "Grandchild" }] } ] } ]; const enhancedTree = addPropertiesToTree(treeData); console.log(enhancedTree); ``` #### 关键点说明: 1. **类型安全** 使用 `EnhancedTreeNode` 继承原始类型并添加新属性,确保类型系统完整性[^1]。 2. **不可变数据** 通过展开运算符 `...node` 创建新对象,避免修改原始数据。 3. **递归逻辑** - 叶子节点检测:`!node.children || node.children.length === 0` - 深度计算:每层递归 `currentDepth + 1` 4. **处理空子节点** 使用可选链 `node.children?` 防止运行时错误。 #### 5. 输出示例 ```javascript [ { id: 1, name: "Root", depth: 0, isLeaf: false, children: [ { id: 2, name: "Child 1", depth: 1, isLeaf: true // 无子节点 }, { id: 3, name: "Child 2", depth: 1, isLeaf: false, children: [ { id: 4, name: "Grandchild", depth: 2, isLeaf: true } ] } ] } ] ``` #### 常见应用场景 - 添加路径追踪 `path: string[]` - 计算子树大小 `subtreeSize: number` - 标记选中状态 `selected: boolean` > **提示**:对于超大树的优化,可用迭代代替递归,但递归在大多数场景下更简洁易读。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值