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