腾讯2016招聘笔试:微信红包

腾讯2016招聘笔试:微信红包

春节期间小明使用微信收到很多个红包,非常开心。在查看领取红包记录时发现,某个红包金额出现的次数超过了红包总数的一半。请帮小明找到该红包金额。写出具体算法思路和代码实现,要求算法尽可能高效。
给定一个红包的金额数组gifts及它的大小n,请返回所求红包的金额。
若没有金额超过总数的一半,返回0。

输入:

[1,2,3,2,2],5

输出

2

思路:
刚刚读完这道题,我的第一想法是利用map数组记录每一个红包金额出现的次数,最后在与数组长度的一半进行比较看是否符合题目要求。但是问题要求算法尽可能高效,map底层采用红黑树实现,查找的时间复杂度为O(n)但是对于空间要求要高一些(因为要记录每一个节点的父节点)。因此我又想到了用哈希表实现的unordered_map(查找效率要高于map),但是unordered_map在建立时会比较费时,另外二者都不能直观的体现算法的思想,因此我又参考了一些文献,最后总结了两个较为高效的方法。

方法1:利用partition方法实现部分快速排序找到中位数

算法思想:
分析题目,题目要求找到出现次数大于数组长度一半的红包金额。不难想到,这样一个数一定是序列中的中位数,因此我们只需要找到该数组的中位数,最后判断该中位数出现的次数是否超过数组长度的一半即可。讲到这里,可能就会有部分同学直接采用quick_sort对数组排序求得中位数(下标为2/n的数),但是快排的时间复杂度为O(nlogn),对于使用map的提升并不是很大。这里我们想一下在求第k个小的数时,我们采用了一种部分快速排序的方法(partition)。我们在数组中随机选取一个数,利用partition函数返回该数在初步排序后的数组中的下标,如果下标刚好为2/n,则说明该数便是数组的中位数。假设下标大于2/n说明中位数在该数的左侧,在左侧继续递归查找即可,反之则在该数的右边查找。找到中位数之后在与数组长度的一半比较,看是否符合题意输出即可。

算法复杂度:
该算法的时间复杂度大致为T(n) = n+n/2+n/4+n/8+….+1,在n 的值较大的情况下T(n)趋于2n,也就是O(n)的复杂度,这是在平均情况下,最坏情况时(每次抽取的数在排序后总是在数组的最左边或者最右边)时间复杂度为
T(n) = n+n-1+n-2+n-3+….+1 = n(n-1)/2.也就是O(n*n)。
空间复杂度为O(1)
程序实现:

#include<bits/stdc++.h>
using namespace std;
int partition(int a[],int left,int right);
int getval(int a[],int n){
	int left = 0;
	int right = n-1;
	int mid = (right-left)/2;
	int index = partition(a,left,right);
	while(index!=n/2){
		if(index<n/2){
			left = index+1;
			index=partition(a,left,right);
		}
		else{
			right = index-1;
			index=partition(a,left,right);
		}
	}
	int val = a[index];
	int cnt=0;
	for(int i=0;i<n;i++){
		if(a[i]==val){
			cnt++;
		}
		if(cnt*2>n){
			return val;
		}
	}
	return 0;
}
int partition(int a[],int left,int right){
	int key = a[left];
	int mid = (left+right)/2;
	while(left<right){
		while(right>left&&a[right]>=key){
			right--;
		}
		while(left<right&&a[left]<=key){
			left++;
		}
		int t = a[left];
		a[left] = a[right];
		a[right] = t;
	}
	return left;
}
int main(){
	int a[100];
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	int b = getval(a,n);
	cout<<b<<endl;
	return 0;
}

方法二:加一减一抵消法

算法思想:
红包金额的出现次数大于总数的一半,意味着该红包金额出现的次数大于其他全部金额出现的次数之和。所以我们可以将第一个元素作为标记,设计一个计数变量cnt,遍历数组,如果出现同样的红包金额,则cnt+1;否则cnt-1。当cnt减少至0时意味着该金额不是我们所求金额,替换a[i](i为当前遍历到的下标)作为新的标记,cnt=1,继续遍历。
最后在遍历一遍数组,判断该金额出现的次数是否大于总数的一半,输出即可。

算法复杂度:
时间复杂度:因为总共遍历了两次数组,所以为O(n)
空间复杂度:O(1)

#include<bits/stdc++.h>
using namespace std;
int panduan(int a[],int n,int val);
int getval(int a[],int n){
	if(n<=0)return -1;
	int time = 1;
	int sol = a[0];
	for(int i=1;i<n;i++){
		if(sol==a[i]){
			time++;
		}
		else if(a[i]!=sol){
			if(time-1==0){
				sol=a[i];
				time=1;
                continue;
			}
			time--;
		}
	}
	return panduan(a,n,sol);
}
int panduan(int a[],int n,int val){
	int cnt=0;
	for(int i=0;i<n;i++){
		if(a[i]==val){
			++cnt;
		}
	}
	if(cnt!=0&&cnt>n/2){
		return val;
	}
	else{
		return 0;
	}
}
int main(){
	int a[100];
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	int b = getval(a,n);
	cout<<b<<endl;
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值