Q29 数组中出现次数超过一半的数字
方法一:基于快排中的Partition求得第K大数的方法;空间复杂度0(1),时间复杂度0(n)
因为该数组排序后,超过一半的那个数肯定是数组的中位数,因此,也就是求得数组的第n/2大数;
Partition:
inline void exchange(int& a, int& b)
{
int m = a;
a = b;
b = m;
}
// len 表示整个数组的长度,start, end分别表示当前的开始、结束索引序号
int Partition(int *numbers, int start, int end)
{
// 边界判断
if( numbers==NULL || beg*end < 0 || start > end)
return -1;
int index = rand()%(end - start+1); // 产生一个[0,end - start+1)之间的随机数
index += start; //获得之间随机的序号
exchange(numbers[start],numbers[index]); // 将比较的值放到第一个
int small = start, x = numbers[start];
for (index = start + 1; index <= end; ++index)
{
if(numbers[index] < x) // 只有当前值比small小时才变动index
{
++small;
exchange(numbers[small],numbers[index]);// small保存着比x小的值的最大序号
}
}
exchange(numbers[start],numbers[small]);
return small; // 返回比较的值的位置
}
求超过一增的数:
用partition排序后,看返回的index是否满足条件index = middle,如果满足则返回如果不满足则进行while循环;具体参照后面的Q30
方法二:利用数组的特点
如A[14] = {2, 1, 3, 2, 2, 2, 5, 6, 2, 2, 1, 2, 2, 2}; 2的个数是9;因此可以用一个值对<val, num>来表示出现的数;
下一个与当前值相同,则num+1,不同则-1,如果num=0;则将下一个数放对中,并值num = 1;
如首先,<2, 1>, <1,1>, <3,1>, <2,1> , <2,2>……,最终显然是超过一个半的那个数保留在里面;当然,后面还要进行检测,以免该值不满足要求;
int FindMoreThanHalfNum(int* numbers, int len)
{
if(numbers == NULL || len <= 0)
return 0;
pair<int,int>val;
val.first = 0, val.second = 0;
for(int i = 0; i < len ;++i)
{
if(val.second == 0)
{
val.first = numbers[i];
val.second = 1;
}
else
{
if(val.first == numbers[i]) // 相同则+1
++val.second;
else
--val.second; // 不相同则-1
}
}
return val.first;
}
Q30 最小的K个数
法一: Partirion 存在改变数组顺序的问题
找K小
// 打印最小的K个数
void findMinK(int *data, int beg, int end, int k)
{
if(data == NULL || beg*end < 0 || beg > end)
return;
int index = Partition(data,beg,end); 一次比较
while(index != k-1) // 当index==k时说明index的前面有k-1个小值,即所求
{
if(index > k-1)
index = Partition(data,beg,index-1);
else if(index < k-1)
index = Partition(data,index+1,end);
}
for (int i = 0; i < k;++i)
cout<<data[i]<<" ";
}
法二:用最大堆 0(nlogk) 适用于海量数据
用当前值与堆顶元素比较,如果大于堆顶,则交换堆顶与当前值,否则不处理;
STL中的set与multiset都是基于红黑树来实现的,因此可以作为最大堆;
void BigHeap(int* Data, const int len, const int k)
{
if(Data == NULL || len <= 0)
return;
multiset<int,greater<int>> intSet;
typedef multiset<int,greater<int>>::iterator setItr;
setItr itr = intSet.begin();
for (int i = 0; i < len; ++i)
{
if((intSet.size()) <= k)
intSet.insert(Data[i]);
else
{ // 比较当前值与最大堆顶我元素大小
itr = intSet.begin();
if(*itr > Data[i])
{
intSet.erase(itr);
intSet.insert(Data[i]);
}
}
}
<span style="white-space:pre"> </span> // 打印结果
itr = intSet.begin();
while(itr != intSet.end())
cout<<*itr++<<" ";
cout<<endl;
return;
}
Q31 最大子数组和
最大子数组合的线性O(N)时间算法:
假设已经知道A[i]到A[j]间的最大子数组和为All[i],且包含A[i]的最大子数组和为start[i],所以可以得到A[i-1]~A[j]的最大子数组和是
max{A[i-1], A[i - 1] + star[i], All[i]};
实现:
Q32 从1到n中十进制1出现的次数
// 还没有搞懂
int pow10(int n)
{
int re = 1;
while(n-->0)
re *= 10;
return re;
}
int numberOf1(const char* strN)
{
int first = *strN - '0';
unsigned int length = (unsigned int)strlen(strN);
if(length == 1 && first == 0)
return 0;
if (length == 1 && first > 0)
return 1;
// 最高位
int HighestDigit = 0;
if(first > 1)
HighestDigit = pow10(length-1);
else if(first ==1)
HighestDigit = atoi(strN+1)+1;
int otherDigits = first*(length-1)*pow10(length-2);
int numRecur = numberOf1(strN+1);
return numRecur + otherDigits + HighestDigit;
}
int numberOf1Between1AndN(int n)
{
if(n<=0)
return 0;
char strN[50];
sprintf(strN,"%d",n);
return numberOf1(strN);
}
Q33 把数组排成最小的数
如{2,31,13,35,425}; 可以先对其转换成字符串,再用字典顺序进行排序;得{13,2,31,35,425};直接合并;
这样得到的结果是最高位永远是最小的;
注意:输入的是int,但合并后很有可能超出int的范围,因此用字符处理;
两个字符串的比较:
strcmp(str1,str2); 从第一个字符开始比较,大小是按字典顺序比较;str1<str2: <0 ; str1 == str2 : =0; str1 > str2: >0;
int compar(const void* str1, const void* str2) // 比较函数
{
string S1 = *(char**)str1, S2 = *(char**)str2;
return strcmp(S1.c_str(),S2.c_str());
}
void PrintMinVal(int* Data, int len)
{
char **StrNum = new char*[len];
for (int i = 0; i < len; ++i)
{
char *ch = new char[MAXLEN];
itoa(Data[i],ch,10);
StrNum[i] = ch;
}
qsort(StrNum,len,sizeof(char*),compar); // 快排
for (int i =0; i < len; ++i)
cout<<StrNum[i]<<" ";
cout<<endl;
}
Q34 丑数
inline int GetMin(int a, int b, int c)
{
int m = min(a,b);
return min(m,c);
}
void FindUglyNum(int index)
{
if(index <= 0)
return;
int *UglyNum = new int[index];
int *p2, *p3, *p5;
p2 = p3 = p5 = UglyNum; // 三个指针从头开始
UglyNum[0] = 1;
int nextNum = 1;
while(nextNum < index)
{
int v2 = *p2 * 2, v3 = *p3 * 3, v5 = *p5 * 5;
int minVal = GetMin(v2,v3,v5);
UglyNum[nextNum] = minVal;
while(*p2 * 2 <= minVal) // 每个都判断一遍是为了防止大的*小的与小的*大的相等
++p2;
while(*p3 * 3 <= minVal) // 这里用if也可以实现,只是while逻辑上更合理
++p3;
while(*p5 * 5 <= minVal)
++p5;
++nextNum;
}
for (int i = 0; i < index; ++i)
cout<<UglyNum[i]<<" ";
cout<<endl;
}
Q35 第一个只出现一次的字符
应用
1. 删除重复的字符
遍历字符串,把已经的遍历过的字符保存起来,对当前字符进行查找,如果有则删除,如果没有则继续遍历;
2. 互变字符串
两个字符串如live, evil,其中生个字符出现的次数都相同,顺序可能不同;
扫描第一个字符串,用一哈希表保存字符出现的个数;
扫描第二个字符串,对每个字符对应的次数-1;
如果最后数组值全为0,则说明两个字符串是互变的;
Q36 统计数组的逆序对
利用归并排序的思想;
Q37 两个链表的公共结点
两个链表有公共的结点,则自然是有相同的链表末端,从某个结点开始两链表将拥有相同的结点;
方法一:从尾结点开始反向对比,直到找到最后一个相同的结点即第一个公共结点;时间复杂度: 0(m+n);
为了实现从尾结点读向头结点,可以将两个链表分别压入两个栈中,之后依次比较;
方法二:先分别遍历一次两个链表,得到两个链表的长度n1,n2,假设n1>n2,因此我们先对链表1向前读取到n2-n1个结点,之后再同时向前读两个链表,找到的第一个相同的结点即第一个公共结点;时间复杂度: 0(m+n);