劍指offer面試題1-10
#1二維陣列中的查詢
在一個二維陣列中,每一行元素都按照從這裡寫程式碼片
左到右遞增的順序排序,每一列元素都按照從上到下遞增的順序排序。實現一個查詢功能的函式,函式的輸入為二維陣列和一個整數,判斷陣列中是否含有該整數。
//例如查詢元素7
//二位陣列的規律是從左往右依次遞增,從下往上依次遞增
//可以根據待查元素,與起點位置比較,起點位置暫設為右上角(也可以選左下角)
//如果待查元素比右上角值小,則j-1向左移動
//如果待查元素比右上角值大,則i-1 向下移動
//如果待查元素等於該點,則找到了。
class Solution { public: bool Find (int target, vector<vector<int>>array) { int row=array.size(); int col=array[0].size(); int i=0; int j=col-1; while(i<row&&j>=0) { if(array[i][j]<target) j--; else if (array[i][j]>target) i++; else return ture; } return false; } }
#2空格替換
請實現一個函式,將一個字串中的每個空格替換成“%20”。例如,當字串為We Are Happy.則經過替換之後的字串為We%20Are%20Happy。
思想:從後往前遍歷,去移動字元位置,時間複雜度O(N)
//合法性檢驗
//計算空格,算出新陣列的空間
//將old從後往前拷貝到new中,
//如果old遇到空格,new向前填充%20,new然後再往前走一步,接著new向前走
//如果old沒有遇到空格,直接進行拷貝
class Solution { public: void replaceSpace(char str[],int length) { if(str==NULL||length<0) return ; int count=0; //計算空格 int i=0; int oldsize=0; //舊陣列的大小 while(str[i]!='\0') { oldsize++; if(str[i]==' ') count++; i++; } int newsize=oldsize+2*count; //新陣列的大小 if(newsize>length) return ; int newindex=newsize; //新陣列的下標 int oldindex=oldsize; //舊陣列的下標 while(newindex>oldindex&&oldindex>=0) //舊陣列不為0且新陣列下標大於舊陣列,即一直迴圈 { if(str[oldindex]==' ') //如果舊陣列遍歷到空格,新陣列開始填充字元%,然後向前移動繼續遍歷 { str[newindex--]='0'; str[newindex--]='2'; str[newindex--]='%'; } else { //若不是空格則移動字元,然後新陣列向前走一步準備下次拷貝 str[newindex]=str[oldindex]; newindex--; } //舊陣列移動完一次字元後,走向前一個位置準備下一次拷貝 oldindex--; } } };
#3從尾到頭列印連結串列
輸入一個連結串列,按連結串列值從尾到頭的順序返回一個ArrayList。
思想:將連結串列中結點的值儲存到棧(後進先出)中,當棧不為空時對棧頂元素依次出棧操作,出棧元素一次插入vector中
class Solution { public: vector<int> printListFromTailToHead(ListNode* head) { //把連結串列的結點值輸入到棧中 //當棧中元素不為空時,將棧頂元素插入vector中/ vector<int> outnode; stack<int> innode; while(head!=NULL) { innode.push(head->val); head=head->next; } while(!innode.empty()) { outnode.push_back(innode.top()); innode.pop(); } return outnode; } };
#4重建二叉樹
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。
思想:
找到根節點在中序遍歷的下標,然後合法性檢驗
左邊為左子樹,右邊為右子樹
將根結點左邊的中序遍歷值插入到左子樹中序遍歷中
將根結點右邊的先序遍歷值插入到左子樹先序遍歷中(起點從pre[0+1]開始)
同理根結點右邊的中序遍歷插入到右子樹的中序遍歷中(起點從rootindex+1開始)
左子樹先序遍歷完後,右邊的先序遍歷值擦汗如到右子樹的先序遍歷中
然後遞迴插入左右子樹
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
int inlen=vin.size(); //計算樹結點個數
if(inlen==0) //合法性檢驗
return NULL;
int rootindex=0;
vector<int>left_vin,left_pre,right_pre,right_vin;
TreeNode* head=new TreeNode(pre[0]);
for(int i=0;i<inlen;i++)
{
if(vin[i]==pre[0])
{
rootindex=i; //找到根節點在中序遍歷的下標
break;
}
}
for(int i=0;i<rootindex;i++)
//將中序遍歷根結點左邊值(左子樹中序遍歷)插入到左子樹
//先序遍歷根結點右邊值(左子樹先序遍歷)插入到左子樹
{
left_vin.push_back(vin[i]);
left_pre.push_back(pre[i+1]);
}
//將中序遍歷根結點右邊值(右子樹先序遍歷)插入到右子樹
//將先序遍歷左子樹後面的結點(右子樹中序遍歷)插入到右子樹
for(int i=rootindex+1;i<inlen;i++)
{
right_vin.push_back(vin[i]);
right_pre.push_back(pre[i]);
}
//取出先序和中序遍歷根節點左邊和右邊的子樹
//再對其遞迴區分左右子樹,直到葉子結點
head->left=reConstructBinaryTree(left_pre,left_vin);
head->right=reConstructBinaryTree(right_pre,right_vin);
return head;
}
};
#5用兩個棧實現佇列
用兩個棧來實現一個佇列,完成佇列的Push和Pop操作。 佇列中的元素為int型別。
思想:
class Solution{
public:
//兩個棧實現一個佇列
//入佇列: st1入棧
//出佇列: st2出棧(當棧不為空),若st2為空則將st1資料轉移到st2中
//取隊首: 與出佇列思想一樣。
stack<int> st1,st2;
void push(int value)
{
st1.push(value);
}
int pop()
{
if(st2.empty())
{
while(!st1.empty())
{
int a=st1.top();
st2.push(a);
st1.pop();
}
}
int a=st2.top();
st2.pop();
return a;
}
};
#6旋轉陣列的最小數字
把一個數組最開始的若干個元素搬到陣列的末尾,我們稱之為陣列的旋轉。 輸入一個非減排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。 例如陣列{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該陣列的最小值為1。 NOTE:給出的所有元素都大於0,若陣列大小為0,請返回0。
思想:
暴力查詢:從前往後遍歷,找出最小的儲存起來,依次與最小值比較,小於則儲存時間複雜度O(N);
二分查詢:題目條件陣列為有序陣列,則旋轉後的兩部分都是有序的,可以根據二分查詢依次取一半比較。時間複雜度O(logN)
//合法性檢驗
//定義一個左下標和右下標,若左下標值大於等於右下標值進行迴圈
//如果左下標值《=中間變數 說明最小值在第二個陣列中,更新中間變數值
//如果右下標值》=中間變數,說明最小值在第二個陣列中,更新中間變數值
//更新一次中間變數,區間縮減一半。
//一直到左右變數相鄰,退出迴圈。
class Solution {
public:
int minNumberInRotateArray(vector<int> ve) {
//二分查詢
if(ve.size()==0)
return 0;
int left=0;
int right=ve.size()-1;
int mid=-1;
while(ve[left]>=ve[right])
{
if(right-left==1)
{
mid=right;
break;
}
int mid=left+(right-left)/2;
if(ve[left]>=ve[mid])
{
left=mid;
}
if(ve[right]>=ve[mid])
{
right=mid;
}
}
return ve[mid];
}
};
#7斐波那契額數列
大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第n項(從0開始,第0項為0)。
n<=39
思想: 遞迴,中間重複計算較多耗費空間
迴圈(動態規劃)將前面計算的結果儲存在變數中,避免重複 計算
class Solution {
public:
int Fibonacci(int n) {
if(n<=1)
return n;
if(n==2)
return 1;
int pre=1;
int mid=1;
int result=0;
for(int i=3;i<=n;i++)
{
result=pre+mid;
pre=mid;
mid=result;
}
return result;
}
};
#8跳臺階
一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(先後次序不同算不同的結果)。
每次只能跳1級或者跳2級
跳1級剩餘n-1種跳法,跳2級剩餘n-2種跳法
初始狀態 f(1)=1 f(2)=2;
遞推式:f(n)=f(n-1)+f(n-2)
思想:基於斐波那契的擴充套件
class Solution {
public:
int jumpFloor(int number) {
int f[number+1];
f[1]=1;
f[2]=2;
if(number<=1)
return number;
for(int i=3;i<=number;i++)
{
f[i]=f[i-1]+f[i-2];
}
return f[number];
}
};
#9變態跳臺階
一隻青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
每次可以跳 1,2,3…n級
//動態規劃
//狀態f(n): 從0到N階臺階的跳法
//遞推 f(1)=1 f(2)=f(2-1)+f(2-2)
// f(n)=f(n-1)+f(n-2)+…f(n-n);
// f(n-1)=f(n-2)+f(n-3)+…f(n-n+1);
// f(n)=2f(n-1);
//初始值 f(0)=-1; f(1)=1;
//返回f(n);
class Solution {
public:
int jumpFloorII(int number) {
if(number<=0)
return -1;
else if(number==1)
return 1;
else
return 2*jumpFloorII(number-1);
}
};
#10矩形覆蓋
我們可以用21的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個21的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?
思想:動態規劃
放置方法等效於: 斐波那契數列
可以橫著/縱向放置,f[n]=f[n-1]+f[n-2];
初始狀態: f[1]=1 f[2]=2;
class Solution {
public:
int rectCover(int number) {
int f[number+1];
f[1]=1;
f[2]=2;
if(number<=1)
return number;
for(int i=3;i<=number;i++)
{
f[i]=f[i-1]+f[i-2];
}
return f[number];
}
};