数组系列

 

目录

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

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

剑指Offer(16)--数值的整数次方

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

剑指Offer(29)--顺时针打印矩阵

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

剑指Offer(40)--最小的k个数

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

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

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

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

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

 剑指Offer(57)--和为s的数字

剑指Offer(63)--股票的最大利润

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

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

算法实现求n的阶乘

判断IP地址是否合法

十进制转二进制

 


剑指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(16)--数值的整数次方

实现函数double Power(doublebase, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

思路:

当指数为正时,直接计算。当指数为负时,可以先对指数求绝对值,算出次方的结果之后再取倒数。

#include<iostream>
using namespace std;
double Power(double base, int exponent)
{
	if (base == 0.0)
	{
		cout << "invalidInput" << endl;
		return -1;
	}
	int index = 0;
	//如果指数为负,那么结果就是 1.0/base的正数次方
	if (exponent < 0)
	{
		index = 1;
		exponent = -exponent;
	}
	double result = 1.0;
	for (int i = 1; i <= exponent; ++i)
		result *= base;
	//说明指数为负
	if (index)
		result = 1.0 / result;
	return result;
}
int main(void)
{
	cout << Power(3,-2) << endl;
	system("pause");
	return 0;
}

思路二:

利用递归进行求解,比如求解的是16,那么求出8次方的结果之后,result*result就是结果。如果求解的是17次方,17/2之后也是8,所以最后还需要判断次方是奇数还是偶数。

#include<iostream>
using namespace std;
double Power(double base, int exponent)
{
	if (exponent == 0)
		return 1;
	if (exponent == 1)
		return base;
	double result = Power(base, exponent >> 1);
	result *= result;
	//说明是奇数
	if (exponent & 0x1 == 1)
		result *= base;
	return result;
}

剑指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(29)--顺时针打印矩阵

题目:

输入一个矩阵,按照从外向里以顺时针打印出每一个数字,例如:输入如下矩阵,则依次打印出的数字为:1、2、3、4、8、12、16、15、14、13、9、5、6、7、11、10。

                                             

思路:

固定住左上角和右下角,然后一圈一圈往内输出。

#include<iostream>
using namespace std;
void spiralOrderPrint(int **p, int l, int h)
{
	if (l < 0 || h < 0)
		return;
	int tR = 0;
	int tC = 0;
	int dR = l - 1;
	int dC = h - 1;
	while (tR <= dR && tC <= dC)
		spiralOrderPrintCore(p, tR++, tC++, dR--, dC--);
	return;
}
void spiralOrderPrintCore(int **p, int tR, int tC, int dR, int dC)
{
	if (tR == dR)
	{
		for (int i = tC; i <= dC; ++i)
			cout << p[tR][i] << " ";
	}
	else if (tC == dC)
	{
		for (int i = tR; i <= dR; ++i)
			cout << p[i][tC] << " ";
	}
	else
	{
		int curC = tC;
		int curR = tR;
		while (curC < dC)
		{
			cout << p[tR][curC] << " ";
			++curC;
		}
		cout << endl;
		while (curR < dR)
		{
			cout << p[curR][dC] << " ";
			++curR;
		}
		cout << endl;
		while (curC > tC)
		{
			cout << p[dR][curC] << " ";
			--curC;
		}
		cout << endl;
		while (curR>tR)
		{
			cout << p[curR][tC] << " ";
			--curR;
		}
	}
}

剑指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(40)--最小的k个数

题目:输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

思路一:

基于快速排序中的Partition函数来解决这个问题。如果基于数组的第k个数字来调整,使得比第k个数字小的所有数字都位于数组的左边,比第k个数字大的所有数字都位于数组的右边。这样调整之后,位于数组中左边的k个数字就是最小的k个数字(这k个数字不一定是排序的)。时间复杂度是O(n)。

采用这种思路是有限制的。我们需要修改输入的数组,因为函数Partition会调整数组中数字的顺序。

#include<iostream>
#include<algorithm>
using namespace std;
int patition(int *arr, int left, int right)
{
	int i = left, j = right - 1;
	//int temp = arr[right];
	while (left < right)
	{
		while (arr[i] < arr[right])
			++i;
		while (arr[j] > arr[right] && j>left)
			--j;
		if (i < j)
			swap(arr[i], arr[j]);
		else
			break;
	}
	swap(arr[i], arr[right]);
	return i;
}
//input是题目给定的数组,n是input的维度
//output是结果,k是题目要求的维度
void GetLeastNumbers(int *input, int n, int *output, int k)
{
	if (input == nullptr || k > n || n < 1 || k < 1)
		return;
	int start = 0, end = n - 1;
	int index = patition(input, start, end);
	while (index != k - 1)
	{
		if (index < k - 1)
		{
			start = index + 1;
			index = patition(input, start, end);
		}
		else
		{
			end = index - 1;
			index = patition(input, start, end);
		}
	}
	for (int i = 0; i < k; ++i)
		output[i] = input[i];
}
int main(void)
{
	int arr[] = { 8,4,9,2,6,87,1,67,34,65,99,78 };
	int result[10];
	GetLeastNumbers(arr, 12, result, 10);
	for (int i = 0; i < 10; ++i)
		cout << result[i] << "	";
	cout << endl;
	system("pause");
	return 0;
}

思路二:

可以先创建一个大小为k的数据容器来存储最小的k个数字,接下来我们每次从输入的n个整数中读入一个数。

如果容器中已有的数字少于k个,则直接把这次读入的整数放入容器之中

如果容器中已有k个数字了,也就是容器已满,此时我们不能再插入新的数字而只能替换已有的数字

因此当容器满了之后,我们要做3件事情:一是在k个整数中找到最大数;二是有可能在这个容器中删除最大数;三是有可能要插入一个新的数字。如果用一个二叉树来实现这个数据容器,那么我们能在O(logk)时间内实现这三步操作。因此对于n个输入数字而言,总的时间效率就是O(nlogk)。采用了红黑树结构作为容器,当然也可以采用堆来实现。

#include<iostream>
#include<vector>
#include<set>
#include<xfunctional>
using namespace std;
void GetLeastNumbers(const vector<int>& v, multiset<int, greater<int>> &ms,int k)
{
	auto iterV = v.begin();
	auto iterS = ms.begin();
	for (; iterV != v.end(); ++iterV)
	{
		if (ms.size() < k)
			ms.insert(*iterV);
		else
		{
			iterS = ms.begin();
			if (*iterV < *iterS)
			{
				ms.erase(iterS);
				ms.insert(*iterV);
			}
		}
	}
}
int main(void)
{
	vector<int> v = { 8,4,9,2,6,87,1,67,34,65,99,78 };
	multiset<int, greater<int>> ms;
	GetLeastNumbers(v, ms,5);
	for (auto num : ms)
		cout << num << " ";
	cout << endl;
	system("pause");
	return 0;
}

剑指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;
}
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;

