1057. Stack (30)-PAT甲级真题 (二分妙用 or 线段树)

题意

首先给出操作次数n,然后每次进行三种操作中的一种,1:push x, 2:pop, 3:取mid。也就是1将x压入栈顶,2弹出栈顶元素并输出,输出栈元素的中位数(若元素个数为偶数则取较小值(n / 2))。

思路1 - 二分法

  • 通过一个vector模拟栈,插入删除时间复杂度O(1)
  • 难点在找中位数,第一个想法是用map记录出现次数,然后根据map顺序的性质,从小到大顺序数,TLE17分。
  • 找到一个很妙的解法(解法1),思路是定义一个全局变量mid记录中位数,每次插入、删除的时候更新mid,peekmid的时候直接输出。
  • 更新操作的时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n):通过两个几乎等长的multiset记录stack的前半段和后半段。保证前半段长度大于等于后半段,长度差最大为1。每次取mid = 前半段最后一个元素.

思路2 - 线段树

柳神的线段树(解法2)没看懂,过两天抽空在acwing上学。

总结

  • multiset的用法:
    multiset.erase(int x)的时间复杂度是 O ( k ⋅ l o g 2 n ) O(k \cdot log_2n) O(klog2n),找到第一个x并依次删除后续x,底层是红黑树。
    multiset.erase(iterator x)的时间复杂度是 O ( l o g 2 n ) O(log2_n) O(log2n),随机找到要删的元素,并删除、调整。
  • iterator 只能++、–;

解法1 - 二分

#include<bits/stdc++.h>
using namespace std;
int n, mid;
vector<int> rec;
multiset<int> l, r;
void sort(){
	if(l.size() < r.size()){
		l.insert(*r.begin());
		r.erase(r.begin());
	}
	else if(l.size() > r.size() + 1){
		r.insert(*(--l.end()));
		l.erase(--l.end());
	}
	if(l.size()) mid = *(--l.end());
}
int main(){
	cin>>n;
	while(n--){
		string input; cin>>input;
		if(input == "Pop"){
			if(!rec.size()) puts("Invalid");
			else{
				int t = rec.back();
				printf("%d\n", t);
				t <= mid ? l.erase(l.find(t)) : r.erase(r.find(t));
				rec.pop_back();
				sort();
			}
		}else if(input == "Push"){
			int b; scanf("%d", &b);
			rec.push_back(b);
			(!l.size() || b <= mid) ? l.insert(b) : r.insert(b);
			sort();
		}else
			!rec.size() ? puts("Invalid") : printf("%d\n", mid);
	}
	return 0;
} 

解法2 - 线段树

#include <cstdio>
#include <stack>
#define lowbit(i) ((i) & (-i))
const int maxn = 100010;
using namespace std;
int c[maxn];
stack<int> s;
void update(int x, int v) {
    for(int i = x; i < maxn; i += lowbit(i))
        c[i] += v;
}
int getsum(int x) {
    int sum = 0;
    for(int i = x; i >= 1; i -= lowbit(i))
        sum += c[i];
    return sum;
}
void PeekMedian() {
    int left = 1, right = maxn, mid, k = (s.size() + 1) / 2;
    while(left < right) {
        mid = (left + right) / 2;
        if(getsum(mid) >= k)
            right = mid;
        else
            left = mid + 1;
    }
    printf("%d\n", left);
}
int main() {
    int n, temp;
    scanf("%d", &n);
    char str[15];
    for(int i = 0; i < n; i++) {
        scanf("%s", str);
        if(str[1] == 'u') {
            scanf("%d", &temp);
            s.push(temp);
            update(temp, 1);
        } else if(str[1] == 'o') {
            if(!s.empty()) {
                update(s.top(), -1);
                printf("%d\n", s.top());
                s.pop();
            } else {
                printf("Invalid\n");
            }
        } else {
            if(!s.empty())
                PeekMedian();
            else
                printf("Invalid\n");
        }
    }
    return 0;
}

题目

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值