关于数组的剑指offer习题

(一)二维数组中数字的查找

此二维数组的特点

每一行都按照从左到右递增 每一列都按照从上到下递增

题目要求:给定这样一个数组和一个整数 查找这个整数是否在数组中

解题思路:

由于题中所所述的二维数组的特点 从二维数组最后一列的第一个元素arr[row][col-1]找起 

如果要找的数字大于arr[row][col] 则直接跳到下一行   由于arr[row][col]前面的数字都小于它 但是从上往下都大于它 所以向下走

如果要找的数字小于arr[row][col] 则直接跳到前面一列   由于arr[row][col]前面的值都小于它 所以往前面走

图例如下:

代码如下:

bool Find_Number(int **arr,int rows,int cols,int n)
{
	bool flag = false;
	int row = 0;

	int col = cols - 1;
	

	if(arr != NULL && rows > 0 && cols > 0)
	{
		while(row < rows && col >= 0 )
		{
			if(arr[row][col] == n)
			{
				flag = true;
				break;
			}
			else if(arr[row][col] > n)
			{
				--col;
			}
			else
			{
				++row;
			}
		}
	}
	return flag;
}

int main()
{
	int row;
	cin>>row;

	int col;
	cin>>col;

	int **arr = new int*[row];
	for(int i = 0; i < row; ++i)
	{
		arr[i] = new int [col]();
	}

	for(int i = 0 ;i < row;++i)
	{
		for(int j = 0 ;j < col;++j)
		{
			cin>>arr[i][j];
		}
	}
	int n ;
	cin>>n;
	if(Find_Number(arr,row,col,7))
	{
		printf("exist\n");
	}
	else
	{
		printf("not exist\n");
	}
	return 0;
}

(二)数组的旋转

输入一个递增数组的一个旋转 找出该数组的最小值  比如{3,4,5,6,7,0,1,2}这是数组{0,1,2,3,4,5,6,7}的一个旋转数组

题目要求:求一个数组的旋转数组的最小值

解题思路:

第一种:遍历一遍数组,找到最小值,时间复杂度O(n) 不建议采取

第二种:可以发现旋转后的数组是两个连续递增的数组 并且第一个数组的最大值和第二个数组的最小值是相邻的

我们可以借助二分查找的思想找第一个递增数组的最大值 找第二个递增数组的最小值  

运用三个low mid  high 指针找  当low high 指针相邻时high指向的就是最小值

特殊情况

数组{1,0,1,1,1}和数组{1,1,1,0,1}都是旋转数组   low mid high指向的元素相等 所以无法使用二分查找 只能顺序查找

代码如下:

int Min(int *arr,int low,int high)
{
	int result = arr[low];
	for(int i = low+1;i < high;++i)
	{
		if(result > arr[i])
		{
			result = arr[i];
		}
	}
	return result;
}
int Min_Number(int *arr,int len)
{
	if(arr == NULL ||len < 0)
		throw new exception("out of bounder");

	int low = 0;
	int mid = 0;
	int high = len - 1;

	while(arr[low] >= arr[high])
	{
		if(high - low == 1)
		{
			mid = high;
			break;
		}

		mid = (high+low)/2;
		if(arr[low] == arr[mid] && arr[mid] == arr[high])
			return Min(arr,low,high);
		if(arr[low] <= arr[mid])
			low = mid;
		if(arr[mid] <= arr[high])
			high = mid;
	}
	return arr[mid];
}


int main()
{
	int arr1[]={11,12,13,14,15,7,8,9,10};
	int len1 = sizeof(arr1)/sizeof(arr1[0]);
	int result1 = Min_Number(arr1,len1);
	cout<<"arr1旋转数组最小值为::"<<result1<<endl;
	int arr2[]={1,0,1,1,1};
	int len2 = sizeof(arr2)/sizeof(arr2[0]);
	int result2 = Min_Number(arr2,len2);
	cout<<"arr2旋转数组最小值为::"<<result2<<endl;
	return 0;
}

(三)调整数组顺序使奇数位于偶数前面

比如数组{1,2,3,4,5}   定义两个指针 pBegin,pEnd
当pBegin < pEnd && pBegin指向的是偶数时 pEnd指向的是奇数时 交换两个指针指向的数据
当pBegin > pEnd 说明所有的奇数都已经位于数组的前半部分 偶数位于数组的后半部分

本题中是目的是奇数在前偶数在后 由于可能会变化划分标准所以我们将标准重写一个函数 比如题目改为前面放不能被3整除的 后面放能被整除的

划分框架不变 变的只是标准

代码如下:

/*
判读数字是奇数还是偶数
 n & 1 == 0
 判断数字是否被3整除
 n % 3 == 0
 isEven函数用来指定一个划分标准
*/
/*
 //断数字是否被3整除
bool isEven1(int n)
{
	return (n % 3 ) == 0;
}

void  part(int *arr,int len,bool (*func)(int))
{
	if(arr == NULL ||len < 0)
		return ;
	int *pBegin = arr;
	int *pEnd = arr + len -1;

	while(pBegin < pEnd)
	{
		while(pBegin < pEnd && !func(*pBegin))
		{
			pBegin++;
		}
		while(pBegin < pEnd && func(*pEnd))
		{
			pEnd--;
		}

		if(pBegin < pEnd)
		{
			int tmp = *pBegin;
			*pBegin  = *pEnd;
			*pEnd = tmp;
		}
	}
}
void Part_Array1(int *arr,int len)
{
	part(arr,len,isEven1);
}
//判读数字是奇数还是偶数
bool isEven2(int n)
{
	return (n & 1 ) == 0;
}
void  part(int *arr,int len,bool (*func)(int))
{
	if(arr == NULL ||len < 0)
		return ;
	int *pBegin = arr;
	int *pEnd = arr + len -1;

	while(pBegin < pEnd)
	{
		while(pBegin < pEnd && !func(*pBegin))
		{
			pBegin++;
		}
		while(pBegin < pEnd && func(*pEnd))
		{
			pEnd--;
		}

		if(pBegin < pEnd)
		{
			int tmp = *pBegin;
			*pBegin  = *pEnd;
			*pEnd = tmp;
		}
	}
}

void Part_Array2(int *arr,int len)
{
	part(arr,len,isEven2);
}

(四)数组中出现次数超过数组长度一半的数字

解题思路一:

如果是数字的次数超过数组长度的一半 那么在将数组排序之后这个数字在统计学上就是这组数的中位数
所以只要利用快排中的划分函数查找即可
如果一次划分排序之后返回的index就等于n/2,则index对应的数字就是那个出现次数最多的数字
如果index != n/2 那么如果index > n/2 则中位数应该在index的左边找
                     如果index < n/2 则中位数应该在index的右边找

代码如下:

int partion(int *arr,int low,int high)
{
	if(arr == NULL )
		return -1;
	int tmp = arr[low];
	while(low < high)
	{
		while((low < high) && arr[high] >= tmp)
		{
			high--;
		}
		if(low == high)//如果遇到比基准值小的 放到low的位置
		{
			break;
		}
		else
		{
			arr[low] = arr[high];
		}
		while((low < high) && arr[low] <=  tmp)
		{
			low++;
		}
		if(low == high)
		{
			break;
		}
		else
		{
			arr[high] = arr[low];
		}
	}
	arr[low] = tmp;
	return low;
}
bool Is_Catch_Half(int *arr,int len ,int n)
{
	if(arr == NULL ||len < 0)
		return false;
	int count = 0;
	for(int i = 0 ;i < len ;++i)
	{
		if(arr[i] == n)
			count++;
	}
	//判断数字n出现的次数是否超过len的2倍
	if(count * 2 < len )
	{
		return false;
	}
	return true;
}

int Find_MaxCount_Number1(int *arr,int len)
{
	if(arr == NULL || len < 0)
		throw new exception("out of bounder");

	//数字左移一位相当于*2.数字右移一位相当于/2
	int mid = len>>1;
	int start = 0;
	int end = len - 1;

	int index = partion(arr,start,end);

	while(index != mid)
	{
		//说明中位数在它的左边
		if(index > mid)
		{
			end = index - 1;
			index = partion(arr,start,end);
		}
		else
		{
			start = index + 1;
			index = partion(arr,start,end);
		}
	}
	//找到中位数的位置 接下来通过另一个函数来判断这个中位数在数组中出现在的次数是否查过len的一半
	int result = arr[mid];
	if(!Is_Catch_Half(arr,len,result))
	{
		result = 0;
	}
	return result ;
}

解题思路二:


//定义两个变量 result用来保存数组中的数字 count用来保存次数
//当我们遍历到下一个数字时,如果下一个数字与之前保存的result相等,则次数加1
//如果下一个数字与result不相等 则次数减1
//如果次数为0 我们需要保存一个数,并将其次数置为1
//由于我们要找的数字出现的次数比其他数字次数多
//那么则最后一次将count置为1的数字即就是对应的数字

代码如下:优点未改变数组的内容 而采用划分函数则修改了数组的内容 时间复杂度o(n)

int Find_MaxCount_Number2(int *arr,int len)
{
	if(arr == NULL ||len < 0)
		throw new exception("out of bounder");
	int result = arr[0];
	int count = 1;
	for(int i = 1 ;i < len ;++i)
	{
		if(count == 0)
		{
			result = arr[i];
			count = 1;
		}
		else if(arr[i] == result)
			count++;
		else
			count--;
	}
	if(!Is_Catch_Half(arr,len,result))
	{
		result = 0;
	}
	return result ;
}

(五)数组中最小的k个数

解题思路:

解法一:将输入的数字进行从小到大排序 那么根据k值取前k个数就是最小的k个数 时间复杂度O(n)

解法二:基于划分函数的启发 如果基于数组第K个数字来调整,将小于第k个数的所有数字放在第k个数字的左边
                          将大于第k个数字的所有数字放在第k个数字的右边 那么最小的k个数就是第k个数字左边的数字
缺点是:找到的前k个数字不一定有序 并且修改了传入的数组的值

代码如下:

int partion(int *arr,int low,int high)
{
	if(arr == NULL )
		return -1;
	int tmp = arr[low];
	while(low < high)
	{
		while((low < high) && arr[high] >= tmp)
		{
			high--;
		}
		if(low == high)//如果遇到比基准值小的 放到low的位置
		{
			break;
		}
		else
		{
			arr[low] = arr[high];
		}
		while((low < high) && arr[low] <=  tmp)
		{
			low++;
		}
		if(low == high)
		{
			break;
		}
		else
		{
			arr[high] = arr[low];
		}
	}
	arr[low] = tmp;
	return low;
}


void Get_K_MinNumber1(int *arr,int len ,int *brr,int k)
{
	if(arr == NULL || brr == NULL || len <= 0 || k > len || k <= 0)
		return ;
	int start = 0;
	int end = len -1 ;
	int index = partion(arr,start,end);

	while(index != k-1)
	{
		if(index > k - 1)
		{
			end = index - 1;
			index =  partion(arr,start,end);
		}
		else
		{
			start = index + 1;
			index =  partion(arr,start,end);
		}
	}
	for(int i = 0 ;i < k ;++i)
	{
		brr[i] = arr[i];
	}
}

解法三:利用一个容器只保存K个数字 只需要读数组的值而不需要修改值
第一步:在容器未满时 将从数组读出来的数字放入容器中
第二步:若容器已经满了 拿出容器中的最大值max与从数组中取出来的值arr[i]相比较 
                      如果max < arr[i] 则将arr[i]丢弃
                      如果max > arr[i] 则用arr[i]替换max
容器的选取方式:由于在容器满时可以最快拿到最大值 可以选择建最大堆 那么根节点的值永远是容器中的最大值
               还可以选择容器中set、map等;因为他们底层都是应用于红黑树 也可以做到取最大值
               本题使用multiset容器
优点: 不修改传入的数组中的值
     时间复杂度O(n*logk)

代码如下:

//greater算法得到降序排列
/*
void Get_K_MinNumber2(const vector<int> &vec ,multiset<int,greater<int>>  &kset,int k)
{
	int size = kset.size();
	kset.clear();//清空set里内容
	if( k < 1 || size > k )
		return ;

	vector<int>::const_iterator  cit  = vec.begin();

	//遍历数组 
	for(;cit != vec.end();++cit)
	{
		//如果kset容器还未满 直接将元素放入kset中
		int size = kset.size();
		if(size < k)
		{
			kset.insert(*cit);
		}
		else
		{
			//如果kset已经满了 拿出kset中首元素的值与待插入的数组中的元素比较
			multiset<int,greater<int>> ::iterator sit = kset.begin();
			//如果*sit > *cit 则将sit指向的删除 将*cit插入kset中
			if(*sit > *cit)
			{
				kset.erase(sit);
				kset.insert(*cit);
			}
		}
	}
}


int main()
{


	vector<int> vec;
	vector<int>::iterator it = vec.begin();
	vec.insert(vec.begin(),4);
	vec.insert(vec.begin(),5);
	vec.insert(vec.begin(),1);
 	vec.insert(vec.begin(),6);
	vec.insert(vec.begin(),2);
	vec.insert(vec.begin(),7);
	vec.insert(vec.begin(),3);
	vec.insert(vec.begin(),8);
	vec.insert(vec.begin(),9);
	vec.insert(vec.begin(),10);
	multiset<int,greater<int>> kset;
	int k = 5;
	Get_K_MinNumber2(vec,kset,k);
	multiset<int,greater<int>>:: iterator sit = kset.begin();
	for(; sit != kset.end();++sit)
	{
		cout<<*sit<<" ";
	}
	cout<<endl;
return 0;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值