1. 程式人生 > >劍指offer面試題1-10

劍指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];
    }
};