【PAT甲级真题】- Stack (30)

题目来源

Stack (30)

注意点

  • pop要输出值

题目描述

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.

输入描述:

Each input file contains one test case. For each case, the first line contains a positive integer N (<= 10 5 10^5 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.

输出描述:

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.

输入例子:

复制代码
17
Pop
PeekMedian
Push 3
PeekMedian
Push 2
PeekMedian
Push 1
PeekMedian
Pop
Pop
Push 5
Push 4
PeekMedian
Pop
Pop
Pop
Pop

输出例子:

复制代码
Invalid
Invalid
3
2
2
1
2
4
4
5
3
Invalid

思路简介

就是平衡搜索数 AVL 的模板

查找中位数即查找第 k 小

后续我会补上AVL的模板详解

遇到的问题

无,写完平衡树一遍过

代码

cpp 复制代码
/**
 * https://www.nowcoder.com/pat/5/problem/4110
 * multiset
 */
#include<bits/stdc++.h>
using namespace std;

// AVL 树节点结构
struct Node {
    int val;        // 节点值
    int h = 1;      // 子树高度(叶子节点高度为1)
    int sz = 1;     // 子树节点总数(包括自身)
    Node *l = nullptr;  // 左孩子
    Node *r = nullptr;  // 右孩子
    Node(int v) : val(v) {}
};

// 获取节点高度(空节点高度为0)
int h(Node* x) { return x ? x->h : 0; }

// 获取子树大小(空节点大小为0)
int sz(Node* x) { return x ? x->sz : 0; }

// 更新节点的高度和大小(必须在其孩子更新后调用)
void upd(Node* x) {
    if (!x) return;
    x->h = 1 + max(h(x->l), h(x->r));   // 高度 = 1 + 左右子树较大高度
    x->sz = 1 + sz(x->l) + sz(x->r);    // 大小 = 自身 + 左子树大小 + 右子树大小
}

// 右旋:将 y 的左孩子 x 提上来
Node* rotR(Node* y) {
    Node* x = y->l;
    y->l = x->r;    // x 的右子树变成 y 的左子树
    x->r = y;       // y 成为 x 的右孩子
    upd(y);         // 先更新 y(现在在下方)
    upd(x);         // 再更新 x(新的根)
    return x;
}

// 左旋
Node* rotL(Node* x) {
    Node* y = x->r;
    x->r = y->l;    // y 的左子树变成 x 的右子树
    y->l = x;       // x 成为 y 的左孩子
    upd(x);
    upd(y);
    return y;
}

// 计算平衡因子)
int bf(Node* x) { return h(x->l) - h(x->r); }

// 平衡操作
Node* bal(Node* x) {
    if (bf(x) > 1) {                // 左子树过高
        if (bf(x->l) < 0)           // LR 型:先左旋左孩子
            x->l = rotL(x->l);
        return rotR(x);             // LL 或 LR 最后都右旋
    }
    if (bf(x) < -1) {               // 右子树过高
        if (bf(x->r) > 0)           // RL 型:先右旋右孩子
            x->r = rotR(x->r);
        return rotL(x);             // RR 或 RL 最后都左旋
    }
    return x;   // 平衡,无需旋转
}

// 插入值为 v 的节点
Node* ins(Node* x, int v) {
    if (!x) return new Node(v);     // 空位,创建新节点
    if (v < x->val)                 // 小于当前值则递归左子树
        x->l = ins(x->l, v);
    else                            // 大于等于则递归右子树(支持重复元素放右边)
        x->r = ins(x->r, v);
    upd(x);                         // 更新高度和大小
    return bal(x);                  // 回溯时平衡
}

// 找子树最小节点(中序后继)
Node* minN(Node* x) {
    while (x->l) x = x->l;
    return x;
}

// 删除值为 v 的节点(递归)
Node* del(Node* x, int v) {
    if (!x) return nullptr;
    if (v < x->val)
        x->l = del(x->l, v);
    else if (v > x->val)
        x->r = del(x->r, v);
    else {  // 找到要删除的节点
        if (!x->l || !x->r) {           // 只有一个孩子或没有孩子
            Node* t = x->l ? x->l : x->r;
            delete x;
            return t;
        }
        // 有两个孩子:用后继节点值替换,再递归删除后继
        Node* s = minN(x->r);
        x->val = s->val;
        x->r = del(x->r, s->val);
    }
    upd(x);
    return bal(x);
}

// 查询第 k 小的元素(k 从 1 开始)
int kth(Node* x, int k) {
    int ls = sz(x->l);                  // 左子树大小
    if (k <= ls)                        // 在左子树中
        return kth(x->l, k);
    if (k == ls + 1)                    // 就是当前根
        return x->val;
    return kth(x->r, k - ls - 1);       // 在右子树中
}

// 计算中位数
int median(Node* root) {
    int n = sz(root);
    if (n == 0) return 0;               // 可自行处理空树情况
    if (n % 2 == 1)                     // 奇数个
        return kth(root, (n + 1) / 2);
    // 偶数个:取中间两个数的平均值
    return kth(root, n / 2);
}

void solve(){
    int n;cin>>n;
    stack<int>s;
    Node* root = nullptr;
    for(int i=0;i<n;++i){
        string op;
        cin>>op;
        if(op=="Push"){
            int n;
            cin>>n;
            root=ins(root,n);
            s.push(n);
        }

        if(op=="Pop"){
            if(!s.size()){
                cout<<"Invalid\n";
                continue;
            }
            int v=s.top();
            s.pop();
            root=del(root,v);
            cout<<v<<'\n';
        }

        if(op=="PeekMedian"){
            if(!s.size()){
                cout<<"Invalid\n";
                continue;
            }
            cout<<median(root)<<'\n';
        }
    }
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    //fstream in("in.txt",ios::in);cin.rdbuf(in.rdbuf());
    int T=1;
    //cin>>T;
    while(T--){
        solve();
    }
    return 0;
}
相关推荐
伊玛目的门徒1 小时前
试用leetcode之典中典 二数之和问题
java·算法·leetcode
Jerry1 小时前
LeetCode 226. 翻转二叉树
算法
想做小南娘,发现自己是女生喵2 小时前
【无标题】
数据结构·算法
Kx_Triumphs4 小时前
HDU4348 To the moon(主席树区间修改模板)
算法·题解
旖-旎4 小时前
《LeetCode647 回文子串 || LeetCode 5 最长回文子串》
c++·算法·leetcode·动态规划·哈希算法
轻颂呀5 小时前
约瑟夫环问题
算法
凤凰院凶涛QAQ7 小时前
《Java版数据结构 & 集合类剖析》栈与队列:“push/pop 是栈的灵魂,offer/poll 是队列的骨架——四组 API,两种人生”
java·开发语言·数据结构
科技大视界7 小时前
投资AI项目,传统尽调不够用了——李章虎律师拆解算法、数据、算力三大雷区
人工智能·算法·数据挖掘
郝学胜-神的一滴7 小时前
算法实战:最小k个数——大顶堆的优雅解法
开发语言·数据结构·c++·python·程序人生·算法·排序算法