剑指offer chapter 5

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]};
实现:
 

 
// i
#include  <iostream>
using  namespace  std ;
void  main ( )  {
     int  a [ ]  =  { -1 , -2 , 3 , 1 , -2 , -3 , 4 , 5 , -1 , -6 , 9 , 6 , -4 , 5 , 8 , -1 } ;
     int  All  =  a [ 0 ] , sta  =  a [ 0 ] ;
     int  len  =  sizeof ( a )/ sizeof ( int ) ;
     for ( int  i  =  1 ;  i  <  len ;  ++ i )
     {
         sta  =  max ( a [ i ] , sta  +  a [ i ]) ;  //  求得包含i 项的最大和值
         All  =  max ( All , sta ) ;            // 求得 i 以前的最大和值
     }
     cout << All << endl ;
}



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;
}



  // char*void*
  // void*stringchar*;
  // char* c = new char*[LEN]; c = (char*)str1;cstr1
  // (char*)str1
  // c = *(char**)str1;
int compar( const  void * str1,  const  void * str2)  
{
    string S1  =  *( char **)str1, S2  =  *( char **)str2;
    S1  = S1.append( *( char **)str2);
    S2  = S2.append( *( char **)str1);
    
     return  strcmp(S1.c_str(),S2.c_str());
}
 
















Q34 丑数

如果一个数的因子是2,3,5 则这个 数是丑数,如8,10; 8/2/2/2 = 1; 10/2/5=1; 1是最小的丑数
如果对每个数(1~n)都依次判断是否为丑数,则时间复杂度较差;
而一个丑数可以看成另一个丑数*2 3 5得到的,因此可以从1开始以小到大的顺序依次计算出丑数;
过程是用3个指针:p2, p3, p5,每次比较当前位置的值*对应的指针,取最小值放入下一个最大丑数;

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 第一个只出现一次的字符

这个题至少要扫描字符串一次才知道每个字符出现的次数;
因此在第一次扫描的过程,为每个字符建立一个<key,val>的映射,用字符的ASCII数做索引,空间大小为256,一次扫描后的时间复杂度为0(n)。
第二次扫描则在找到第一个val=1时即找到了对应的字符;

// 上面即是一个 哈希表的运用


应用   
这样的哈希表应用可以用于统计字符串里重复的字符或是出现的次数
1. 删除重复的字符

遍历字符串,把已经的遍历过的字符保存起来,对当前字符进行查找,如果有则删除,如果没有则继续遍历;


2. 互变字符串

两个字符串如live, evil,其中生个字符出现的次数都相同,顺序可能不同;

扫描第一个字符串,用一哈希表保存字符出现的个数;

扫描第二个字符串,对每个字符对应的次数-1;

如果最后数组值全为0,则说明两个字符串是互变的;


Q36 统计数组的逆序对

利用归并排序的思想;


Q37 两个链表的公共结点

两个链表有公共的结点,则自然是有相同的链表末端,从某个结点开始两链表将拥有相同的结点;

方法一:从尾结点开始反向对比,直到找到最后一个相同的结点即第一个公共结点;时间复杂度: 0(m+n);

为了实现从尾结点读向头结点,可以将两个链表分别压入两个栈中,之后依次比较;


方法二:先分别遍历一次两个链表,得到两个链表的长度n1,n2,假设n1>n2,因此我们先对链表1向前读取到n2-n1个结点,之后再同时向前读两个链表,找到的第一个相同的结点即第一个公共结点;时间复杂度: 0(m+n);

































  // char*void*
  // void*stringchar*;
  // char* c = new char*[LEN]; c = (char*)str1;cstr1
  // (char*)str1
  // c = *(char**)str1;
int compar( const  void * str1,  const  void * str2)  
{
    string S1  =  *( char **)str1, S2  =  *( char **)str2;
    S1  = S1.append( *( char **)str2);
    S2  = S2.append( *( char **)str1);
    
     return  strcmp(S1.c_str(),S2.c_str());
}
 







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值