题目:
在一次聚会中,你想找出来聚会的人中所有的名人,名人就是别人认识他,而他不认识别人。现在在聚会的k个人中,输入邻接矩阵adj[i][j]用来表示他们之间的关系,其中 adj[i][j]=1表示i认识j,adj[i][j]=0表示i不认识j。
输入: 第一行输入k,表示一共有k个人,编号从1到n
之后k行输入邻接矩阵
输出; 输出所有名人的编号,没有名人则输出-1
输入示例: 输出示例
3 2
0 1 0
0 0 0
1 1 1
分析:拿到这题,最简单的方法就是对于每个人i,进行遍历,在i确定时,再遍历j,进行i和j之间的关系判断,最后找到名人,这个方法时间复杂度达到O(n^2),不是很有效率,这时我们就想能否找到一个时间复杂度只有O(n)的解决方案,答案是有的,而且不止一个,现在一一道来:
想要进行优化,我们首先要知道一个问题,名人最多只有一个,不可能同时存在两个名人,这是很容易想的,我就不过多废话,并且在adj[i][j]=0,我们知道i不认识j,那么根据名人的定义,j肯定不是,如果adj[i][j]=1,那么也很容易得到i不是名人,那么这样在无论adj[i][j]是0还是1的情况下,都能排除其中一个人不是名人,那么只需要在剩下的人中进行查找即可.
那么我们就可以将目前比较的人i,j保存在数组的前两个元素中,每次判断后都从将数组的最后一个人替换掉刚才不是名人的那个人,直到判断到只剩下一个人,那么再对那个人进行具体的判断,具体源码见下:
#include<iostream>
using namespace std;
#define Max 10000
int n;
int adj[Max][Max];
int findFamous()
{
int person[Max]; // person存放还未判断是不是名人的数组
for(int i=0;i<n;i++)
{
person[i]=i;
}
int count=n;
while(count>1)
{
if(adj[0][1]) //第一个人认识第二个人,说明第一个人不是名人
person[0]=person[--count]; //从 person中删除第一个人
else
person[1]=person[--count]; //第二个人认识第一个人,说明第二个人不是名人
//从 person中删除第二个人
}
//剩下只有person[0]才有可能是名人,再进行完全判断一下
for(int i;i<n;i++)
{
if(person[0]!=i && (adj[person[0]][i]==1 || adj[i][person[0]]==0))
return -1;
}
return person[0]+1;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
cin>>adj[i][j];
}
cout<<findFamous()<<endl;
return 0;
}
下面的一个方法和上面的思想类似,只是
i,j一开始分别指向数组的第一个人和最后一个人,在比较后,那一边的人不是名人,那么i或者j指针就分别向中间移动,直到最后i=j.
#include<iostream>
using namespace std;
#define Max 10000
int n;
int adj[Max][Max];
int findFamous()
{ //i,j的意义:
int i=0,j=1; //i,j表示有可能是名人的两个人
//且下标为0~i-1的人不是名人;下标i+1~j-1的不是名人
while(j<n)
{
if(adj[i][j]) //i认识j,说明i不是名人,那么这时 标为0~i-1和下标i+1~j-1再加上
//i不是名人,则0到j-1都不是名人,则i=j,这时还是满足i,j的意义
i=j;
//在i认识j时,直接j++,就行,i+1~j-1范围扩大一个
j++; //但是这里要注意的是,即便i认识j,j还是要自加
//这时i,j意义还是满足的
}
//i,j不断增大,最终j>=n超出下标范围,i表示最终有可能的名人
//剩下只有person[0]才有可能是名人,再进行完全判断一下
for(int k=0;k<n;k++)
{
if(k!=i && (adj[i][k]==1 || adj[k][i]==0))
return -1;
}
return i+1;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
cin>>adj[i][j];
}
cout<<findFamous()<<endl;
return 0;
}
最后的一个方法则有点绕,i<j,且i,j的意义是:i,j表示有可能是名人的两个人 ,且下标为0~i-1的人不是名人;下标j+1~n-1的不是名人 ,具体的i,j的变化趋势见如下代码:
#include<iostream>
using namespace std;
#define Max 10000
int n;
int adj[Max][Max];
int findFamous()
{ //i,j的意义:
int i=0,j=n-1; //i,j表示有可能是名人的两个人
//且下标为0~i-1的人不是名人;下标j+1~n-1的不是名人
while(i<j)
{
if(adj[i][j]) //i认识j,说明i不是名人,那么这时 i++,则i左侧不是名人的范围扩大
i++;
else //在i认识j时,直接j--,就行,j+1~n-1范围扩大一个
j--;
}
//最终i=j,由逻辑上可以知道,最对只有一个名人,下标为i,则最终i左边0~i-1和i右边i+1~n-1都不是名人
for(int k=0;k<n;k++)
{
if(k!=i && (adj[i][k]==1 || adj[k][i]==0))
return -1;
}
return i+1;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
cin>>adj[i][j];
}
cout<<findFamous()<<endl;
return 0;
}
PS:上学后,事情多,争取每周两篇左右,每篇一道算法题目的分析~