剑指Offer——数组相关
1、第1题 二维数组中的查找
题目描述:
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解题思路:
本题我们需要着重的考虑数组的结构,需要关注到的是数组无论是从行还是从列的角度,都是一个单调递增的过程。我们将这个二维数组看成是一个二维的矩阵,我们考虑左下角的元素,从该元素开始,向上元素递减,向右元素递增。以该元素为起点,如果目标元素大于该元素则向右走,如果目标元素小于该元素在向上走。走到其他元素的时候的,过程与该元素类似。
代码实现:
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
if(array.size() == 0||array[0].size()==0)
return false;
int leng = array.size()-1;
int leng2 = 0;
while(leng>=0 && leng2 < array.size() )
{
if(target > array[leng][leng2])
leng2++;
else if(target < array[leng][leng2] )
leng--;
else
return true;
}
return false;
}
};
2、第6题 旋转数组中的最小数字
题目描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
解题思路:
这道题,我们需要注意到的时候原始数组是一个非递减的数组,那么在旋转之后,可以将原始的数组分成两个部分,第一个部分是一个非递减的数组,在旋转之后的部分是一个非递减的数组,第二个数组的第一个节点就是整个数组最小的元素。
代码实现:
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
if(rotateArray.size()==0)
return 0;
for(int index =0;index<rotateArray.size()-1;index++)
{
if(rotateArray[index]>rotateArray[index+1])
{
return rotateArray[index+1];
}
}
return rotateArray[0];
}
};
3、第28题 数组中出现次数超过一半的数字
题目描述:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
基本思路:
如果数组是有序的,那么超过数组一半的元素肯定会出现在数组中间的位置。下面,我们只需要将数组进行排序,然后获取到中间元素,在计算中间元素的个数是否超过了数组的一半即可。
代码实现:
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.size()== 0)
return 0;
sort(numbers.begin(),numbers.end());
int middle = numbers[numbers.size()/2];
int count = 0;
for(int i=0;i<numbers.size();i++)
if(numbers[i] == middle)
count ++;
if(count > (numbers.size()/2))
return middle;
else
return 0;
}
};
4、第30题 连续子数组的最大和
题目描述:
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
解题思路:
本题是是一道经典的动态规划的问题,我们来考虑这个问题中动态规划的转移情况,首先,动态规划的第一个状态是数组的第一个元素,对于下一个状态而言,如果前一个状态加上当前的元素的和要比当前元素的值要大,那么当前状态的取值就应该是前一个状态的值和当前数组元素的和,否则我们只保留当前元素即可。最后,我们找到整个状态序列中的最大的元素即可。
代码实现:
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array) {
int length = array.size();
if(length==0)
return 0;
int * dp = new int (length);
dp[0] = array[0];
int maxv = dp[0];
for(int i=1;i<length;i++)
{
if((array[i]+dp[i-1]) > array[i])
dp[i] = array[i] + dp[i-1];
else
dp[i] = array[i];
if(dp[i] > maxv)
maxv = dp[i];
}
return maxv;
}
};
5、第32题 把数组排成最小的数
题目描述:
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
解题思路:
该题是一个排列的问题,对于两个数字串A和B而言,如果AB排序之后的结果小于BA,则将A排在前面,B排在后面。所以,我们只需要将数组中的所有字符串按照上面的方式进行排序,在将数组中的字符串拼接起来。
代码实现:
class Solution {
public:
static bool cmp(int a,int b)
{
string A = "";
string B = "";
A += to_string(a);
A += to_string(b);
B += to_string(b);
B += to_string(a);
return A<B;
}
string PrintMinNumber(vector<int> numbers) {
string res = "";
if(numbers.size() == 0)
return res;
sort(numbers.begin(),numbers.end(),cmp);
for(int i=0;i<numbers.size();i++)
res += to_string(numbers[i]);
return res;
}
};
6、第35题 数组中的逆序对
题目描述:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
解题思路:
对于该题,我们需要借助归并排序中的思想,先将数组进行分的过程,这个过程将原来的数组不断的进行分解。比较是否逆序对。然后是合并的过程,这个过程中我们借助一个辅助的数组copy,在合并的过程中同时统计两个数组之间的逆序对的数量。在这个合并的过程中,我们需要对数组进行排序,以免在后续的统计中重复统计。
代码实现:
class Solution {
public:
int InversePairs(vector<int> data) {
int length = data.size();
if(length <= 0)
return 0;
vector<int> copy;
for(int i=0;i<length;i++)
copy.push_back(data[i]);
long long count = InversePairCore(data,copy,0,length-1);
return count % 1000000007;
}
long long InversePairCore(vector<int> &data,vector<int> ©,int start,int end)
{
if(start == end)
{
copy[start] = data[start];
return 0;
}
int length = (end - start) /2;
long long left = InversePairCore(copy,data,start,start+length);
long long right = InversePairCore(copy,data,start+length+1,end);
int i = start + length;
int j = end;
int indexcopy = end;
long long count = 0;
while((i >= start) && (j>=start+length+1))
{
if(data[i]>data[j])
{
copy[indexcopy--] = data[i--];
count = count+j-start-length;
}
else
copy[indexcopy--] = data[j--];
}
for(;i>=start;i--)
copy[indexcopy--] = data[i];
for(;j>=start+length+1;j--)
copy[indexcopy--] = data[j];
return left+right+count;
}
};
7 第37题、数字在排序数组中出现的次数
题目描述:
统计一个数字在排序数组中出现的次数。
基本思路:
一次遍历,计算次数即可。
代码实现:
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
int count = 0;
for(int i=0;i<data.size();i++)
if(data[i] == k)
count += 1;
return count;
}
};
8 第40题、数组中只出现一次的数字
题目描述:
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
基本思路:
这里,介绍一个基本的知识,两个数字A,B,如果A,B相同,则AB异或的结果为0。依照这个思路,我们一次对数组中的元素进行异或,最后所剩的结果一定是只出现一次的两个元素。将结果视为二进制,则其中的1表示的就是只出现1次的两个元素A,B的不同的位。下一步我们按照二进制结果中的第一次出现1的位置来将数据分成两个数组。此时AB两个元素肯定在不同的数组中,并且相同的元素肯定在一个数组中,在按照这个规律进行异或。获取最后的结果。
代码实现:
class Solution {
public:
void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
if(data.size() == 0)
return ;
int res = 0;
for(int i=0;i<data.size();i++)
res ^= data[i];
int index = 1;
while((index & res) ==0)
index = index << 1;
int result1 = 0;
int result2 = 0;
for(int i=0;i<data.size();i++)
{
if((index & data[i])==0)
result1 = result1 ^ data[i];
else
result2 = result2 ^ data[i];
}
num1[0] = result1;
num2[0] = result2;
return ;
}
};
9、 第50题 数组中重复的数字
题目描述:
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
解题思路:
本题的思路为采用map映射的方式,遍历一遍数组,记录数组中每一个元素的个数,在遍历的过程中找到数组元素个数不为1的元素。
代码实现:
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
if(length <= 0)
return false;
map<int,int> mp;
for(int i=0;i<length;i++)
{
if(mp.find(numbers[i])!=mp.end())
{
*duplication = numbers[i];
return true;
}
else
{
mp[numbers[i]] ++;
}
}
return false;
}
};
10 第51题 构建乘积数组
题目描述:
给定一个数组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[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)
解题思路:
本题的思路是双向计算的过程,首先是前向计算,对于前向计算,首先从B[0]=1,开始B[i] = B[i-1] *A[i-1]。下一个部分是反向计算,其中B[i] = B[i] *A[i+1],…A[n-1],这样就避开了A[i]元素。
代码实现:
class Solution {
public:
vector<int> multiply(const vector<int>& A) {
vector<int> B;
if(A.size() == 0)
return B;
B.push_back(1);
for(int i=1;i<A.size();i++)
B.push_back(B.back() * A[i-1]);
int temp = 1;
for(int i = A.size()-1;i>0;i--)
{
temp *= A[i];
B[i-1] *= temp;
}
return B;
}
};