(1)面试题目搜集1
(2)面试题目搜集2
(3)面试题目搜集3
(4)面试题目搜集4
(5)面试题目搜集5
(6)面试题目搜集6
(7)百度面试题目搜集
1.一个台阶总共有n级,如果一次可以跳1级,也可以跳2级,求总共有多少种跳法。
相信大家一看到这个题就知道是个斐波拉契序列,没错,它就是这个答案。
f[n] = f[n-1] + f[n-2];//f[n-1]表示f[n]跳一级剩下的级数,f[n-2]表示f[n]跳二级剩下的级数
f[1] = 1;
f[2] = 2;//1+1或者2
int Fibonacci(int n)
{
if(n == 1)return 1;
if(n == 2)return 2;
return Fibonacci(n-1)+Fibonacci(n-2);
}
<<编程之美>>上关于斐波拉契序列的求法给出了四种答案:(1)递归;(2)迭代;(3)直接推了一个方程;(4)通过[f(n) f(n-1)] = [f(n-1) f(n-2)] A,转换为A矩阵的n次方,n表示成2进制,计算出A的幂。(海涛的想法就是A^n=(A^n/2)^2,这样降到o(logn)的时间复杂度。)
我看到这个题想了这么一个问题:”假设我要将一个数转换成1和2的和,1和2可以任意多个,求有多少种做法?”那是不是也可以同样的一个表达式来递推:f[n] = f[n-1] + f[n-2]。后来仔细想想发现不是,f[n] = f[n-1] + f[n-2]这个表达式隐藏了一个顺序的概念。什么意思?举个例:假设有3个台阶,则1+2的跳法和2+1的跳法是不一样的跳法;但是我们如果将3转换成1和2的和时,1+2和2+1其实是一种解。
这就说明我们不能用这个解法来解“假设我要将一个数转换成1和2的和”的问题。那这个递推关系式是什么,考虑了一下应该是这样的。我们只要强制转换一下这个问题,怎么转换呢?我们只要限定我们一定是先跳1级台阶,才能再跳2级台阶,就是说当你开始跳2级台阶了以后,就不能再跳1级台阶了,这样解就不会出现1+2和2+1这种重复解了。那么按照这个思想我们就可以写出如下递推关系式:f[i,n] = f[i-1,n] + f[i-1,n-2] + f[i-1,n-4]...一直到n-2*k>0,此时i只能为2和1。按照上面这个思想我们可以写出这个代码:
#include <iostream>
#include <vector>
using namespace std;
#define NUM 2
int steps[NUM]={1,2};
int CalPermNum(int index,int num)
{
if(index < 0 || num <0 ) return 0;
if(index == 0 || num == 1)return 1;
int sum = 0;
for(int i = num; i >= 0; i-= steps[index]){
sum += CalPermNum(index-1,i);//一定是index-1
}
return sum;
}
void main()
{
vector<int> vec;
cout<<CalPermNum(1,5)<<endl;
system("pause");
}
2. 再举个例子:将一个较大的钱,不超过1000000(10^6)的人民币,兑换成数量不限的100、50、10、20、5、2、1的组合,请问共有多少种组合呢?
按照上面的分析我们不能简单的将F(N)=F(N-100)+F(N-50)+F(N-20)+F(N-10)+F(N-5)+F(N-2)+F(N-1),因为这里面的大部分解都是重复的。
同样,我们需要额外加一个变量,表示当前用到了第index中人民币的面值。
(1)动态规划的递归解法
我们这里设一个数组表示人民币的面值:int money[MAX]={1,2,5,10,20,50,100};此时,我们就可以按照上面的思路来解:
(1)只用 50 及以下的面值构成 [n] + 0 张 100
(2)只用 50 及以下的面值构成 [n-100] + 1 张 100
(3)只用 50 及以下的面值构成 [n-200] + 2 张 100
……
也就是说,按 F[n] 的组成方案中 100 的张数,将 F[n] 划分成若干等价类,等价类的划分要不重复、不遗漏。这些等价类恰好完整覆盖了 F[n] 的所有情况。
#include <iostream>
#include <vector>
using namespace std;
#define NUM 7
int money[NUM]={1,2,5,10,20,50,100};
int CalPermNum(int index,int num)
{
if(index < 0 || num <0 ) return 0;
if(index == 0 || num == 1)return 1;
int sum = 0;
for(int i = num; i >= 0; i-= money[index]){
sum += CalPermNum(index-1,i);//一定是index-1
}
return sum;
}
void main()
{
vector<int> vec;
cout<<CalPermNum(1,5)<<endl;
system("pause");
}
(2) 完全背包递归解法
其中 dp[i][j] 表示只用第 i 张面值及以下构成 j 用多少种方法。
改进如下:
a[6][n] = ar[6][n-100] // 至少包含 1 张 100 的拆分个数
+ ar[5][n] // 不包含 100 的拆分个数
我们将当前的解分为用了第i张的面值和没有第i张的面值,利用这种思想,我们可以用递归写出如下代码。
#define NUM 7
int money[NUM]={1,2,5,10,20,50,100};
int CalPermNum(int index,int num)
{
if(index < 0 || num <0 ) return 0;
if(index == 0 || num == 1)return 1;
//一定index-1和index的混合,细细的体味一下这个意思
return CalPermNum(index-1,num)+CalPermNum(index,num-money[index]);
}
这道题其实是一个完全背包问题:http://blog.youkuaiyun.com/lsjseu/article/details/11400201给出了四种解法:(1)母函数;(2)动态规划;(3)完全背包问题;(4)完全背包问题滚动数组优化。本文的解法1其实里面的动态规划解法的递归解法,解法2其实就是里面的完全背包递归解法。
3. 再提个问题:将一个较大的钱,不超过1000000(10^6)的人民币,兑换成数量不限的100、50、10、5、2、1的组合,将这些组合输出来。
#include <iostream>
#include <vector>
using namespace std;
#define NUM 7
int money[NUM]={1,2,5,10,20,50,100};
void dfs(int sum,vector<int>& vec, int curSum, int id)
{
int i;
if(curSum == sum)
{
static int inum = 1 ;
cout<<"方案 "<<inum++<<": ";
for(i = 0; i < vec.size() ; ++i)
cout<<vec[i]<<" ";
cout<<endl;
return;
}
for(i = id ; i <= sum; ++i)
{
if(curSum+money[i] <= sum)
{
vec.push_back(money[i]); //求组合的时候,下面就是i+1
dfs(sum,vec,curSum+money[i],i); //这里不是i+1,表示可以重复
vec.pop_back();
}
else
break;
}
}
void main()
{
vector<int> vec;
dfs(20,vec,0,0);
system("pause");
}
另外本博客《面试题目搜集2》的第11题整数拆分问题,其实就是同样一个问题。http://blog.youkuaiyun.com/lsjseu/article/details/9390443
4. 有一百个灯泡关着的,标号为1到100,现在有100个人,每个人走过去将标号为其倍数的灯泡关掉,最后亮着的有哪些。
分析:其实,就是一个打印出1到100内的完全平方数。
平方数的方法可以这么来求:(n+1)^2 = (n-1)^2 + (2n+1) = 1 + 3 +....+(2n+1)。
即一个n^2 = 1 + 3 +....+(2n-1)。 举例验证:4^2 = 1 + 3 + 5 +(2*4-1)。于是可写出如下o(根号n)代码:
void PrintSquareNum(int num)
{
int i = 1;
int square = 0;
while(square < num){
square += i;
i += 2;
cout<<square<<" ";
}
}
5.写一个函数,检查字符是否是整数,如果是,返回整数值。(用4行代码)
思路:和高手学习一下,递归思路
正数: 例如65535 = 6553 *10 +5 ; 6553 = 655*10 +3 。。。。如此递归下去
复数: -65535 = -6553 *10 -5; -6553 = -655*10 -3 。。。。 但是到了 -6的时候 仍然执行 -6 *10,所以到str[0] = '-'时, 返回 -0.1作为平衡。
long strtoint(char *str,int length){
if(length > 1) {
return str[0]=='-' ? strtoint(str, length-1)*10-(str[length-1]-'0') : strtoint(str, length-1)*10+str[length-1]-'0';
} else {
return str[0]=='-' ? -1/10 : str[0]-'0';
}
}
要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。
例如:
var a=[100,99,98,1,2, 3];
var b=[1, 2, 3, 4,5,40];
求解思路:http://blog.youkuaiyun.com/chdhust/article/details/11610459
当前数组a和数组b的和之差为
A = sum(a) - sum(b)
a的第i个元素和b的第j个元素交换后,a和b的和之差为
A' = sum(a) - a[i] + b[j] - (sum(b) - b[j] + a[i])
= sum(a) - sum(b) - 2 (a[i] - b[j])
= A - 2 (a[i] - b[j]) = A - 2x
设x = a[i] - b[j]
要使交换之后的差|A'|<|A|,则必须满足,x与A同号且0<X<A。如果没有满足条件的话则循环结束,程序结束。
#include <iostream>
#include <cctype>
#include <cassert>
using namespace std;
int Sum(int arr[],int len)
{
assert(arr !=NULL && len>0);
int sum = 0;
for(int i=0; i<len; ++i){
sum += arr[i];
}
return sum;
}
void Swap(int &a,int &b)
{
a = a + b;
b = a - b;
a = a - b;
}
void SwapArrNum(int arrA[],int arrB[],int len)
{
assert(arrA!=NULL && arrB!=NULL && len>0);
bool flag;
int sumA = Sum(arrA,len);
int sumB = Sum(arrB,len);
if(sumB == sumA)return;
if(sumB > sumA){
int *temp = arrA;
arrA = arrB;
arrB = temp;
}
while(true){
cout<<"Iterator"<<endl;
flag = false;
for(int i=0; i<len; i++){
for(int j=0; j<len; j++){
int diff = arrA[i] - arrB[j];
int addA = Sum(arrA,len);
int addB = Sum(arrB,len);
if(diff>0 && diff<(addA-addB)){
swap(arrA[i],arrB[j]);
flag = true;
}
}
}
if(!flag)break;
}
}
void PrintArray(int arr[],int len)
{
assert(arr!=NULL && len>0);
for(int i=0; i<len; ++i){
cout<<arr[i]<<" ";
}
cout<<endl;
}
int main()
{
const int MAX = 6;
int arrA[MAX] = {1,9,8,1,2, 3};
int arrB[MAX] ={ 1, 2, 3, 4,5,40};
cout<<Sum(arrA,MAX)<<endl;
cout<<Sum(arrB,MAX)<<endl;
SwapArrNum(arrA,arrB,MAX);
PrintArray(arrA,MAX);
PrintArray(arrB,MAX);
cout<<Sum(arrA,MAX)<<endl;
cout<<Sum(arrB,MAX)<<endl;
system("pause");
return 0;
}
7.给定N对括号,输出其所有的合法的组合状态,例如,N=3,所有的合法状态为:"((()))”, “(()())”, “(())()”, “()(())”, “()()()”
思路:还是深搜DFS的思路,深搜的过程关键在于记录已经用掉的左括号个数和右括号的个数,当用过的左括号个数小于右括号则非法;当二者个数和大于2N则非法;当二者个数相等且数目等于2N则为合法。传说这是卡特兰数。
题目引自:http://www.ahathinking.com/archives/186.html
#include<iostream>
using namespace std;
#define PAIR 50
char str[PAIR * 2 + 1]; // 设括号对数不超过50, str记录括号组合状态
void DFS_bracket(int n, int left_used, int right_used)
{
if(left_used == right_used && left_used + right_used == 2*n)
{
printf("%s\n",str);
return;
}
if(left_used < right_used || left_used + right_used >= 2*n)
{
return ;
}
int index = left_used + right_used;
str[index] = '(';
DFS_bracket(n, left_used + 1, right_used);
str[index] = ')';
DFS_bracket(n, left_used, right_used + 1);
}
void main()
{
int N;
scanf("%d", &N);
DFS_bracket(N,0,0);
}