面试题9:斐波那契数列
题目:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。n<=39
举一反三:青蛙跳台阶;小矩形覆盖大矩形。
class Solution {
public:
int Fibonacci(int n) {
int num[n];
num[0] = 0;
num[1] = 1;
for(int i=2; i<=n; i++){
num[i] = num[i-2]+num[i-1];
}
return num[n];
}
};
题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
class Solution {
public:
int jumpFloor(int number) {
int num[number];
num[0] = 1;
num[1] = 1;
for(int i=2; i<=number; i++){
num[i] = num[i-1]+num[i-2];
}
return num[number];
}
};
题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
解题思路:得出规律f(n)=2的(n-1)次方。
class Solution {
public:
int jumpFloorII(int number) {
int num[number];
num[1] = 1;
for(int i=2; i<=number; i++){
num[i] = 2*num[i-1];
}
return num[number];
}
};
题目:我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
解题思路:斐波那契数列
class Solution {
public:
int rectCover(int number) {
if(number <= 0)
return 0;
if(number == 1)
return 1;
if(number ==2)
return 2;
int sum, num1=1, num2 = 2;
for(int i=3;i<=number;i++){
sum = num1+num2;
num1 = num2;
num2 = sum;
}
return sum;
}
};
面试题10:二进制中1的个数
题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
解题思路:第一种方法是该数和1相与后右移一位,但是无法解决负数问题;第二种方法是该数和1相与后,1左移一位,循环次数就是该数的二进制位数;第三种方法是该数和(该数减1)相与,那么该数的二进制中最右边的1变成0其他位数不变,循环次数就是该数二进制中1的个数,此法最佳。
举一反三:用一条语句判断一个整数是不是2的整数次方(如果是的话,它的二进制表示有且只有1位是1)。计算需要改变m的二进制数中的多少位才能得到n(先异或,再统计异或结果中1 的位数)。
class Solution {
public:
int NumberOf1(int n) {
int count = 0;
while(n){
count++;
n = (n-1) & n;
}
return count;
}
};
面试题11:数值的整数次方
题目:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
解题思路:两个情况需要考虑到,一是base为0,exponent<0时,返回0,用全局变量g_InvalidInput提示;二是base非0,exponent<0时,对正数时的它求导数即可。
class Solution {
public:
bool g_InvalidInput = false;
double Power(double base, int exponent) {
if(equal(base,0.0) && exponent<0){
g_InvalidInput = true;
return 0.0;
}
int absExponent = exponent;
if(exponent<0)
absExponent = -exponent;
double res = powerWithUnsignedExponent(base, absExponent);
if(exponent<0)
res = 1.0/res;
return res;
}
bool equal(double num1, double num2){
if((num1 - num2 < 0.0000001) && (num1 - num2 > -0.0000001))
return true;
else
return false;
}
double powerWithUnsignedExponent(double base, int exponent){
if(exponent == 0)
return 1;
if(exponent == 1)
return base;
double result = powerWithUnsignedExponent(base, exponent>>1);
result *= result;
if(exponent & 0x1 == 1)
result *= base;
return result;
}
};
面试题20:顺时针打印矩阵
题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
解题思路:要注意中间maxCol和minCol、maxRow和minRow的大小判断,以防剩下一列和一行的情况。
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
vector<int> result;
int rows = matrix.size();
int columns = matrix[0].size();
int minRow = 0, maxRow = rows-1, minCol = 0, maxCol = columns-1;
int curRow, curCol;
while(minRow<=maxRow && minCol<=maxCol){
for(curRow = minRow, curCol = minCol; curCol <= maxCol; curCol++){
result.push_back(matrix[curRow][curCol]);
}
minRow++;
for(curRow = minRow, curCol = maxCol; curRow <= maxRow; curRow++){
result.push_back(matrix[curRow][curCol]);
}
maxCol--;
if(maxCol < minCol || maxRow < minRow)
break;
for(curRow = maxRow, curCol = maxCol; curCol >= minCol; curCol--){
result.push_back(matrix[curRow][curCol]);
}
maxRow--;
for(curRow = maxRow, curCol = minCol; curRow >= minRow; curRow--){
result.push_back(matrix[curRow][curCol]);
}
minCol++;
}
return result;
}
};
面试题32:从1到n整数中1出现的次数
题目:输入一个整数n,求从1到n这n个整数的十进制中1出现的次数。
解题思路:把数分成两部分,对前部分递归计算,对后部分直接计算。
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
if(n <= 0)
return 0;
char str[50];
sprintf(str,"%d",n);
return numberOf1(str);
}
int numberOf1(const char* str){
if(!str || *str=='\0' || *str<'0' || *str>'9')
return 0;
//假设str是“21345”
int firstNum = *str - '0'; //firstNum=2
int length = strlen(str);
if(length==1 && firstNum==0) //终止条件是length=1
return 0;
if(length==1 && firstNum>0)
return 1;
int firstDigit = 0;
//firstDigit保存1345-21345之间最高为1的数的数目
if(firstNum > 1)
firstDigit = powerBase10(length-1);
else if(firstNum == 1)
firstDigit = atoi(str+1)+1;
//OtherDigits保存1345-21345之间后4位出现1的数的数目:2*4*1000
int OtherDigits = firstNum * (length-1) * powerBase10(length-2);
//recursive保存1-1345之间出现1的数目,递归
int recursive = numberOf1(str+1);
//三数相加
return firstDigit+OtherDigits+recursive;
}
int powerBase10(int n){
int result = 1;
while(n--)
result *= 10;
return result;
}
};
面试题34:丑数
题目:把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
解题思路:寻找规律以避免对非丑数的计算。新的丑数来自于旧的丑数乘以2/3/5,所以用一个辅助数组保存已经计算出的丑数,将其乘以2/3/5,分别求出比已有最大丑数M大但又最接近M的数:M2/M3/M5,再求其中最小的一个,就是下一个丑数。这个方法以空间消耗换取时间效率的提升。
class Solution {
public:
int GetUglyNumber_Solution(int index) {
if(index<=0)
return 0;
int uglyNumbers[index];
uglyNumbers[0] = 1;
int *M2 = uglyNumbers;
int *M3 = uglyNumbers;
int *M5 = uglyNumbers;
int uglyIndex = 1;
while(uglyIndex < index){
int minNum = Min(*M2 * 2, *M3 * 3, *M5 * 5);
uglyNumbers[uglyIndex++] = minNum;
while(*M2 * 2 <= minNum)
M2++;
while(*M3 * 3 <= minNum)
M3++;
while(*M5 * 5 <= minNum)
M5++;
}
int result = uglyNumbers[uglyIndex-1];
return result;
}
int Min(int a,int b,int c){
int min = (a < b) ? a : b;
return (min < c) ? min : c;
}
};
面试题41:和为S的连续正数序列
题目:输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
解题思路:两个指针从头出发,若小于sum则right向前走一步,若大于sum则left向前走一步,若等于sum则添加到vector中接着让right向前走一步,停止条件是left走到(sum+1)/2.
class Solution {
public:
vector<vector<int> > FindContinuousSequence(int sum) {
vector<vector<int>> result;
if(sum < 3)
return result;
int left = 1;
int right = 2;
int curSum = left + right;
while(left < (sum+1)/2){
if(curSum < sum){
right++;
curSum += right;
}else if(curSum > sum){
curSum -= left;
left++;
}else{
vector<int> tmp;
for(int i=left;i<=right;i++){
tmp.push_back(i);
}
result.push_back(tmp);
right++;
curSum += right;
}
}
return result;
}
};
题目:输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。对应每个测试案例,输出两个数,小的先输出。
解题思路:两个指针一头一尾出发。
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
vector<int> result;
int len = array.size();
if(len > 1){
int left = 0;
int right = len-1;
while(left < right){
int curSum = array[left] + array[right];
if(curSum == sum){
result.push_back(array[left]);
result.push_back(array[right]);
break;
}else if(curSum > sum){
right--;
}else{
left++;
}
}
}
return result;
}
};
面试题44:扑克牌的顺子
题目:从扑克牌中随机抽几张牌,判断是不是一个顺子。2-10为本身,A为1,J为11,Q为12,K为13,大小王可以看成任意数字(在输入中0代表大小王)。
解题思路:先排序,统计数组中0的个数,最后统计排序后的数组中相邻数字之间的空缺总数。如果空缺总数小于或者等于0的个数,那么这个数组就是连续的,反之则不连续。
class Solution {
public:
bool IsContinuous( vector<int> numbers ) {
int len = numbers.size();
if(len==0)
return false;
sort(numbers.begin(),numbers.end());
int numOfZero = 0;
for(int i=0;i<len;i++)
if(numbers[i] == 0)
numOfZero++;
int numOfGap = 0;
int small = numOfZero;
int big = small + 1;
while(big<len){
if(numbers[small] == numbers[big])
return false;
numOfGap += numbers[big] - numbers[small] - 1;
small++;
big++;
}
return (numOfZero >= numOfGap ? true : false);
}
};
面试题45:圆圈中最后剩下的数字
题目:0,1,…,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
解题思路:最直观的想法就是用环形列表来实现。但是时间复杂度有O(mn),空间复杂度有O(n)。最好的办法是寻找数学规律,就会变得很简单。
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if(n<1 || m<1)
return -1;
list<int> nums;
for(int i=0;i<n;i++)
nums.push_back(i);
list<int>::iterator cur = nums.begin();
while(nums.size() > 1){
for(int i=1;i<m;i++){
cur++;
if(cur == nums.end())
cur = nums.begin();
}
list<int>::iterator next = ++cur;
if(next == nums.end())
next = nums.begin();
nums.erase(--cur);
cur = next;
}
return *cur;
}
};
//下面这个更好
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if(n<1 || m<1)
return -1;
int last = 0;
for(int i=2; i<=n; i++){
last = (last + m) % i;
}
return last;
}
};
面试题46:求1+2+3+…+n
题目:求1+2+3+…+n。要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句。
//用n>0的&&来代替终止判断。
class Solution {
public:
int Sum_Solution(int n) {
int sum = n;
bool ans = (n>0)&&((sum+=Sum_Solution(n-1))>0);
return sum;
}
};
//利用函数指针求解
typedef int (*fun)(int);
class Solution {
public:
static int Solution_Terminator(int n){
return 0;
}
static int Sum_Solution(int n) {
static fun f[2] = {Solution_Terminator,Sum_Solution};
return n+f[!!n](n-1);
}
};
面试题47:不用加减乘除做加法
题目:求两个整数之和。要求在函数体内不得使用+、-、*、/ 四则运算符号。
解题思路:首先想到位运算,然后经过分析可以发现是位异或、位相与再左移一位的结果的和。
class Solution {
public:
int Add(int num1, int num2)
{
int sum,carry;
do{
sum = num1 ^ num2;
carry = (num1 & num2) << 1;
num1 = sum;
num2 = carry;
}while(num2 != 0);
return num1;
}
};
面试题49:把字符串转换成整数
题目:将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
解题思路:看似简单的题目,但是需要考虑很多特殊情况,比如是否为空、正负号、非法溢出、非法字符(除0到9之外的数字)的处理。
class Solution {
public:
int StrToInt(string str) {
int len = str.length();
if(len == 0)
return 0;
long num = 0;
int flag = 1;
int i = 0;
while(str[i] == ' ') //过滤前面的空格
i++;
if(str[i] == '+') //处理正负号
i++;
else if(str[i] == '-'){
i++;
flag = -1;
}
while(str[i] != '\0'){
if(str[i]>='0' && str[i]<='9'){
num = num * 10 + flag *(str[i] - '0');
if((flag==1 && num>0x7fffffff) || (flag==-1 && num<(signed int)0x80000000)){ //处理溢出
num = 0;
break;
}
i++;
}else{ //处理非法字符
num = 0;
break;
}
}
return num;
}
};
面试题53:正则表达式匹配
题目:请实现一个函数用来匹配包括’.’和’‘的正则表达式。模式中的字符’.’表示任意一个字符,而’‘表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串”aaa”与模式”a.a”和”ab*ac*a”匹配,但是与”aa.a”和”ab*a”均不匹配
解题思路:最直观的两个匹配情况是:str中的字符和pattern中的字符是同种字符;pattern中的字符是’.’,str中的字符可以任意。复杂的是pattern字符是’‘,画一个状态机就可以知道,有三种匹配模式:str的字符和pattern’‘前面的字符不匹配,pattern向后移动两个字符;str的字符和pattern’‘前面的字符匹配,str向后移动一个字符,pattern向后移动两个字符;str的字符和pattern’‘前面的字符匹配,str向后移动一个字符,pattern不移动。停止条件是:如果str和pattern同时到达末尾,则模式匹配成功;如果str没有到达末尾,而pattern到达末尾,则模式匹配失败。
class Solution {
public:
bool match(char* str, char* pattern)
{
if(str == NULL || pattern == NULL)
return NULL;
return matchCore(str,pattern);
}
bool matchCore(char* str,char* pattern){
if(*str == '\0' && *pattern == '\0')
return true;
if(*str != '\0' && *pattern == '\0')
return false;
if(*(pattern+1) == '*'){
if(*pattern == *str || (*pattern=='.'&&*str!='\0'))
return (matchCore(str+1,pattern+2) || matchCore(str+1,pattern) || matchCore(str,pattern+2));
else
return matchCore(str,pattern+2);
}
if(*str == *pattern || (*pattern == '.' && *str != '\0'))
return matchCore(str+1, pattern+1);
return false;
}
};
面试题64:数据流中的中位数
题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
解题思路:首先要确定用什么数据结构来定义存放数据的数据容器,需要考虑插入和查找的时间复杂度。经过对数组、链表、二叉搜索树、AVL树的分析,插入最少需要O(log n)时间,查找最少需要O(1),但是AVL数的实现较复杂,所以想到用一个最小堆和一个最大堆来实现。接下来是实现的细节,首先需要保证数据平均分配到两个堆中,因此两个堆中的数目之差不能超过1,可以在当前数据的总数目为偶数时,将数据插入到最小堆中,为奇数时,插入到最大堆中。但是还要保证最大堆中的数据都比最小堆中的数据来得小,插入时怎么保证这一点,可以先把数据插入到最大堆中,再把最大堆中最大的数据拿出来插入最小堆中。奇数时也是类似。
class Solution {
public:
void Insert(int 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<int>());
num = max[0];
pop_heap(max.begin(),max.end(),less<int>());
max.pop_back();
}
min.push_back(num);
push_heap(min.begin(),min.end(),greater<int>());
}else{
if(min.size()>0 && num > min[0]){
min.push_back(num);
push_heap(min.begin(),min.end(),greater<int>());
num = min[0];
pop_heap(min.begin(),min.end(),greater<int>());
min.pop_back();
}
max.push_back(num);
push_heap(max.begin(),max.end(),less<int>());
}
}
double GetMedian()
{
int size = min.size() + max.size();
//if(size == 0)
//throw exception("No numbers are available");
double median;
if((size & 1) == 0)
median = (min[0] + max[0])/2.0;
else
median = min[0];
return median;
}
private:
vector<int> min;
vector<int> max;
};
面试题65:滑动窗口的最大值
题目:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
解题思路:如果采用蛮力法,每个窗口需要O(k)的时间才能找出滑动窗口里的最大值,对于长度为n的输入数组,总的时间复杂度为O(nk)。可以把一个滑动窗口看成是一个两端开口的队列,只把有可能成为滑动窗口最大值的数值存入到这个两端开口的队列(deque)中。
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> max;
deque<int> index;
if(num.size() >= size && size >= 1){
for(unsigned int i=0; i<num.size(); i++){
while(index.size() && num[i] >= num[index.back()])//把队列中所有比待输入的数字num小的数字都弹出
index.pop_back();
if(index.size() && (i-index.front()+1)>size) //判断队首的数字有没有超过滑窗的大小
index.pop_front();
index.push_back(i);
if(size&&i+1>= size) 滑动到第size个num才开始放入结果
max.push_back(num[index.front()]);
}
}
return max;
}
};
面试题66:矩阵中的路径
题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
解题思路:这是个用回溯法解决的典型题。由于回溯法递归的特性,路径可以被看成是一个栈,当在矩阵中定位了路径中前n个字符的位置之后,在第n个字符的周围寻找第n+1个字符,若没有则回到第n-1个字符重新定位第n个字符。路径的起点可以是矩阵中的任一点。由于路径不能重复进入矩阵的格子,还需要定义和字符矩阵大小一样的布尔值矩阵,用于标识路径是否已经进入了每个格子。
class Solution {
public:
bool hasPath(char* matrix, int rows, int cols, char* str)
{
if(matrix == NULL || rows<1 || cols<1 || str==NULL)
return false;
bool *visited = new bool[rows * cols];
memset(visited,0,rows*cols);
int pathLength = 0;
for(int row=0;row<rows;row++){
for(int col=0;col<cols;col++){
if(hasPathCore(matrix,rows,cols,row,col,str,pathLength,visited))
return true;
}
}
delete[] visited;
return false;
}
bool hasPathCore(char* matrix,int rows,int cols,int row,int col,char* str,int &pathLength,bool* visited){
if(str[pathLength] == '\0')
return true;
bool hasPath = false;
if(row>=0 && row<rows && col>=0 && col<cols && matrix[row*cols+col]==str[pathLength] && !visited[row*cols+col]){
pathLength++;
visited[row*cols+col] = true;
hasPath = hasPathCore(matrix,rows,cols,row,col-1,str,pathLength,visited) ||
hasPathCore(matrix,rows,cols,row,col+1,str,pathLength,visited) ||
hasPathCore(matrix,rows,cols,row+1,col,str,pathLength,visited) ||
hasPathCore(matrix,rows,cols,row-1,col,str,pathLength,visited);
if(!hasPath){
pathLength--;
visited[row*cols+col] = false;
}
}
return hasPath;
}
};
面试题67:机器人的运动范围
题目:地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
解题思路:用的也是回溯法,机器人从坐标(0,0)开始移动,当它准备进入到坐标为(i,j)的格子时,通过检查坐标的数位和来判断机器人能否进入,如果机器人能够进入坐标为(i,j)的格子,再接着判断它能否进入四个相邻的格子。
class Solution {
public:
int movingCount(int threshold, int rows, int cols)
{
bool *visited = new bool[rows*cols];//标识这个格子是否已经进过了
memset(visited,0,rows*cols);
int count = movingCountCore(threshold,rows,cols,0,0,visited);//从原点出发
delete[] visited;
return count;
}
int movingCountCore(int threshold,int rows,int cols,int row,int col,bool* visited){
int count = 0;
if(check(threshold,rows,cols,row,col,visited)){ //check函数判断机器人能否进入坐标为(row,col)的格子
visited[row*cols+col] = true;
count = 1+movingCountCore(threshold,rows,cols,row+1,col,visited)+
movingCountCore(threshold,rows,cols,row-1,col,visited)+
movingCountCore(threshold,rows,cols,row,col+1,visited)+
movingCountCore(threshold,rows,cols,row,col-1,visited);
}
return count;
}
bool check(int threshold,int rows,int cols,int row,int col,bool* visited){
if(row>=0 && row<rows && col>=0 && col<cols &&
getDigitSum(row)+getDigitSum(col)<=threshold && !visited[row*cols+col])
return true;
return false;
}
int getDigitSum(int num){ //getDigitSum用来你得到一个数字的数位之和
int sum = 0;
while(num>0){
sum += num%10;
num /= 10;
}
return sum;
}
};