1. 孩子们的游戏(圆圈中剩下的数)
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1) 。如果没有小朋友,请返回-1 。
分析:可以采用 list 双向链表的数据结构,每次从 list 中 erase 掉一个小朋友。也可以用递归方法。
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if(n<=0) return -1;
list<int> lt;
for(int i=0; i<n; ++i)
lt.push_back(i);
list<int>::iterator ltit;
int index=0;
while(n>1)
{
index = (index+m-1)%n;
ltit = lt.begin();
advance(ltit, index);//定位list的第index个成员
//advance函数要求第一个迭代器参数是左值,方便赋值
lt.erase(ltit);
--n;
}
return *(lt.begin());
}
};
2. 数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
分析:用另外一个数据结构存放已经访问过的数字,如果该数字被再次访问,就说明遇到了重复的数字,空间复杂度是 O(n)。如果采用 In-place方案,空间复杂度变为 O(1)。
//方案一
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
vector<int> num2pos(length,-1);//num2pos的第n个位置存放数字n在numbers数组中的位置
for(int i=0; i<length; ++i)
{
if(num2pos[numbers[i]]==-1)
num2pos[numbers[i]]=i;
else{
//这样写是错误的:duplication=&numbers[num2pos[numbers[i]]];因为numbers是拷贝过来的数组,duplicate函数结束后就会被释放。
*duplication = numbers[i];
return true;
}
}
duplication = nullptr;
return false;
}
};
//方案二:In-place
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
for(int i=0; i<length; ++i)
{
int j=i;
while(numbers[j]!=j)
{
if(numbers[j]!=numbers[numbers[j]])
swap(numbers[j],numbers[numbers[j]]);
else
{
*duplication = numbers[j];
return true;
}
}
}
return false;
}
};
3. 剪绳子
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
分析:因为不知道到底要将绳子分为多少段,也不知道每段要剪成多长,所以最开始的想法是用动态规划做。后来发现剪绳子是有一定规律的。假设绳子长为 20,可以将绳子剪为 10 和 10,此时乘积为 100,但如果将绳长 10 继续划分,还将得到 5+5=10,而5*5>10,所以继续划分是有好处的。但划分到什么时候最佳呢?我们发现,绳长小于等于 4 时,继续划分并不能够增大乘积。而且如果有一根绳子长度被分割为 1,就会非常不利于最后的乘积大小。尝试几个例子后,发现绳子最好被剪为 3、3、……、3、2或4 的几段。绳长对 3 取余,有可能为 0或1或2,如果取余结果是 1,那么就分割为一系列 3 和一个 4,如果取余结果是 2,就分割成一系列 3 和一个 2,否则分割为一系列 3。
class Solution {
public:
int cutRope(int number) {
if(number==2) return 1;
if(number==3) return 2;//m>1
int ret=1, resid;
if((resid=(number%3))!=0){
if(resid==1)
resid += 3;
number -= resid;
ret *= resid;
}
while(number){
ret *= 3;
number -= 3;
}
return ret;
}
};
4. 求 1+2+3+…+n
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
分析:利用递归和短路原理。
class Solution {
public:
int Sum_Solution(int n) {
int sum = n;
bool flag = n && (sum += Sum_Solution(n-1));
return sum;
}
};
5. 数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用 Insert() 方法读取数据流,使用 GetMedian() 方法获取当前读取数据的中位数。
分析:求一个数组的中位数时,可以采取多种方式。
第一种是用无序容器存储数据,用 sort 函数对无序数组排序,然后取中位数,时间复杂度是 O(nlogn)。
第二种是利用优先队列,将数据分为偏大和偏小的两部分,将偏小的数据存入大顶堆,偏大的数据存入小顶堆,如果数据个数为奇数,此时大顶堆的 top 数据就是中位数,否则,大顶堆和小顶堆的 top 数据之和的平均值就是中位数。具体步骤为,先将无序数据的前一部分存入大顶堆,此时大顶堆的 top 元素一定大于等于中位数;然后将后一部分数据依次与大顶堆 top 元素比较,如果比 top 元素大,那么它就一定属于偏大的一部分数据,直接存入小顶堆;如果小于等于 top 元素,就将当前元素存入大顶堆,再将大顶堆的 top 元素移入小顶堆。时间复杂度同样是 O(nlogn)。
而本题目要得到数据流的中位数,也就是说每得到一个新的数据,我们就要计算一次中位数。此时对于前面的方案一就不太友好了,因为前一次已经对所有数据进行了排序,新来的数据只要直接插入就好,而再次对所有数据进行快速排序,反而会使快排陷入 O(n2) 的最差复杂度。每个数据采用插入排序的时间复杂度为二分查找的 O(logn) 和移动数据的 O(n)。
如果使用堆排序,那么每次使一个数据有序只需要 O(logn)。所以本题目最优的解法应该是用堆排序。
class Solution {
public:
priority_queue<int,vector<int>,greater<int>> smallHeap;
priority_queue<int,vector<int>,less<int>> bigHeap;
int count=0;
void Insert(int num)
{
++count;
if((count&1)==1)
{
if(smallHeap.empty()||num<=smallHeap.top())
bigHeap.push(num);
else{
bigHeap.push(smallHeap.top());
smallHeap.pop();
smallHeap.push(num);
}
}
else{
if(num>=bigHeap.top())
smallHeap.push(num);
else{
smallHeap.push(bigHeap.top());
bigHeap.pop();
bigHeap.push(num);
}
}
}
double GetMedian()
{
if((count&1)==0)
return (double)(bigHeap.top()+smallHeap.top())/2;
else
return bigHeap.top();
}
};
6. 扑克牌顺子
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
分析:五个数除了 0 之外,不能出现重复的数字,且最大一张牌与最小一张牌的差值不能超过4。
class Solution {
public:
bool IsContinuous( vector<int> numbers ) {
if(numbers.empty()) return false;//numbers有数组为空的可能,就很坑的测试用例
sort(numbers.begin(),numbers.end());
int posNotZero;
for(int i=0; i<5; ++i)
{
if(numbers[i]==0)
{
posNotZero = i+1;
continue;
}
if(i+1<5 && numbers[i]==numbers[i+1]) return false;
}
return numbers.back()-numbers[posNotZero]<5;
}
};