bool compare(string str1, string str2)
{
	return stoi(str1 + str2) < stoi(str2 + str1) ? true : false;
}

vector<int> lowest(int *numbers,int length)
{
	vector<string> strs;
	for (int i = 0; i < length; ++i)
		strs.push_back(to_string(numbers[i]));
	sort(strs.begin(), strs.end(), compare);
	vector<int> res;
	for (int i = 0; i < strs.size(); ++i)
		res.push_back(stoi(strs[i]));
	return res;
}

int main()
{
	int numbers[] = { 3,32,321 };
	vector<int> v = lowest(numbers, 3);
	for (auto i : v)
		cout << i;
	cout << 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>
#include<deque>
#include<vector>
using namespace std;

int getFirstK(int *data, int length, int start, int end, int k)
{
	if (start > end)
		return -1;
	int middle = (start + end) / 2;
	if (data[middle] == k)
	{
		if (middle > start && data[middle - 1] == k)
			return getFirstK(data, length, start, middle - 1, k);
		else
			return middle;
	}
	else if(data[middle]>k)
		return getFirstK(data, length, start, middle - 1, k);
	else
		return getFirstK(data, length, middle+1, end, k);
}
int getLastK(int *data, int length, int start, int end, int k)
{
	if (start > end)
		return -1;
	int middle = (start + end) / 2;
	if (data[middle] == k)
	{
		if (middle < end && data[middle + 1] == k)
			return getFirstK(data, length, middle +1 , end, k);
		else
			return middle;
	}
	else if (data[middle]>k)
		return getFirstK(data, length, start, middle - 1, k);
	else
		return getLastK(data, length, middle + 1, end, k);
}
int getNumber(int *data, int length, int k)
{
	if (data == nullptr || length < 1)
		return -1;
	int result = 0;
	int first = getFirstK(data, length, 0, length - 1,k);
	int last = getLastK(data, length,0, length - 1,k);
	if (first < 0 || last < 0)
		return -1;
	result = last - first + 1;
	return result;
}
int main(void)
{
	int array[7] = { 1,2,3,3,3,4,5 };
	int result = getNumber(array, 7, 3);
	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 *data, int length)
{
	if (data == nullptr || length < 1)
		return -1;
	int num[32] = { 0 };
	int bit = 1;
	for (int i = 0; i < 32; ++i)
	{
		for (int j = 0; j < length; ++j)
		{
			int value = bit&data[j];
			if(value!=0)
				num[i] += 1;
		}
		bit = bit << 1;
	}
	int result = 0;
	for (int i = 31; i >= 0; --i)
	{
		result = result << 1;
		result += num[i] % 3;
	}
	return result;
}
int main(void)
{
	int array[10] = { 1,1,3,2,1,11111,3,2,3,2 };
	int result = findNumber(array, 10);
	cout << result << endl;
	system("pause");
	return 0;
}

 剑指Offer(57)--和为s的数字

题目:

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s,如果有多对数字的和等于s,输出任意一对即可。

思路:

夹逼准则

#include<iostream>
using namespace std;
bool FindNumbersWithSum(int *data, int length, int s,
	int *num1, int *num2)
{
	if (data == nullptr || length < 2)
		return false;
	int head = 0;
	int tail = length - 1;
	while (head < tail)
	{
		if (data[head] + data[tail] == s)
		{
			num1 = &data[head];
			num2 = &data[tail];
			return true;
		}
		else if (data[head] + data[tail] < s)
			++head;
		else
			--tail;
	}
	return false;
}

题目二:

和为s的连续正数系列。

输入一个正数s,打印出所有和为s的连续正数序列(至少含有两个数)。例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以结果打印出3个连续序列1-5,4-6和7-8。

思路:

用两个数small和bbig分别表示序列的最小值和最大值。首先把small初始化为1,bbig初始化为2.如果从small到big的序列和大于s,则可以从序列中去掉较小的值,也就是增大small的值。如果从small到big的序列和小于s,则可以增大big,让这个序列包含更多的数字。

#include<iostream>
using namespace std;
/*
small到big区间内的数就是和为sum的数,如果小于sum,则
big++,如果大于small,则small++
*/
void FindContinusSequence(int sum)
{
	if (sum < 3)
		return;
	int small = 1;
	int big = 2;
	int middle = (1 + sum) / 2;
	int curSum = small + big;
	//因为序列最少要两个数字,而(1+S)/2+(1+S)/2>S
	//所以小一点的数不能超过(1+S)/2
	while (small < middle)
	{
		if (curSum == sum)
			Print(small, big);
		while (curSum>sum && small<middle)
		{
			curSum -= small;
			small++;
			if (curSum == sum)
				Print(small, big);
		}
	
	    big++;
	    curSum += big;
    }
}
void Print(int small, int big)
{
	for (int i = small; i <= big; ++i)
		cout << i << " ";
	cout << endl;
}



剑指Offer(63)--股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可获得的最大利润是多少?例如,一只股票在某些时间节点的价格为{9,11,8,5,7,12,16,14}。如果我们能在价格为5的时候买入并在价格为16时卖出,则能获得最大的利润为11。

思路:

第一种是找出数组中的所有数对,然后比较差值,时间复杂度是O(n^2)。

第二种是同时记录最小值和最大利润,也就是说,如果在扫描到数组中的第i个数字时,只要我们能够记住之前的i-1个数字中的最小值,就能算出在当前价位卖出时可能得到的最大利润。

#include<iostream>
using namespace std;
int MaxDiff(const int *numbers, int length)
{
	if (numbers == nullptr || length < 2)
		return 0;
	int min = numbers[0];
	int maxDiff = numbers[1] - numbers[0];
	for (int i = 2; i < length; ++i)
	{
		if (numbers[i - 1] < min)
			min = numbers[i - 1];
		int curDiff = numbers[i] - min;
		if (curDiff > maxDiff)
			maxDiff = curDiff;
	}
	return maxDiff;
}

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

算法实现求n的阶乘

求大整数n阶乘,在找工作笔试和面试的过程中,不止一次遇到这个问题,用一个for循环迭代出的结果肯定是不行的,即直接用int,默认是32位,它能表示的最大值为2,147,483,647,但是12的阶乘为479,001,600,13的阶乘为6,227,020,800,所以当n为13的时候已经溢出了。所以当n为更大的值时,需要采用巧妙的方法来防止溢出。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#define MAX 40000
int main(void)
{
	int n;
	while (scanf("%d", &n) != EOF && n >= 1)
	{
		int i, j;
		int arr[MAX];//每一位存储的都是结果
		int p, h;//p存储是当前结果的位数,h是每次产生的进位
		arr[1] = 1;
		p = 1;
		//上面对应的是1的阶乘
		//接下来求k的阶乘
		for (i = 2; i <= n; ++i)
		{
			//计算阶乘之前先将进位设为0
			for (j = 1, h = 0; j <= p; ++j)
			{
				arr[j] = arr[j] * i + h;
				h = arr[j] / 10;
				arr[j] = arr[j] % 10;
			}
			//说明产生了新的位数
			while (h > 0)
			{
				arr[j] = h % 10;
				h /= 10;
				++j;
			}
			//p是当前的位数
			p = j - 1;
		}
		for (i = p; i >= 2; --i)
			printf("%d", arr[i]);
		printf("%d\n", arr[i]);
	}

	system("pause");
	return 0;
}

举个例子,比如说我们现在得到了4!= 24,在数组中是以倒序的方式存储,即42,a[1] =  4,a[2] = 2(数组下标从1开始),现在要计算5!,内层for循环对目前的两位4和2进行处理,即每一位都乘以5,首先a[1] = a[1]*5 + h(进位)= 20,h从0变为2,a[1] = 0;然后a[2] = a[2] * 5 + 2 = 12,h从2变为1,a[2] = 2;这时内层for循环执行结束。
总结1:内层for循环的作用是更新已经存在的位数中的每一位与i的乘积的结果.

接着执行while循环,目前h = 1,a[3] = 1,h 从1变为0,然后退出while循环。处理结束就得到了5! = 120.p的位数增加到3.

总结2:while循环的作用是计算p更新之前的位数之后的进位,比如说p = 2的时候,数组中为42,然后内层for循环把42更新为02,while循环增加进位1,整个数组变为021.

判断IP地址是否合法

#include<iostream>
#include<string>
using namespace std;
 
bool IsLegal_Ip(const string ipaddr)
{
	int len=ipaddr.length();//计算字符串的长度
	int stage=0;//该IP地址共有几部分
	int stage_value=0;//每个部分的值
	int is_stage=0;//判断当前部分是否有值
 
	if(isalpha(ipaddr[0]))//===》if(ipaddr[i]>='a'&&ipaddr[i]<='z'&&ipaddr[i]>='A'&&ipaddr[i]<='Z')
		return false;
 
	for(int i=0;i<len;++i)
	{
		if(isdigit(ipaddr[i]))//==》if(ipaddr[i]>='0' && ipaddr[i]<='9')
		{
			stage_value=stage_value*10+(ipaddr[i]-'0');
			++is_stage;
		}
		else if(ipaddr[i]=='.')
		{
			if(stage_value<=255 && is_stage>0)
				++stage;
			else
				return false;
			
			//进行下一轮判断,重新置为0
			is_stage=0;
			stage_value=0;
		}
		else//判断有负数的情况
			return false;	
	}
 
	//判断第四部分是否满足IP地址的要求
	if(stage_value<=255 && is_stage>0)
		++stage;
	
	if(stage==4)
		return true;
	else 
		return false;
}
 

十进制转二进制

#include<iostream>
using namespace std;
void main()
{
   int n,i,j=0;
   int a[1000];
   cin>>n;
   i=n;
   while(i)
   {
    a[j]=i%2;
    i/=2;
    j++;
    
   }
   for(i=j-1;i>=0;i--)
    cout<<a[i];
   cout<<endl;
}

下一个排列

比如求149632的下一个排列为:162349。

#include<iostream>
#include<vector>
using namespace std;
//以149632为例进行分析
void nextPermutation(vector<int>& nums)
{
	int k = -1;
	for (int i = nums.size() - 2; i >= 0; --i)
	{
		//找到最大的下标k,使得nums[k]<nums[k+1]
		if (nums[i] < nums[i + 1])
		{
			k = i;
			break;
		}
	}
	//这种情况说明vector中的数完全逆序
	if (k == -1)
	{
		reverse(nums.begin(), nums.end());
		return;
	}
	int l = -1;
	//从后往前找,找到k之后满足nums[k] < nums[l]的最大的下标i
	for (int i = nums.size() - 1; i >= 0; --i)
	{
		if (nums[i] > nums[k])
		{
			l = i;
			break;
		}
	}
	swap(nums[k], nums[l]);
	/*
	当初找到的k,说明的是,从k往后的元素是从大到小排列,经过
	swap之后依然是从大到小,我们应该弄成从小到大,因为是找第一个
	排列大于题给定的序列
	*/
	reverse(nums.begin() + k + 1, nums.end());
}
int main(void)
{
	vector<int> v = { 1,2,3 };
	nextPermutation(v);
	for (auto i : v)
		cout << i << " ";
	cout << endl;
	system("pause");
	return 0;
}

缺失数字

给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。

示例 1:

输入: [3,0,1]
输出: 2
示例 2:

输入: [9,6,4,2,3,5,7,0,1]
输出: 8

方法一:

思路:用等差数列的求和公式求出0到n之间所有的数字之和,然后再遍历数组算出给定数字的累积和,然后做减法,差值就是丢失的那个数字。

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int sum = 0, n = nums.size();
        for (auto &a : nums) {
            sum += a;
        }
        return 0.5 * n * (n + 1) - sum;
    }
};

方法二:

既然0到n之间少了一个数,我们将这个少了一个数的数组合0到n之间完整的数组异或一下,那么相同的数字都变为0了,剩下的就是少了的那个数字了。

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int res = 0;
        for (int i = 0; i < nums.size(); ++i) {
            res ^= (i + 1) ^ nums[i];
        }
        return res;
    }
};

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值