题解 | #连续段的中数#

连续段的中数

http://www.nowcoder.com/practice/c14a99834969462bb412cd541870b57a

题意整理:

题目给出一个正整数数组,要求求出其中的长度至少为k的连续子数组的最大中数。 一个数组的中数定义为数组中最大的满足数组中最少一半值大于等于该值的数。

对于一个已经排序完成的数组,简单的分析这个数组的中数。

对于含有奇数个数字的数组a[0:2i]a[0:2i]a[0:2i],显然a[i]为中数。对于a[i],大于等于该值的元素有i+1个,满足条件;而大于等于a[i+1]的只有i个,不满足最少一半。 对于含有偶数个数子的数组a[0:2i+1]a[0:2i+1]a[0:2i+1],其中数为a[i+1]。对于a[i+1],大于等于该数的元素有i+1个,满足条件,而对于a[i+2],大于等于该数的有i个,不满足条件。

对于上述分析,可以发现对于一个长度为len的数组a,其中数必定为a[len/2]a[len/2]a[len/2],对于奇数个元素的数组,这是数组最中间的元素;对于偶数个元素的数组,这是中间位置偏右的第一个元素。

方法一:枚举+排序

核心思想:

可以枚举每一个满足条件的区间,排序后确定中数,然后取得最大的中数即可。具体做法即枚举起点,然后从最小的满足条件的区间逐渐向后枚举到数组尾部,得到区间后对其排序并取得a[len/2]对答案进行更新。

核心代码:

class Solution {
public:
    int solve(int n, int k, vector<int>& a) {
        int ans = 0;
        for(int i = 0; i < n; ++i) {//枚举起点
            for(int j = i + k; j <= n; ++j) {//枚举终点(不包括)
                vector<int> res(a.begin() + i, a.begin() + j);//构造
                sort(res.begin(), res.end());
                int p = res[res.size() / 2];//取得中数
                ans = max(ans, p);
            }
        }
        return ans;
    }
};

复杂度分析:

时间复杂度:O(n3logn)O(n^3logn)O(n3logn)。二重循环开销为O(n2)O(n^2)O(n2),排序开销为O(nlogn)O(nlogn)O(nlogn),总开销为O(n3logn)O(n^3logn)O(n3logn)

空间复杂度:O(n)O(n)O(n),即开辟的用于排序的临时数组

方法二:枚举+双优先队列

核心思想:

方法一的时间复杂度很高,主要原因在于每次枚举区间的新终点都需要进行排序,实际上对于固定起点的区间枚举,是一个数据流中获取中位数的经典题目,可以采用双优先队列的方法,使得每个新元素加入时中位数的获取时间复杂度为O(1)而不是O(nlogn)O(nlogn)O(nlogn)

双优先队列的思想很简单,因为需要获取的只是中位数,其他数字的顺序并不重要,所以只需要保证能够准确的获取中位数即可,所以可以使用一个小顶堆和一个大顶堆,小顶堆保存较大的一半数字,大顶堆保存较小的一半,插入元素时如果不满足条件就从一个队列弹出一个元素到另一个队列。取得中位数时,根据情况在两队列的队首根据元素数量进行判断即可。 alt

核心代码:

class Solution {
public:
    priority_queue<int, vector<int>, less<int>> queMin;//存放小的一半
    priority_queue<int, vector<int>, greater<int>> queMax;//存放大的一半
    //添加数字的函数
    void addNum(int num) {
        if (queMin.empty() || num <= queMin.top()) {
            //加入较小的队列,如果队列不平衡则弹出数字到较大队列
            queMin.push(num);
            if (queMax.size() + 1 < queMin.size()) {
                queMax.push(queMin.top());
                queMin.pop();
            }
        } else {
            //加入较大的队列,如果队列不平衡则弹出数字到较小队列
            queMax.push(num);
            if (queMax.size() > queMin.size()) {
                queMin.push(queMax.top());
                queMax.pop();
            }
        }
    }
    
    int solve(int n, int k, vector<int>& a) {
        int ans = 0;
        for(int i = 0; i < n; ++i) {//枚举起点
            queMin = priority_queue<int, vector<int>, less<int>>();
            queMax = priority_queue<int, vector<int>, greater<int>>();
            for(int j = 0; j < k - 1; ++j) {
                addNum(a[i + j]);
            }
            //相当于在数据留中寻找中位数
            for(int j = i + k - 1; j < n; ++j) {
                addNum(a[j]);
                //如果较小队列长度大,那么说明有奇数个数字,中位数是较小队列首元素
                //如果两队列等长,那么说明有两个符号的数字,取得较大队列首元素(更大)
                int p= queMin.size() > queMax.size() ? queMin.top(): queMax.top();
                ans = max(ans, p);
            }
        }
        return ans;
    }
};

复杂度分析:

时间复杂度:O(n2)O(n^2)O(n2),枚举起点开销为O(n)O(n)O(n),指定起点后,其后的每个元素进入队列一次,开销为O(n)O(n)O(n),取得中位数开销为O(1)O(1)O(1),故总开销O(n2)O(n^2)O(n2)

空间复杂度:O(n)O(n)O(n),即优先队列开销

全部评论

相关推荐

好久没来牛客了,今天面试了一个实习生,感觉对方形象乱糟糟的,头发像鸡窝,像刚睡醒就来面试了,第一印象直接大打折扣,感觉我没有受到应有的尊重,再加上对方业务能力也一般,我直接挂掉;大家面试的时候还是好好收拾一下自己吧,争取给面试官留下个好印象,面试这东西还是存在眼缘的
MinJerous:更在乎本质,应该看候选人是否和岗位需要的能力匹配。洗脸/不洗头都无所谓吧,说不定人家刚刚通宵准备,就是为了这场面试呢?你挂掉他核心原因还是他能力不行,而不是形象。就算形象好点,能力不行你敢给过吗,不怕后面+1质疑你
点赞 评论 收藏
分享
大方的大熊猫准备进厂:1.教育背景:你希望从事什么专业的工作你的主修课就是什么;成绩优秀是你应该做的,没什么可描述的,成绩不优秀也许人家在大学忙着创业呢?(成绩优秀不一定是好事,只能说明多元化的大学你上成了高中,没有真正上明白大学,反而体现了你死板,不爱社交,没有别的突出能力) 2.实践经历:你想表达的意思没有说清楚。你是说你会个性化服务,还是你有实习经历。如果没有带来,经济收益,表彰,更好的发展前景,那你还不如说说提升了自己哪些技能。你说有人给你送锦旗我都能明白你优秀,但是你说你会xxxx,你说这话谁信,证据呢。 3.入伍经历:你描述的就是你的工作职责或者你应该做的,并没有体现出来你把这个事情做好了,而且入伍经历并不能证明你能干好你要应聘的工作,不如只写经历其余所有内容都不写。 4.荣誉技能:重点突出一下,但不要过多描述,这些荣誉的含金量懂得都懂。 重点:你要应聘什么工作(具体岗位,实习生不具体),你的期望薪资
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务