PAT 1129 C++版【updating…】

1.题意

实现一个简单的推荐系统。这里的推荐规则如下:

  • 给出一个总的可推荐的数目n
  • 给出一串访问序列,针对该访问序列得到的访问当前商品时的推荐商品。

那么如何生成这个推荐商品序列呢?主要有两点:

  • 已经访问过的商品的最高频商品
  • 如果频率相同,则优先输出较小下标的商品

2.分析

处理这道题,有直接暴力的思想:

  • 直接将所有的已访问商品放到一个结构体数组中,然后每次输入的时候,进行一个排序,然后输出前k个。
    上面的思想虽然简单,但是容易出现测试用例超时的结果。所以这个时候就需要使用换另外的方法。还有更好的方法么?

我们需要思考的是:上面这个思想的代码实现主要的耗时在哪里?通过思想可以知道,主要的耗时操作是在执行sort()。因为如果有N个输入,那么其时间复杂度就是N^2。所以就会导致超时。

3.代码

#include<cstdio>
#include<algorithm>
#include<iostream>

using namespace std;
struct item{
	int index = -1;//表示当前的这个商品的索引 
	int accNum = 0 ;//每种商品的访问量
	int order ;//访问的顺序 
	int isVisit =0;//表示是否访问过 
};

int cmp(item i1,item i2){
	if(i1.accNum == i2.accNum) return i1.index < i2.index ;
	return i1.accNum > i2.accNum;
}

int main(){
	int num,limit;	 
	scanf("%d %d",&num,&limit);
	item it[num];
	int i,j;
	int accItem;
	int k;
	for(i = 0;i< num;i++){//这里的i只是用作控制输入,不作下标用 
		scanf("%d",&accItem);				
		if(i != 0){
			k = min(limit,i);
			sort(it,it+i,cmp);
			printf("%d: ",accItem);
			for(j = 0;j< k;j++){
				if(j != k - 1) 	{									
					printf("%d ",it[j].index);
				}
				else{
					printf("%d",it[j].index);
				}
 			}printf("\n");			
		}
		
		for(j = 0;j< i;j++){
			if(it[j].index == accItem){				
				it[j].accNum++;
				it[j].order = i;//重置order的值 
				break;
			}
		}
		if(j == i){
			it[i].accNum++;
			it[i].index = accItem;//表示这个结构体中访问的就是accItem这一项
			it[i].order = i;				
		}		
	}	
}

4.测试用例

12 3
3 5 7 5 5 3 2 1 8 3 8 12

5.执行结果

PAT 1129 C++版

6.代码优化

6.1 优化1

针对上面的输出,我认为上述的下面这个for()循环去找一个合适的index时出现了问题。

#include<cstdio>
#include<algorithm>
#include<iostream>

using namespace std;
struct item{
	int index = -1;//表示当前的这个商品的索引 
	int accNum = 0 ;//每种商品的访问量
};

int cmp(item i1,item i2){
	if(i1.accNum == i2.accNum) return i1.index < i2.index ;
	return i1.accNum > i2.accNum;
}

int cmp_index(item i1,item i2){
	return i1.index < i2.index ;	
}

int main(){
	int num,limit;	 
	scanf("%d %d",&num,&limit);
	item it[num];
	int i,j;
	int accItem;
	int k;
	
	//使用二分法寻找合适的数字 
	int high ,low ,mid ;
	for(i = 0;i< num;i++){//这里的i只是用作控制输入,不作下标用 
		scanf("%d",&accItem);				
		if(i != 0){
			k = min(limit,i);
			sort(it,it+i,cmp);
			printf("%d: ",accItem);
			for(j = 0;j< k;j++){
				if(j != k - 1) 	{									
					printf("%d ",it[j].index);
				}
				else{
					printf("%d",it[j].index);
				}
 			}printf("\n");			
		}
		
		//将如下的代码修改成使用2分法 
		//但是存在的问题是:如果使用二分法,那么前提得是:it[i].index 呈一个有序的状态,
		//如果这个非有序序列,那么就无法使用二分法 
		sort(it,it+i,cmp_index);
		high = i-1;
		low = 0;		
		while(low <= high){
			mid = (high + low)/2; //求出mid的值
			//printf("mid = %d\n",mid);
			if(it[mid].index < accItem){
				low = mid + 1; 				
			}
			else if(it[mid].index > accItem){
				high = mid - 1;	
			}
			else break;
		}
		
		if(it[mid].index == accItem){//如果当前输入的值已经存在了			
				it[mid].accNum++;			
		}
		else{
			it[i].accNum++;
			it[i].index = accItem;//表示这个结构体中访问的就是accItem这一项							
		}		
	}	
}

