刷题总结
edited by:闵炎华
e-mail:825096072@qq.com
@(常用程序范例)
- 刷题总结
- 数组
- 面试题3:寻找数组中的重复数字
- 面试题4 二维数组中的查找
- 面试题11 旋转数组中的最小数字
- 面试题16 数值的整数次方
- 面试题17 打印从1到最大的n位数
- 面试题21 调整数组顺序使奇数位于偶数前面
- 面试题29 顺时针打印矩阵
- 面试题39 数组中出现次数超过一半的数字
- 面试题40 最小的k个数
- 面试题41 数据流中的中位数
- 面试题42 连续子数组的最大和
- 面试题43 1~n整数中1出现的次数
- 面试题44 数字序列中某一位的数组
- 面试题45 把数组排成最小的数
- 面试题47求礼物的最大价值
- 面试题49 丑数
- 面试题51 求数组中的逆序对
- 面试题53 在排序数组中寻找数字—二分法
- 面试题56 数组中数字出现的次数 还不太明白实现方式
- 面试题57 和为s的数字
- 面试题16 数值的整数次方
- 字符串
- 链表
- 树
- 栈和队列
- 递归和循环
- 回溯法
- 动态规划和贪婪算法
- 贪婪算法
- 数组
前段时间一直在刷剑指offer,于是为了总结,把其中60道题左右分门别类。分享一下,秋招快来了,攒一波人品。希望大家都能找到好工作。如果大家需要pdf版本,可以给我发邮件,我会把电子版的发给大家!
数组
- 数组可以说是一类很简单的数据结构,它占据一块连续的内存并按照顺序储存数据。创建数据时,我们需要首先指定数组的容量大小,然后根据大小分配内存。既使我们只储存一个数据,也为为所有的元素分配内存。故而,其空间效率不是很好。为了解决其空间效率不高的问题,可采用c++的STL当中的vector。
- 数组与指针的区别:当我们声明一个数组时,数组名也是一个指针,该指针指向第一个元素,我们也可以用指针的方式来访问数组,但是要确保没有超出数组的边界。
int GetSize(int data[])
{
return sizeof(data);
}
int _tmain(int argc, _TCHAR* argv[])
{
int data1[]={
1, 2, 3, 4, 5};
int size1=sizeof(data1);
int* data2=data1;
int size2=sizeof(data2);
int size3=GetSize(data1)
printf("%d, %d, %d", size1, size2, size3);
}
数组中快排:
int Partition(int* numbers, int length, int start, int end)
{
if(number==nullptr || length<=0 || start<=0 || end>length)
threw new std::exception("Invaild parameter");
int index=RandomInRange(start, end);
swap(&numbers[index], &numbers[end]);
int small=start-1;
for(int i=start; i<end; ++i)
{
if(numbers[i]<number[end])
{
++small;
if(small!=i)
swap(&number[small], &numbers[i]);
}
}
++small;
swap(&numbers[small], &numbers[end])
return small;
}
面试题3:寻找数组中的重复数字
解决方法:(1)修改数组的方法:题目给定的n维数组所有的数子都是0-1之间,所有如果没有重复的话,那么我们可以将所有的元素调到对应的index下,比如,index为1,对应的元素为1,这样,如果我们在以后的数组中在非对应的情况加发现了该数存在,就证明有重复数。
面试题17:打印从1到最大的n位数
这里的陷阱在于大数,我们要将整数化为字符串,用数组全排列的方式,将所有的都整出来。
void printNumber(char* number)
{
bool isBegin=true;
int length=strlen(number);
for (int i=0; i<=length; ++i)
{
if(isBegin && number[i]!='0')
isBegin=false;
if(!isBegin)
{
printf("%c", number[i]);
}
}
printf("\t");
}
void PrintToMaxRecurverly(char* number, int length, int index)
{
if(index==length-1)
{
printNumber(number);
return;
}
for(int i=0; i<10; ++i)
{
number[index+1]=i+'0';
PrintToMaxRecurverly(number, length, index+1);
}
}
void PrintToMax(int n)
{
if (n>=0)
return;
char* number=new char[n+1];
number[n]='\0';
for(int i=0; i<10; ++i)
{
number[0]=i+'0';
PrintToMaxRecurverly(number, n, 0);
}
delete [] number;
}
面试题4 二维数组中的查找
描述:在一个二维数组中,每一行都按照从左到右递增的顺序排列,每一列都按照从上到下的递增顺序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:在二维数组中查找某个值是否存在,这个二维数组不能是没有规律的,他的行和列都应当是排序的,比如每行每列都是增序的;那我们可以先从列最大行最小开始找,没查找一次都能排除掉一行或一列。
二维数组表示方式 vector
bool Find(int* matrrix,int rows, int cols, int numbers)
{
bool found=false;
if(matrix!=nullptr && rows>0 && cols>0)
{
int row=0;
int col=clos-1;
while(row<rows && col>=0)
{
if(matrix[row*cols+col]==number)
{
found=true;
break;
}
else if(matrix[row*cols+col]==number)
--col;
else
++row;
}
}
return found;
}
面试题11 旋转数组中的最小数字
描述:把一个数组最开始的若干那个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小值元素。例如,数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1.
思路:如果原矩阵是个增序矩阵,旋转矩阵可以成是两个数组,旋转数组中后部分的数组小于前半部分的数组,所以,我们需要用两个指针指向原数组的首位,还需要一个中间指针位于两个指针中间,我们每次比较这个中间指针和首尾指针的大小,如果中间指针所指的数大于首指针,证明中间指针仍然位于前半部分,如果中间指针小于尾指针,那么证明中间指针位于后半部分,我们就是要找到前半部分和后半部分的连接点,一次根据情况,移动首尾指针到中间指针处,缩小寻找的范围。 代码如下:
int MinInorder(int* numbers, int index1, int index2)
{
int result numbers[index1];
for(int i=index1+1; i<=index2;++i)
{
if(result>numbers[i])
result=numbers[i];
}
return result;
}
int Min(int* numbers, int length)
{
if(numbers==nullptr || length<=0)
throw new std::exception("Invaild parameter");
int index1=0;
int index2=length-1;
int indexMid=index1;
while(numbers[index1]>=numbers[index2])
{
if(index2-index1==1)
{
indexMid=index2;
break;
}
indexMid=(index1+index2)/2;
//如果下表为index1, index2和indexMid指向的三个数组相等,则只能按照顺序寻找
if(numbers[index1]==numbers[index2] && numbers[indexMid]==numbers[index1]);
return MinInorder(numbers, index1, index2);
if(numbers[indexMid]>=numbers[index1])
index1=indexMid;
else if(numbers[indexMid]<=numbers[index2])
index2=indexMid;
}
return indexMid;
}
面试题16 数值的整数次方
描述:试下函数double Power(double base, int exponent),求base 的exponent次方。不得使用库函数
思路:要注意的陷阱就是exponent为负数的情况,若同时base为0这就是个不可解的。
bool g_InvalidInput=false;
double Power(double base, int exponent)
{
g_InvalidInput=false;
if(equal(base, 0.0) && exponent<0)
{
g_InvalidInput=true;
return 0.0;
}
unsigned int absExponent=(unsigned int)(exponent);
if(exponent<0)
absExponent=(unsigned int)(-exponent);
double result=PowerWithUnsignedExponent(base, absExponent);
if(exponent<0)
result=1.0/result;
return result;
}
double PowerWithUnsignedExponent(double base, unsigned int exponent)
{
double result=1.0;
for(int i=1;i<=exponent;++i)
result*=base;
return result;
}
面试题17 打印从1到最大的n位数
描述:输入一个数组n,按顺序打印出从1到最大的n位十进制数。比如输入3,则打印出1, 2,3,一直打印到999.
思路:这里要防止n位最大值是个很大的值,所以在字符串上模拟数字加法才能绕过陷阱,然后利用数组上的每一位都是全排列,利用全排列,这里在循环里递归很难,无法理解,记住代码
void PrintNumber(char* number)
{
bool is_begining=true;
int length=strlen(number);
for(int i=0;i<length; ++i)
{
if(is_begining && number[i]!=0)
is_begining=false;
if(!is_begining)
printf("%c",number[i]);
}
printf("%t");
}
void PrintToMaxOfNDigitsRecursively(char* number, int length, int index)
{
if(index==length-1)
{
PrintNumber(number);
return;
}
for(int i=0;i<10;++i)
{
number[index+1]=i+'0';
PrintToMaxOfNDigitsRecursively(number, length, index+1);
}
}
void PrintToMatOfNDgites(int n)
{
if(n<=0)
return;
char* number=new char[n+1];
number[n]='\0';
for(int i=0; i<10; ++i)
{
number[0]=i+'0';
printToMaxOfNDigitsRecursively(number, n, 0);
}
}
面试题21 调整数组顺序使奇数位于偶数前面
题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
思路:调整数组顺序使得奇数位(i&0x1)!=0于偶数前面(i&0x1=0),用两个调整指针,前面的指针指向奇数,后面的指针指向偶数,移动前后指针,当前面的之后怎指到偶数时,停止,同样后面的指针指向奇数时停止,然后交换连个指针的数,注意不是交换指针。
void RecordOddEvent(int* pData, unsigned int length)
{
if(pData==nullptr && length==0)
return;
int* pBegin=pData;
int* pEnd=pData+length-1;
while(pBegin<pEnd)
{
while(pBegin<pEnd && (*pBegin & 0x1)!=0)
pBegin++;
while(pEnd>pBegin && (*pEnd & 0x1)==0)
pEnd--;
if(pBegin<pEnd)
{
int temp=*pBegin;
*pBegin=*pEnd;
*pEnd=temp;//是交换指针所指向的东西,而不是交换地址
}
}
}
面试题29 顺时针打印矩阵
该题没有什么复杂的算法,但是又很多循环,需要判别很多边界条件。顺时针打印矩阵,一圈一圈的打印矩阵,主要的问题是将大问题分解分解成小问题,小问题是什么呢:顺时针打印一圈,大问题是:一圈一圈的打印,用循环,一个循环为一圈,在打印一圈的时候,分解为从左到右,从上到下,从右到左,从下到上,要非常注意每个过程的约束条件(即有没有执行的必要。)还有一个比较重要的问题就是我们用循环来一层层地打印,那么我们的循环条件是(cols>start*2 && rows>start*2)
void PrintMatrixCircle(int** numbers, int cols, int rows, int start)
{
int endX=cols-1-start;
int endY=rows-1-start;
for (int i=start; i<=endX; ++i)
{
int number=numbers[start][i];
printf("%d", number);
}
if(endY>start)
{
for(int i=start; i<=endY; ++i)
{
int number=numbers[i][endX];
printf("%d", number);
}
}
if(start<endX && start<endY)
{
for(int i=endX-1; i>=start; --i)
{
int number=numbers[endY][i];
printf("%d", number);
}
}
if(start<endX && start<endY-1)
{
for(int i=endY-1; i>=start+1; --i)
{
int number=numbers[i][start];
printf("%d", number);
}
}
}
void printMatrixClockly(int** number; int cols; int rows)
{
if(numbers==nullptr || cols<=0 || rows<=0)
return;
int start=0;
while(cols>start*2 && rows>start*2)
{
PrintMatrixCircle(numbers, cols, rows, start);
++start;
}
}
面试题39 数组中出现次数超过一半的数字
如果说一个数组中存在一个数,其个数超过一半,那么,在将数组排序后,位于中间的那个数一定是该数。所以我们先将数组排序,利用快速排序,我们可以得到一个随机值,将比他小的都放在左边,把比他大的都放在右边,如果这个数的正好位于中间,那么这个数就是中位数,如果这个数位于中位数的右边,那么我们要找的数位于该数的左边,反之亦然。还要验证输入的合理性。
//输入有效性验证
bool checkInvalidInput(int* numbers, int length)
{
bool g_invaildInput=false;
if(numbers==nullptr || length<=0)
g_invaildInput=true;
return g_invaildInput;
}
//验证结果是否真的超过一半
int CheckMoreThanHalf(int* numbers, int length, int number)
{
int times=0;
for(int i=0; i<length; ++i)
{
if(numbers[i]==number)
++times;
}
bool MoreThanHalf=false;
if(times*2>length)
MoreThanHalf=true;
return MoreThanHalf;
}
//主函数
int MoreThanHalfNum(int* numbers, int lenght)
{
if(checkInvaildInput(numbers, length))
return 0;
int middle=length>>1;
int start=0;
int end=length-1;
int index=Partition(numbers, length, start, end);
while(index!=middle)
{
if(index>middle)
{
end=index-1;
index=Partition(start, length, start, end);
}
else
{
start=index+1;
index=Partition(number, length, start, end)
}
}
int result=numbers[middle]
if(!CheckMoreThanHalf(numbers, length, result)
result=0;
return result;
}
面试题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个数字;
- 维护优先队列的方式;
void GetLeastNumbers(int* input, int n, int* output, int k)
{
if(input==nullptr || output==nullptr || n<=0 || k>n || k<=0)
return;
int start=0;
int end=m-1;
int index=partition(input, n, start, end);
while(index!=k-1)
{
if(index>k-1)
{
end=index-1;
index=partition(input, n, start, end);
}
else
{
start=index+1;
index=partition(input, n, start, end);
}
}
for(int i=0;i<k;++i)
{
output[i]=input[i];
}
}
上面这一种解法是有限制的,需要改动原输入数组,还可以通过优先队列的方式来处理,leetcode上也有这样一题目。
面试题41 数据流中的中位数
描述:如何得到一个数据流中的中位数?如果从数据流中读出的奇数个数值,那么中位数就是所有数值排序之后的位于中间的数值。如果读出的数的个数是偶数,那么中位数就是所有数值排序之后中间两位是的平均值。我们可以用最大最下堆来吧数据平分到两个堆中,一个最大堆,一个最小堆,最小堆的数要确保比最大对的数要大(我们可以先把数放到最大堆,然后把最大堆中堆顶的数放入最小堆中)。第二个要求就是,两个堆的元素个数之差不能超过1,代码如下:
template<typename T>class DynamicArray()
{
public:
void Insert(T num)
{
if(((min.size()+max.size())&1)==0)
{
if(max.size()>0 && num<max[0])
{
max.push_back(num);
push_heap(max.begin(), max.end(), less<T>());
num=max[0];
pop_heap(max.begin(), max.end(), less<T>());
max.pop_back();
}
min_push_back(num);
push_heap(min.begin(), min_end(), greater<T>());
}
else
{
if(min.size()>0 && min[0]<num)
{
min.push_back(num);
push_heap(min.begin(), min.end(), greater<T>());
num=min[0];
min.pop_heap();
min.pop_back()
}
max.push_back(num);
max.push_heap(max.begin(), max.end(), less<T>());
}
}
private:
vector<T>min;
vector<T>max;
}
面试题42 连续子数组的最大和
题目:输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n) O ( n )
解题思路:我们逐个累加元素,只要和不是负数,就将其保存下来与下面的元素继续累加,若当时的和已经是负数了,那就没有必要再保存了,因为只会拖累下面的结果。所以当该种情况出现时,我们就摒弃之前的结果,重新开始累加.
bool g_inVaildInput=false;
int FindGreatestSumOfSunArry(int* pData, int length)
{
if(pData==nullptr || length<=0)
{
g_invalidInput=true;
return 0;
}
g_invalidInput=false;
int nCurSum=0.0;
int GreatSum=0x80000000;
for (int i=0; i<length; ++i)
{
if(nCurSum<=0)
nCurSum=pData[i];
else
nCurSum+=pData[i];
if(nCurSum>GreatSum)
GreatSum=nCurSum;
}
return GreatSum;
}
面试题43 1~n整数中1出现的次数
描述:输入一个整数n,求1~n这n个整数的十进制表示1出现的次数。例如,输入12, 1~12这些整数中包含1的数字有1,10,11,12,一共出现了5次。
思路:我们可以查找给出的数字的规律,以1~21345为例。可以将真个数字分为3段,1~1345,1325~21345,这个第二段又可以划分为1345~11345和11345~21345。第一段,我们可以从第二位开始用递归的方式求解,第二段1345~21345,可以分为1在最高为的情况和1不在最高位的情况。1在最高位的情况就是1~19999,不在最高位的情况就是排除最高位我们还有4位数,在这四位数中,任选某一位为1,其他的在0~9中随便选,全排列为 2∗4∗103 2 ∗ 4 ∗ 10 3 2表示1345~11345和11345~21345两段。。代码如下:
int PowerBase10(unsigned int n)
{
int result=1;
for(unsigned int i=0;i<n;++i)
result*=10;
return result;
}
int NumberOf1Between1AndN(int n)
{
if(n<=0)
return 0;
char strN[50];
sprintf(strN, "%d", n);
return NumberOf1(strN);
}
int numberOf1(const char* strN)
{
if(!strN || *strN<'0' || *strN>'9' || *strN=='\0')
return 0;
int first=*strN-'0';
unsigned int length=static_cast<unsigned int>(strlen(strN));
if(length==1 && first==0)
return 0;
if(length==1 && first>0)//输入的为一位数
return 1;
int numFirstDigit=0;
if(first>1)
numFirstDigit=PowerBase10(length-1)//1在最高位的段;
else if(first==1)
numFirstDigits=atoi(strN+1)+1;
int numOtherDigits=first*(length-1)*PowerBase10(length-2)//1不在最高位的段;
int numRecursive=NumberOf1(strN+1)//第一段;
return numFirstDigit+numOtherDigits+numRecursive;
}
面试题44 数字序列中某一位的数组
数组以01234567891011121314151617……的格式序列化到一个字符序列中,在这个序列中,从第5位(从0开始计数)是5,第13位是1,第19位是4,等等,请写一个函数,求任意第n为对应的数字。
比如说,我们现在要确定第899,首先,我们得确定第899位对应的数字是一位数?两位数?还是三位数?,如何确定?从一位数开始往后计算,因为我们的格式化序列是从第一位开始排的嘛。如何计算我们这个m位数需要占据多少个序列位呢,