最近需要找工作了,特將遇到的面試筆試題總結在這裏,後續會陸續添加。。希望對大家也有幫助
1:不能被繼承只能被實例化3次
分析:不能被繼承,只需要將該類的構造函設置爲私有的就可以了,那麼如何得到它呢,只能通過靜態函數得到了(不能創建對象,自然不能用對象調用該函數)。。當然如果用指針,必須得有析構,析構函數當然可以不爲私有(但是爲了對稱,一般寫成私有)。。。(爲了防止賦值和複製,複製構造函數及賦值操作符都要爲私有)。此外代碼中也可以用引用類型。
代碼:
#include<iostream>
using namespace std;
class Singleton {
public:
static int count;
static Singleton* GetInstance() {
if(count>0){
count--;
return new Singleton();
}else{
return NULL;
}
}
static void DeleteInstance(Singleton *PInstance){
if(PInstance!=NULL){
delete PInstance;
PInstance=NULL;
}else{
cout<<"is NULL"<<endl;
}
}
static void Getcount(int n){
count=n;
}
private:
Singleton()
{
cout<<"Singleton is constructed"<<endl;
}
~Singleton()
{
cout<<"Singleton is deconstructed"<<endl;
}
};
int Singleton::count=0;
int main() {
Singleton::Getcount(3);
Singleton *p1=Singleton::GetInstance();
Singleton *p2=Singleton::GetInstance();
Singleton *p3=Singleton::GetInstance();
Singleton *p4=Singleton::GetInstance();
if(p3==NULL)
{
cout<<"p3 NULL"<<endl;
}
else{
cout<<"p3 not NULL"<<endl;
}
if(p4==NULL)
{
cout<<"p4 NULL"<<endl;
}
else
{
cout<<"p4 not NULL"<<endl;
}
Singleton::DeleteInstance(p1);
Singleton::DeleteInstance(p2);
Singleton::DeleteInstance(p3);
Singleton::DeleteInstance(p4);
return 0;
}
2: 以下代碼調用了幾次構照函數和析構函數
#include<iostream>
#include<vector>
using namespace std;
class Test
{
public:
Test()
{
cout<<"Test 被構建!"<<endl;
}
~Test()
{
cout<<"Test 被析構!"<<endl;
}
};
int main(int argc,char *argv[])
{
vector<Test> (*vec)=new vector<Test>();
Test a1,a2;
vec->push_back(a1);
vec->push_back(a2);
delete vec;
return 0;
}
分析:答案是調用了兩次構造函數,卻調用了5次析構函數,
如果將最後delete vec;去掉 則只調用3次析構函數。爲什麼會調用了5次構造函數呢,原因是vector容器的自增長,當將a1 push到容器中時,會複製a1,調用複製構造函數,此時當push a2的時候,容器預分配的容量(capacity)爲1,因此需要重新找一塊更大的內存空間來存放兩個元素,並且將第一個元素複製過來,再撤銷第一個元素,依次會調用依次複製構造函數,並且立刻析構,再調用複製構造函數a2.如下:
#include<iostream>
#include<vector>
using namespace std;
class Test
{
public:
Test()
{
cout<<"Test 被構建!"<<endl;
}
Test(const Test &a){
*this = a;
cout << "複製構照函數被調用!" << endl;
}
~Test()
{
cout<<"Test 被析構!"<<endl;
}
};
int main(int argc,char *argv[])
{
vector<Test> (*vec)=new vector<Test>();
Test a1,a2;
vec->push_back(a1);
vec->push_back(a2);
delete vec;
return 0;
}
第一次複製構照函數被調用,是push a1發生複製,第二次複製構照函數被調用和析構是重新分配內存是複製a1並撤銷原有的a1,第三次複製構照函數被調用是push a2.
vector是在內存中連續存儲,中的函數reserve()用來設置容器預分配容量的大小,resize()是設置容器實際的大小,size()是返回容器實際大小,capacity()是得到容器預分配容器大小。當前實際容器大小爲n,也等於預分配的大小時,當調用push_back()會將容器預分配的容量變成n+n/2,此時會複製n個元素,並加上push新的元素,共有n+1個實際元素。
3:函數調用過程與函數返回值過程
函數返回過程:函數返回值如果是函數中普通的變量---假設爲類類型,那麼普通變量的範圍只能在函數內部,因此此時會產生一個全局的臨時變量(如果是類類型則調用複製構造函數並析構該局部普通變量),,然後會調用賦值操作符將臨時的全局變量賦值我們的變量,並且賦值操作符調用後會析構臨時的全局變量。。
函數調用過程:將函數的下一個地址入棧,並將函數形參從右到左依次入棧,(不會產生臨時全局構造函數),,,函數返回過程會逆序出棧。。
代碼:
#include<iostream>
using namespace std;
template <class T>
class Test
{
public:
Test(T _t){t=_t;}
Test(){}
~Test(){cout<<"_析構函數調用了!"<<endl;}
Test(const Test<T>& _test){
cout << "複製構照函數被調用!" << endl;
t=_test.t;
}
Test& operator=(const Test<T>& _test)
{
cout<<"賦值操作符函數被調用!"<<endl;
t=_test.t;
return *this;
}
// 友元需要重寫模板type
template<class Type> friend Test<Type> operator+(const Test<Type>&,const Test<Type>&);
void print(Test<T> test);
T t;
};
template <class T>
Test<T> operator+(const Test<T>&obj1,const Test<T> &obj2)
{
Test<T> obj;
obj.t=obj1.t+obj2.t;
return obj; // 會產生一個臨時的全局變量temp,會調用複製構造函數將obj賦給temp,然後析構obj,————函數返回過程---產生全局臨時變量
}
template <class T>
void Test<T>::print(Test<T> test){
cout << test.t << endl;
}
int main(int argc,char** argv)
{
Test<int> test_1(2);
Test<int> test_2(3);
Test<int> test_3;
test_3 = test_1+test_2; // 然後將全局變量temp賦值給test_3 賦值操作符結束 臨時變量temp被析構 ——函數返回過程
test_3.print(test_2); // 模擬函數調用過程,將函數的下一個地址入棧,並將display的形參從右到左依次入棧,(直接將test_2複製,不會產生臨時全局構造函數)
char c;
cin>>c;
return 0;
}
結果:
第一次調用複製構照函數是由於operator+中返回obj時會產生一個全局的臨時變量,調用複製構照函數,並將局部變量obj析構掉,然後調用賦值操作符將全局臨時變量賦值給給test_3,並析構掉全局臨時變量( 第二次析構函數調用),然後print實參傳形參調用複製構照函數。。
注意當一個類還沒有定義完全時,不能使用該類類型定義該類的成員,但可以用指針或其引用,,也可以聲明(而不是定義)使用該類型作爲形參類型或者返回類型的函數。如例子中的print函數
此外注意的是友元函數,可以將非成員函數,類及成員函數聲明爲友元,當將非成員函數和類聲明爲友元時,我們無需提前聲明,友元會將該非成員函數及類引入到外圍作用域,,而將成員函數設爲友元,則需要事先定義包含該友元函數的類A,然後定義所需要的類B,最終定義類A中被當做友元的成員函數。
4(阿里面試題):約瑟夫問題
n個人(編號爲0,1,...,n-1)圍成一個圈子,從0號開始依次報數,每數到第m個人,這個人就得自殺,之後從下個人開始繼續報數,直到所有人都死亡爲止。問最後一個死的人的編號(其實看到別人都死了之後最後剩下的人可以選擇不自殺……)。
分析:這是典型的約瑟夫問題,可以用單循環鏈表來進行求解,但此時時間複雜度過高,會達到O(n*m)的時間複雜度——這是我在面試的時候所說的方法。這裏我們給出一種時間複雜度爲O(n),空間複雜度爲O(1)的解法——這裏是我主要想講解的方法。當然也有兩種O(lgn)的解法,但是這兩種解法我還沒有看,參看http://maskray.me/blog/2013-08-27-josephus-problem-two-log-n-solutions
爲了討論方便,先把問題稍微改變一下,並不影響原意:
問題描述:n個人(編號0~(n-1)),從0開始報數,報到(m-1)的退出,剩下的人繼續從0開始報數。求勝利者的編號。
我們知道第一個人(編號一定是(m-1) mod n)出列之後,剩下的n-1個人組成了一個新的約瑟夫環(以編號爲k=m mod n的人開始):
k k+1 k+2 ... n-2,n-1,0,1,2,... k-2
並且從k開始報0。
我們把他們的編號做一下轉換:
k --> 0
k+1 --> 1
k+2 --> 2
...
...
k-2 --> n-2
變換後就完完全全成爲了(n-1)個人報數的子問題,假如我們知道這個子問題的解:例如x是最終的勝利者,那麼根據上面這個表把這個x變回去不剛好就是n個人情況的解嗎?!!變回去的公式很簡單,相信大家都可以推出來:x'=(x+k) mod n
如何知道(n-1)個人報數的問題的解?對,只要知道(n-2)個人的解就行了。(n-2)個人的解呢?當然是先求(n-3)的情況 ---- 這顯然就是一個倒推問題!好了,思路出來了,下面寫遞推公式:
令f表示i個人玩遊戲報m退出最後勝利者的編號,最後的結果自然是f[n]
遞推公式
f[1]=0;
f=(f+m) mod i; (i>1)
有了這個公式,我們要做的就是從1-n順序算出f的數值,最後結果是f[n]。因爲實際生活中編號總是從1開始,我們輸出f[n]+1
由於是逐級遞推,不需要保存每個f,程序也是異常簡單:
#include <iostream>
using namespace std;
const int m = 3;
int main()
{
int n, f = 0;
cin >> n;
for (int i = 1; i <= n; i++) f = (f + m) % i;
cout << f + 1 << endl;
return 0;
}
參考:http://baike.baidu.com/view/213217.htm
5.1(阿里面試題):請在一個小時內實現atoi的c函數
以下是我的代碼:當時處理溢出的時候用了long long,但是MAXINT+1,沒有轉換成long long
#define MAXINT (int)0x7fffffff
int strToInt(const string &str){
if(str == "")return 0;
int i = 0;
bool isMinus = false;
if(str[i]=='+' || str[i] == '-'){
if(str[i] == '-')
isMinus = true;
i++;
}
long long int result = 0;
for(; i < str.size(); i++)
{
if(str[i] < '0' || str[i] > '9') return 0;
result = result * 10 + str[i] - '0';
if(result > (long long)(MAXINT)+1)
return 0;
}
result = isMinus ? -result : result;
return result == (long long)(MAXINT)+1 ? 0 : result;
}
或者更直接的,因爲unsigned int的範圍爲0~0xffffffff = 4294967295,而int的範圍爲-2147483648(0x80000000)~2147483647(0x7fffffff) 所以我們可以直接將result 先設置爲unsigned int, 這樣就不會超過0x7fffffff了。MAX+1也不需要進行轉換了。代碼如下:
#define MAX (unsigned int)0x7fffffff
int strToInt2(const string &str){
if(str == "")return 0;
int i = 0;
bool isMinus = false;
if(str[i]=='+' || str[i] == '-'){
if(str[i] == '-')
isMinus = true;
i++;
}
unsigned int result = 0;
for(; i < str.size(); i++)
{
if(str[i] < '0' || str[i] > '9') return 0;
result = result * 10 + str[i] - '0';
if(result > MAX+1)
return 0;
}
result = isMinus ? -result : result;
return result == MAX+1 ? 0 : result;
}
5.2 實現strcpy函數(注意:strcpy基於dst已經有足夠的空間容納src了,否則會出現運行上的錯誤)
代碼如下:要考慮從後往前還是從前往後進行copy
// strcpy函數中源代碼調用的是memcpy(dst, src, count) 其中count = strlen(src)+1 === strncpy
// 其中strcpy基於dst已有足夠空間容納src的長度,否則運行出錯
//其實現等同於下面的代碼 其中已經確保dst有足夠空間容納src了
char *my_mecpy(const char *src, char *dst){ // 最好用void* 然後再在代碼中強制將void*轉換成char*
if(NULL == dst || NULL == src) return dst;
int len = strlen(src)+1; // 注意strcpy拷貝的空間包括\0 字符所以長度爲strlen(str)+1
char *ret = dst;
if(src + len <= dst || dst <= src){ // 這兩種情況從前往後進行copy
while(len--){
*dst++ = *src++;
}
}
else{ // 反之從後往前進行copy
dst = dst + len - 1;
src = dst + len - 1;
while(len--){
*dst-- = *src--;
}
}
return ret;
}
注意memcpy不考慮複製的重疊部分,memmove才考慮重疊部分,所以上面的是memmove
參考文獻:1:http://blog.csdn.net/xinpo66/article/details/85517882:http://blog.csdn.net/gpengtao/article/details/7464061/
int *myIntArray()
{
int buffer[6] = {0};
for (int i = 1; i <= sizeof(buffer); i++)
{
buffer[i-1] = i;
}
return buffer;
}
int *myInt()
{
int i = 10;
int *buffer = &i;
return buffer;
}
以上兩段函數調用的時候,會返回什麼樣的結果?
cout << myIntArray()[0] << endl;
cout << * myInt() << endl;
其中myIntArray()沒有使用new或者malloc分配內存,所有buffer數組的內存區域在棧區隨着char*myIntArray()的結束,棧區內存釋放,字符數組也就不存在了,所以會產生也指針,輸出結果未知. 而myInt會返回正常結果10(爲什麼??按常理int i的值也被釋放了啊,爲什麼還能返回正確值。 可能是編譯器的原因,,如果是類類型 則也會出現野指針)
class SolutionTest{
public:
SolutionTest():x(10){
cout << "構照函數被調用" << endl;}
SolutionTest(const SolutionTest &st){
this->x = st.x;
cout << "賦值構照函數被調用" << endl;
}
SolutionTest& operator=(const SolutionTest& st){
this->x = st.x;
cout << "賦值操作符被調用" << endl;
return *this;
}
~SolutionTest(){
cout << "析構函數被調用" << endl;
}
int x;
};
SolutionTest* getPtr(){
SolutionTest st;
SolutionTest *pst = &st;
return pst;
}
此時調用SolutionTest *st = getPtr(); cout << st->x << endl; 也會是野指針
7(360研發)在寫一個函數,根據兩文件的絕對路徑算出相對路徑。如 a="/qihoo/app/a/b/c/d/new.c",b="/qihoo/app/1/2/test.c',那麼b相對於a的相對路徑是"../../../../1/2/test.c"
分析:相對路徑就是從a的文件開始,../表示到a文件的上一層,所以相對路徑爲a和b字符串不相同的字符開始a後面/個數就有多少個../作爲b的前綴。而後半部分爲b開始不相同字符所對應的上一個/開始到字符串b的結尾。。
代碼:
#include <iostream>
#include <string>
using namespace std;
void getRelativeString(const string &a, const string &b){
int i = 0, j = 0;
while(i < a.size() && j < b.size()){ // 不相同的字符位置
if(a[i] == b[j]){
i++;
j++;
}else break;
}
// 計算a在不同點後面有多少個/
int remain_a = 0;
while( (i = a.find_first_of('/', i)) != string::npos){ // 找到a後面還有多少'/' 結果即爲a有多少個/ 那麼b的前綴就有多少個../ 即爲pre_str
remain_a ++;
i++;
}
string pre_str = "";
for(int k = 0; k < remain_a; k++){
pre_str += "../";
}
// 計算b的後半部分
//cout << remain_a << endl;
while(j < b.size() && b[j] != '/')j++; // 找到b不相同的地方到上一個/的單詞 結果就爲pre_str 加上從該單詞開始的b後面的字符串。
string str = b.substr(0, j);
str = b.substr(str.find_last_of('/')+1);
// 得到結果
str = pre_str + str;
cout << str << endl;
}
int main(){
string a = "/qihoo/app/a/b/c/d/new.c";
string b = "/qihoo/app/1/2/test.c";
getRelativeString(a, b);
return 0;
}
8:已知rand7()可以產生1~7的7個數(均勻概率),利用rand7() 產 生 rand10() 1~10(均勻概率)。(騰訊筆試題)
分析:剛開始我是用(int)rand7()/7.0*10,但是後來發現不對,這樣生成到1-10不是均勻概率。可以這樣7*(rand7()-1)+rand7()這樣會均勻的生成1-49之間數的均勻概率,這裏減1的目的就是爲了能產生1-7的數字,此時我們讓1-40之間的數值模10且加1就能均勻的生成1-10之間的數字了(1-10,11-20.21-30,31-40),而對於大於40以上的數字它重新生成
代碼:
int rand10()
{
int n=49;
while(n>40){
n=7*(rand7()-1)+rand7();
}
return n%10+1;
}
變形:給定能隨機生成整數 1 到 5 的函數,寫出能隨機生成整數 1 到 7 的函數。同理:如下代碼,這樣21以下的元素對7取模再加1就能生成1-7之間的數字了。
int rand7()
{
int n=25;
while(n>21){
n=5*(rand5()-1)+rand5();
}
return (n%7)+1;
}
9:找最長重複字串
分析:以下代碼時間複雜度爲O(N^3)
#include <iostream>
#include <string>
using namespace std;
int main(){
string s;
cin >> s;
int len = 0;
for(int i = 0; i < s.size(); i++){ // 字串位置
for(int j = s.size()-i; j >= i; j--){ // 字串長度
string str = s.substr(i, j);
int front = s.find(str); // 在s中從前往後找字串
int back = s.rfind(str); // 在s中從後往前找字串
if(front != back && j > len){
len = j;
}
}
}
cout << len << endl;
return 0;
}
10:(美團模擬筆試題)求出一個給定的字符串如str = “ I love you ”;返回“I love you”;去掉首尾空格字符,字符串中間出現多個空格字符時,只保留1個。
代碼:
char *eraseEmptySpace(char *str){
char *result = new char[256]; // 不知道大小是不是要固定。
int i = 0, t = 0;
while(str[i] != '\0'){
if(str[i] != ' ' || str[i+1] != ' '){ // 當前爲空不爲空或者當前不爲空下一個爲空 則加入 // 否則++
result[t++]= str[i];
}
i++;
}
result[t]='\0'; //
if(result[0] == ' ')result = result+1;
return result;
}
int main(){
char *str = " I love you ";
char *result = eraseEmptySpace(str);
cout << result << endl;
return 0;
}
11:N二維座標系中的點對中求最近點對的距離
分析:/*採用分治法,最主要的是計算一個點在S1中,一個點在S2中的最近距離,方法是我們先得到S1和S2內部最小的最近距離dis,然後在
以mid爲中心的dis距離內的點放入新數組T中,然後對T按照Y座標軸排序,我們知道要想滿足條件須要當前點最多與其後面的7個點比較就可以了
*/
代碼:closePair.h
#define N 1000 // 最大節點數
#define maxInt 0x7fffffff
struct Point{
int x;
int y;
};
closePair.cpp
Point V[N], T[N];
inline bool sortByX(const Point &p1, const Point &p2){
return p1.x < p2.x;
}
inline bool sortByY(const Point &p1, const Point &p2){
return p1.y < p2.y;
}
double calc_dist(const Point &p1, const Point &p2){
return sqrt((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y)+0.0);
}
// 二分 遞歸的求解s到t之間的
double merge(int s, int t){
if(s >= t) return maxInt;
double dis = maxInt;
int mid = (s+t)>>1;
dis = min(dis, min(merge(s, mid), merge(mid+1,t))); // 計算兩邊的最近點對距離
//將兩邊距離中心在dis距離以內的點作爲候選並按照Y座標進行排序
int len = 0;
for(int i = s; i <= t; i++){
if(V[i].x >= V[mid].x - dis && V[i].x <= V[mid].x+dis){
T[len++] = V[i];
}
}
sort(T, T+len, sortByY);
for(int i = 0; i < len; i++){ // 當前這個點按y軸排序好的最多8個點(dis*(2*dis)領域內)
for(int j = i+1; j < min(i+7, len) && T[j].y - T[i].y <= dis; j++){
dis = min(dis, calc_dist(T[i], T[j]));
}
}
return dis;
}
// 主函數
double closestPair(){
int n;
cin >> n;
for(int i = 0; i < n; i++)
cin >> V[i].x >> V[i].y;
sort(V, V+n, sortByX); // 按x軸進行排序
return merge(0, n-1); // 歸併
}
參考文獻:1:編程之美
2:http://noalgo.info/793.html
12:隨機洗牌算法
思路:關鍵是從後往前進行遍歷 這樣某張牌被選中放在任何一個位置的概率都是1/n
/*
隨機的洗牌算法:從後往前進行更新某張排被選中放在第i個位置的概率爲1/n
*/
int arr[101];
int n;
void start(){
for(int i = 0; i < n; i++)
arr[i] = i;
for(int i = n-1; i >= 0; i--){ // 這裏採用逆序遍歷
int index = rand()%(i+1);
swap(arr[index], arr[i]);
}
}
參考文獻:
1:http://sumnous.github.io/blog/2014/05/13/random-pick-function/ 隨機數生成函數面試題
2:等概率隨機排列數組 http://www.gocalf.com/blog/shuffle-algo.html
13: 實現stack,並由操作push,pop,top,getMin即取最小值
分析:此題用鏈表,關鍵是在head上進行操作,取最小值最佳情況也能達到O(1)時間內完成
myStack.h
#ifndef MYSTACK_H
#define MYSTACK_H
struct Node{
Node *next;
int value;
Node(int v):value(v), next(NULL){}
};
class myStack{
public:
myStack():head(NULL){}
void push(int v);
void pop();
int top();
int getMin();
private:
Node *head;
};
#endif
myStack.cpp
/*
實現棧,並模擬取出棧中的最小值;; 我們用鏈表來實現,head作爲棧的頭部
*/
void myStack::push(int v){
Node *p = new Node(v);
if(head == NULL) head = p;
else{
p->next = head;
head = p;
}
}
// O(1)時間內刪除
void myStack::pop(){
if(head == NULL) return;
Node *p = head;
head = head->next;
delete p;
}
// 也是O(1)
int myStack::top(){
if(head == NULL) return 0;
return head->value;
}
// 另外一種思路是在myStack中保存一個最小值,在push的時候動態更新它,
// 此時取得最小值就是O(1)了,但是刪除的時候就需要重新遍歷stack更新最小值(O(N)) 但是很多情況下取的最小值就是O(1)了
int myStack::getMin(){
if(head == NULL) return -1;
Node *p = head;
int result = head->value;
while(p != NULL){
result = min(result, p->value);
p = p->next;
}
return result;
}
14: 已知n是個正整數,輸出1/n的小數點後k位數字,如n = 5, k=3,則輸出爲200,; 如n=10, k=3,則輸出010
分析:此題我用的思路直接得到1/n 爲double類型,但是面試官說這個有可能精度不夠,其實這是一個模擬人工算1/n的過程
代碼1:
// 取出1/n 小數點後面k位數字 給定的n是個正整數
void getKMod(int n, int k){
double t = 1/(n+0.0); // 浮點型可能不夠準確
int tmp = 0;
while(k--){
tmp = t*10;
cout << tmp;
t = t*10 - tmp;
}
cout << endl;
}
模擬人工算的工程:
void getKMod2(int n, int k){ // 最爲準確的解法,模仿1/n的過程
int t = 10;
while(k--){
cout << t/n;
t = (t%n)*10;
}
cout << endl;
}
15:fibonacci數列變形,每步只能走一個臺階或者兩個臺階,其中第5,8,13臺階不能走,問走到第20的臺階方法數有多少種
方法一:迭代 f(n)= f(n-1)+f(n-2) 這樣當n = 5, 8, 13的時候f(5)=f(8)=f(13)=0
方法二:分別對1~4; 6~7;9~12;14~20計算fibonacci數列,然後進行相乘即可爲3*1*3*13 = 117
16:有A和B兩個數組,如果A中有100個硬幣,30個向上,現在有兩種操作,一:直接將A中硬幣移到B中,二:移到B中進行翻轉,問什麼樣的操作才能是A與B中的硬幣朝上數目相等
假設從A中選擇x個元素,有y個向上,此時將x移動到B中並翻轉,此時30-y = x-y 導出x=30 因此只需要將30個硬幣全部翻轉移到B數組中即可