所以我决定使用二分法去解决寻找。修改的代码如下:

sort(it,it+i,cmp_index);
		high = i-1;
		low = 0;		
		while(low <= high){
			mid = (high + low)/2; //求出mid的值
			//printf("mid = %d\n",mid);
			if(it[mid].index < accItem){
				low = mid + 1; 				
			}
			else if(it[mid].index > accItem){
				high = mid - 1;	
			}
			else break;
		}

但是得到的结果仍然是运行超时

6.2 优化2

针对上面的运行超时问题,可以大致猜到修改查找index的值不是时间的关键,时间的关键是:sort()。所以转换另外一种思路。使用set + 运算符重载 + 自定义类型 解决这个问题。

  • 代码如下:
#include<cstdio>
#include<set>
#include<iostream>
#include<cstring>

using namespace std;

struct node{
	int cnt;
	int value;
	
	bool operator<(const node &n)const{
		if(n.cnt == cnt) return n.value > value;
		return n.cnt < cnt; 
	}
};

int rate[50005];

int main(){
	int num,limit;	 
	scanf("%d %d",&num,&limit);
	set<node> s;//用于存放浏览记录的 set 
	int i,j;
	int accItem;
	int k;
	node n;//定义一个临时的node 变量 	 
	memset(rate,0,sizeof(rate));//初始化rate数组
	 
	for(i = 0;i< num;i++){//这里的i只是用作控制输入,不作下标用 
		scanf("%d",&accItem);
		
		//遍历这个it,然后得到其中的值 
		k = min(limit,i);//求出输出的项数 
		int size = s.size();
		//cout << "s.size() = "<<s.size()<<endl	; 
		k = min(k,size);
		if(i > 0){
			//cout << "k = "<<k<<endl;
			cout<<accItem<<": ";	
			set<node>::iterator it;
			for(it = s.begin(),j = 0;it !=s.end() && j<k; it++,j++){			
				if(j!=k-1)	cout << it->value<<" ";//输出值 
				else cout<< it->value<<endl;
			}	
		}
				
		n.value = accItem;		
		n.cnt = rate[accItem];
		rate[accItem]++;//现在访问过这个商品了,需要将其访问次数加一
		 
		if(s.find(n) != s.end()){//说明该商品已经浏览过 
			s.erase(n);//删除这个商品 
			//cout<<"找到了这个商品"<<endl;
			//然后再将这个商品添加到set中 					
		}
		n.cnt = rate[accItem];
		s.insert(n);				
	}
}
  • 测试用例
12 10
3 5 7 5 5 3 2 1 8 3 8 12
  • 关键问题
    但是现在的问题是,如果使用find()函数,那么这个set查找的是什么元素呢?(是value还是cnt呢?)答案是:既非cnt,也不是value。而是一个node 变量。所以需要定义 一个临时的node变量 n。然后判断 n 是否 存在于set中。
    同时需要注意输出的格式问题。这里虽然说是输出的格式问题,但是实际上是因为代码考虑不周导致的错误发生。如下图所示,使用该测试用例得到的结果是:
    PAT 1129 C++版
    很明显的可以看到这个输出是有误的,为什么呢?错误的代码如下:
k = min(limit,i);//求出输出的项数 

我这里只考虑了limit,i两者的大小关系,而没有考虑到set本身的大小。所以导致输出格式错误。修改代码如下:

		int size = s.size(); 
		k = min(limit,size);//求出输出的项数 

因为s.size <= i恒成立,所以应该取set 的size和 limit之间的最小值。

  • 执行结果
    PAT 1129 C++版

相关文章: