数组系列

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wk_bjut_edu_cn/article/details/83589925

剑指Offer(3)--数组中重复的数字

找出数组中重复的数字,这个数组的特点是:如果长度为n,那么数组中所有的数字都在0~n-1的范围内。

#include<iostream>
using namespace std;
int duplicate(int numbers[], int length)
{
	if (numbers == nullptr || length < 1)
		return -1;
	for (int i = 0; i < length; ++i)
	{
		while (numbers[i] != i)
		{
			if (numbers[i] == numbers[numbers[i]])
				return numbers[i];
			int temp = numbers[i];
			numbers[i] = numbers[temp];
			numbers[temp] = temp;
		}
	}
	return -1;
}
int main(void)
{
	int arr[6] = { 1,3,4,5,2,5 };
	int result = duplicate(arr, 6);
	cout << result << endl;
	system("pause");
	return 0;
}

尽管上述代码中有一个两重循环,但每个数字最多只要交换两次就能找到属于它的位置,因此总的时间复杂度是O(n)。另外所有的步骤都是在输入数组上进行的,不需要分配额外内存,空间复杂度是O(1)。

如果不允许修改原数组,那么可以创建一个辅助数组,然后逐一把原数组的每个数字复制到辅助数组,如果原数组中被复制的数字是m,则把它复制到辅助数组中下标为m的位置。这样下次再放的时候就可以比较出哪个数字是重复的了。

#include<iostream>
using namespace std;
//该方案的时间、空间复杂度均为O(n)
int duplicate(int numbers[], int length)
{
	if (numbers == nullptr || length < 1)
		return -1;
	int *copyNumbers = new int[length];
	for (int i = 0; i < length; ++i)
	{
		copyNumbers[i] = -1;
	}
	for (int i = 0; i < length; ++i)
	{
		if (numbers[i] == copyNumbers[i])
			return numbers[i];
		copyNumbers[numbers[i]] = numbers[i];
	}
	return -1;
}
int main(void)
{
	int arr[6] = { 1,3,4,5,2,5 };
	int result = duplicate(arr, 6);
	cout << result << endl;
	system("pause");
	return 0;
}

剑指Offer(4)--二维数组中的查找

在一个二维数组中,每一行从左向右递增,每一列从上到下递增。判断一个整数是否在这个二维数组中。

#include<iostream>
using namespace std;
//rows是行数,cols是列数
bool find(int *matrix, int rows, int cols, int number)
{
	if (matrix != nullptr && rows > 0 && cols > 0)
	{
		int row = 0;
		int col = cols - 1;
		while (row < rows && col >= 0)
		{
			if (matrix[row*cols + col] == number)
				return true;
			else if (matrix[row*cols + col] > number)
				--col;
			else
				++row;
		}
	}
	return false;
}

 剑指Offer(21)--调整数组顺序使奇数位于偶数前面

两个指针,一个向后,一个向前,当都走到不符合条件时停下,然后交换两元素。接着重复此步骤,直到第一个指针不小于第二个指针。这个思想很重要。

#include<iostream>
using namespace std;
void arrangeArray(int *pData, int length)
{
	if (pData == nullptr || length < 1)
		return;
	int *pBegin = pData;
	int *pEnd = pData + length - 1;
	while (pBegin < pEnd)
	{
		//说明是奇数,继续前移找偶数
		while ((pBegin < pEnd) &&(*pBegin & 0x1) != 0)
			++pBegin;
		while ((pBegin < pEnd) && (*pEnd & 0x1) == 0)
			--pEnd;
		if (pBegin < pEnd)
		{
			int temp = *pBegin;
			*pBegin = *pEnd;
			*pEnd = temp;
		}
	}
}
int main(void)
{
	int arr[7] = { 2,4,1,6,3,5,8 };
	arrangeArray(arr, 7);
	for (int i : arr)
		cout << i << " ";
	cout << endl;
	system("pause");
	return 0;
}

 剑指Offer(39)--数组中出现次数超过一半的数字

#include<iostream>
using namespace std;
/*
思路:数组中有一个数字出现的次数超过数组长度的一半,也就是说它在数组中出现的次数
比剩余的加起来还要多,因此我们可以考虑在遍历数组的时候保存两个值:一个是数组的一个
数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字
相同,则次数加1;如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为零,
我们需要保存下一个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字
出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设为1时对应的数字。
*/
bool checkMoreThanHalf(int *numbers, int length, int number);
int MoreThanHalfNum(int *numbers, int length)
{
	if (numbers == nullptr || length < 1)
		return -1;
	int result = numbers[0];
	int times = 1;
	for (int i = 1; i < length; ++i)
	{
		if (times == 0)
		{
			result = numbers[i];
			times = 1;
		}
		else if (numbers[i] == result)
			times++;
		else
			times--;
	}
	//找到了数组中元素次数最多的,但不一定过半,还需进行验证
	if (!checkMoreThanHalf(numbers,length,result))
		return -1;
	return result;
}
//判断某值在数组中出现的次数是否过半
bool checkMoreThanHalf(int *numbers, int length, int number)
{
	int times = 0;
	for (int i = 0; i < length; ++i)
	{
		if (numbers[i] == number)
			times++;
	}
	if (times * 2 <= length)
		return false;
	return true;
}

P206页的另一种思路也要记住

  剑指Offer(42)--连续子数组的最大和

整形数组中有整数也有负数,求数组的子数组的最大和。

#include<iostream>
using namespace std;
int greateSum(int *pData, int length)
{
	if (pData == nullptr || length < 1)
		return -1;
	int maxSum = -1000;//当前的连续子数组的最大和
	int curSum = 0;
	for (int i = 0; i < length; ++i)
	{
		if (curSum < 0)
			curSum = pData[i];
		else
			curSum += pData[i];
		if (curSum > maxSum)
			maxSum = curSum;
	}
	return maxSum;
}
int main(void)
{
	int arr[7] = { -3,4,-4,6,3,-1,8 };
	int result = greateSum(arr, 7);
	cout << result << endl;
	system("pause");
	return 0;
}

 剑指Offer(45)--把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//数组中的最大长度
const int maxNumberLength = 10;
char *strCombine1 = new char[maxNumberLength * 2 + 1];
char *strCombine2 = new char[maxNumberLength * 2 + 1];
int compare(const void *strNumber1, const void *strNumber2);
void minNumber(int *numbers, int length)
{
	if (numbers == nullptr || length < 1)
		return;
	//为二级指针动态初始化
	char **strNumbers = (char**)new int[length];
	/*
	char **strNumbers;
	strNumbers=new int[length];
	*/
	for (int i = 0; i < length; ++i)
	{
	
		strNumbers[i] = new char[maxNumberLength + 1];
		//函数功能是将数据格式化输出到字符串
		sprintf(strNumbers[i], "%d", numbers[i]);
	}
	/*
	_CRTIMP void __cdecl qsort(void*, size_t, size_t,int (*)(const void*, const void*));
	compare函数原型:compare( (void *) & elem1, (void *) & elem2 );

	*/
	qsort(strNumbers, length, sizeof(char*), compare);
	for (int i = 0; i < length; ++i)
		printf("%s", strNumbers[i]);
	cout << endl;
	for (int i = 0; i < length; ++i)
		delete[] strNumbers[i];
	delete[] strNumbers;
}
//compare的使用具有固定格式,百度qsort函数看示例
int compare(const void *strNumber1, const void *strNumber2)
{
	strcpy(strCombine1, *(const char**)strNumber1);
	strcat(strCombine1, *(const char**)strNumber2);

	strcpy(strCombine2, *(const char**)strNumber2);
	strcat(strCombine2, *(const char**)strNumber1);
	return strcmp(strCombine1, strCombine2);
}
int main(void)
{
	int array[4] = { 23,1,45,53 };
	minNumber(array, 4);
	system("pause");
	return 0;
}
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
/*
给定一个字符串类型的数组strs,找到一种拼接方式,使得把所
有字 符串拼起来之后形成的字符串具有最低的字典序。

思路:贪心算法的利用,注意一般证明贪心策略正确是非常困难的,
用对数器进行验证
*/
bool compare(string str1, string str2)
{
	return str1 + str2 < str2 + str1 ? true : false;//由小到大排序
}
string lowest(vector<string> strs)
{
	if (strs.size() == 0)
		return "";
	sort(strs.begin(), strs.end(), compare);
	string res = "";
	for (int i = 0; i < strs.size(); ++i)
		res += strs[i];
	return res;
}

int main()
{
	vector<string> strs;

	strs.push_back("b");
	strs.push_back("ba");
	strs.push_back("kj");
	strs.push_back("ca");
	cout << lowest(strs) << endl;
	system("pause");
	return 0;
}

 剑指Offer(51)--数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字构成一个逆序对。

#include<iostream>
using namespace std;
int InversePairsCore(int *data, int *copy, int start, int end);
int InversePairs(int *data, int length)
{
	if (data == nullptr || length < 0)
		return 0;
	int *copy = new int[length];
	for (int i = 0; i < length; ++i)
		copy[i] = data[i];
	int count = InversePairsCore(data, copy, 0, length - 1);
	delete[]copy;
	return count;
}
//递归函数的返回值是逆序对的个数
int InversePairsCore(int *data, int *copy, int start, int end)
{
	if (start == end)
	{
		copy[start] = data[start];
		return 0;
	}
	int length = (end - start) >> 1;
	int left = InversePairsCore(copy, data, start, start + length);
	int right = InversePairsCore(copy, data, start + length + 1, end);
	//i初始化为前半段的最后一个数字的下标
	int i = start + length;
	//j初始化为后半段的最后一个数字的下标
	int j = end;
	//index是排序数组的下标,从后向前,依此找该位置上的元素
	int index = end;
	//统计逆序对个数
	int count = 0;
	while (i >= start && j >= start + length + 1)
	{
		if (data[i] > data[j])
		{
			copy[index--] = data[i--];
			count += j - start - length;
		}
		else
			copy[index--] = data[j--];
	}
	for (; i >= start; --i)
		copy[index--] = data[i];
	for (; j >= start + length + 1; --j)
		copy[index--] = data[j];

	//左半部分的逆序对+右半部分的逆序对+左右合起来的逆序对
	return left + right + count;
}
int main(void)
{
	int array[5] = { 23,1,3,53,6 };
	int result = InversePairs(array, 5);
	cout << result << endl;
	system("pause");
	return 0;
}

  剑指Offer(53)--在排序数组中查找数字

统计一个数字在排序数组中出现的次数。

思路:利用二分查找,找出该数字在数组中第一次出现的位置和最后一次出现的位置,因为是排序数组,即可得出在数组中出现的次数。

#include<iostream>
using namespace std;
//在data数组中找第一次出现k的地方
int getFirstK(int *data, int length, int k, int start, int end)
{
	if (start > end)
		return -1;
	int middleIndex = start + ((end - start) >> 1);
	if (data[middleIndex] == k)
	{
		if (middleIndex - 1 < 0)
			return middleIndex;
		else if (middleIndex >= 0 && data[middleIndex - 1] != k)
			return middleIndex;
		else
			end = middleIndex - 1;
	}
	else if (data[middleIndex] > k)
		end = middleIndex - 1;
	else
		start = middleIndex + 1;
	return getFirstK(data, length, k, start, end);
}
//在data数组中找最后一次出现k的地方
int getLastK(int *data, int length, int k, int start, int end)
{
	if (start > end)
		return -1;
	int middleIndex = start + ((end - start) >> 1);
	if (data[middleIndex] == k)
	{
		if (middleIndex + 1 > length - 1)
			return middleIndex;
		else if (middleIndex <= length-1 && data[middleIndex + 1] != k)
			return middleIndex;
		else
			start = middleIndex + 1;
	}
	else if (data[middleIndex] > k)
		end = middleIndex - 1;
	else
		start = middleIndex + 1;
	return getLastK(data, length, k, start, end);
}
int getNumber(int *data, int length, int k)
{
	if (data == nullptr || length < 1)
		return -1;
	int result = 0;
	int first = getFirstK(data, length, k, 0, length - 1);
	int last = getLastK(data, length, k, 0, length - 1);
	if (first < 0 || last < 0)
		return -1;
	result = last - first + 1;
	return result;
}
int main(void)
{
	int array[7] = { 2,2,3,4,4,4,8};
	int result = getNumber(array, 7, 4);
	cout << result << endl;
	system("pause");
	return 0;
}

剑指Offer(56)--数组中数字出现的次数

找出一个数组中只出现一次的两个数字,剩下的其他数字都是出现两次。

思路:

异或的性质:任何一个数字异或自己都为0;如果是出现一次的只有一个数字的话,那么对整个数组进行异或,得到的结果就是这个唯一出现一次的数字。现在题中有两个出现一次的数字,所以想办法将它俩分开。

#include<iostream>
using namespace std;
int findFirstBitIs1(int num);
bool IsBit1(int num, int indexBit);
//因为找的是两个数字,而以返回值的形式只能返回一个,
//所以可以使用形参作为传出参数
void searchNum(int *data, int length, int *num1, int *num2)
{
	if (data == nullptr || length < 1)
		return;
	int resultBit = 0;
	//数组中的所有元素经过异或运算之后,肯定有某位为1,因为数组
	//中有两个只出现一次的数字
	for (int i = 0; i < length; ++i)
		resultBit ^= data[i];
	int indexOf1 = findFirstBitIs1(resultBit);
	//将数组中两个唯一的整数分到两个子数组中,这样在每个子数组中
	//只有唯一一个出现一次的整数,异或之后,即可得到该整数。
	*num1 = *num2 = 0;
	for (int i = 0; i < length; ++i)
	{
		//某整数的第indexOf1位为1
		if (IsBit1(data[i], indexOf1))
			*num1 ^= data[i];
		else
			*num2 ^= data[i];
	}
}
//在整数num的二进制表示中,找到最右边是1的位
int findFirstBitIs1(int num)
{
	int indexBit = 0;
	while ((num & 1) == 0 && (indexBit < 8 * sizeof(int)))
	{
		num = num >> 1;
		++indexBit;
	}
	return indexBit;
}
//判断整数num的二进制表示中从右边起的indexBit是否为1
bool IsBit1(int num, int indexBit)
{
	num = num >> indexBit;
	return (num & 1);
}
int main(void)
{
	int num1 = 0;
	int num2 = 0;
	int array[8] = { 2,3,4,6,4,3,2,8 };
	searchNum(array, 8, &num1, &num2);
	cout << num1 << " " << num2 << endl;
	system("pause");
	return 0;
}
/*
两个整数的位与&,位或|,位异或^运算
先将两个数据转化为二进制数,然后按位进行与运算,同为1结果为1,其它情况结果为0;
先将两个数据转化为二进制数,然后进行按位或运算,只要有一个是1结果为1,不然结果为0;
先将两个数据转化为二进制数,然后进行按位异或运算,只要位不同结果为1,不然结果为0;

*/

在一个数组中除一个数字外,其他数字都出现了3次。找出这个唯一出现一次的数字。

思路:把数组中所有数字的二进制表示的每一位都加起来,如果某一位的和能被3整除,那么那个只出现一次的数字二进制表示中对应的那一位是0;否则就是1。 

#include<iostream>
using namespace std;
int findNumber(int *numbers, int length)
{
	if (numbers == nullptr || length < 1)
		return -1;
	//将数组初始化为0
	int bitSum[32] = { 0 };
	for (int i = 0; i < length; ++i)
	{
		int bitMask = 1;
		for (int j = 31; j >= 0; --j)
		{
			int bit = bitMask & numbers[i];
			if (bit!=0)
				bitSum[j] += 1;
			bitMask = bitMask << 1;
		}
	}
	int result = 0;
	for (int i = 0; i < 32; ++i)
	{
		result = result << 1;
		result += bitSum[i] % 3;
	}
	return result;
}

int main(void)
{
	int array[7] = { 2,6,5,6,6,2,2,};
	int result = findNumber(array, 7);
	cout << result << endl;
	system("pause");
	return 0;
}

   剑指Offer(66)--构建乘积数组

给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。

思路:把数组B看成由一个矩阵来创建

#include<iostream>
using namespace std;
void multiplyCore(int *numbers1, int *numbers2, int length);
void multiply(int *numbers, int length)
{
	int *copy = new int[length];
	for (int i = 0; i < length; ++i)
		copy[i] = numbers[i];
	multiplyCore(numbers, copy, length);
	for (int i = 0; i < length; ++i)
		cout << copy[i] << " ";
	cout << endl;
	delete []copy;
}
void multiplyCore(int *numbers1, int *numbers2, int length)
{
	if (length > 1)
	{
		numbers2[0] = 1;
		//矩阵中左下半部分每一行的乘积
		for (int i = 1; i < length; ++i)
			numbers2[i] = numbers2[i - 1] * numbers1[i - 1];
		int temp = 1;
		for (int i = length - 2; i >= 0; --i)
		{
			temp *= numbers1[i + 1];
			//补上上面每一行中缺失的部分
			numbers2[i] *= temp;
		}
	}
}
int main(void)
{
	int array[4] = { 1,2,3,4};
	multiply(array, 4);
	system("pause");
	return 0;
}

 实现一个洗牌函数,即随机打乱一组数

#include<iostream>
#include<ctime>
using namespace std;
void GetRandNumber(int array[], int length)
{
	if (array == NULL || length == 0)
		return;
	int value = 0;
	int temp = 0;
	for (int index = 0; index < length; ++index)
	{
		value = index + rand() % (length - index);
		temp = array[index];
		array[index] = array[value];
		array[value] = temp;
	}
}
int main(void)
{
	srand((int)time(0));
	int array[] = { 1,2,3,4,5,6,7,8,9,10,11 };
	GetRandNumber(array, 11);
	for (auto i : array)
		cout << i << " ";
	cout << endl;
	system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/83589925