算法題摘錄三

轉載請註明原文地址:http://www.cnblogs.com/ygj0930/p/6429971.htmlhtml

1:判斷給定數組是否是二叉搜索樹的前/後序遍歷序列算法

二叉搜索樹的特色是:左子樹的值<根結點值<右子樹的值。而前/後序遍歷序列的首/尾數是根結點,依據根結點能夠把數組其他部分劃分爲左子樹、右子樹,而後根據左右子樹序列又能夠遞歸地肯定父結點並劃分子樹......若是能遞歸遍歷完整個數組則說明合法,不然非法。數組

下面是我實現的判斷後序遍歷序列的代碼:函數

public boolean isSquenceOfBST(int[] nums,int start,int end){
      if(nums==null){
          return false;
      }
      //序列的end就是當前層的根結點
      int curr_root=end;
      //由根結點劃分序列獲得左子樹的結束位
      int leftEnd=0;
      for (int i = start; i <end; i++) {
        if(nums[i+1]>nums[curr_root]){
            leftEnd=i;
            break;
        }
    }
      //由左子樹結束位+1獲得右子樹開始位
      int rightStart=leftEnd+1;
      
      //若是左子樹序列不爲空,則遞歸左子樹
      boolean isLeftOfBST=true;
      if(leftEnd>0){
       isLeftOfBST=isSquenceOfBST(nums, start, leftEnd);}
      
      //同理,先判斷右子樹序列是否存在,是則遞歸右子樹
      //這裏要注意右子樹是rightStart到end-1!
      boolean isRightOfBST=true;
      if(rightStart<end-1){
           isRightOfBST=isSquenceOfBST(nums, rightStart, end-1);
      }     
      return isLeftOfBST&&isRightOfBST;
  }

 

2:給定一棵二叉樹,判斷給的序列是否對應二叉樹的前/後/中序遍歷序列優化

傳統解法:先按前/後/中序遍歷整個樹,把每一個結點的值保存到一個數組中,而後把結果數組與所給數組一一比較便可。這是由樹求數組去判數組。編碼

遞歸解法:好比咱們判斷後序遍歷序列的合法性:咱們從二叉樹的根節點開始遍歷,curr_root記錄當時樹的根節點。由curr_root與當前序列最後一個值比較判斷當前層序列的合法性;而後由curr_root—>left值劃分遍歷序列獲得左子樹序列的最後一位leftEnd,由rightStart=leftEnd+1獲得右子樹序列的開始下標。而後遞歸左右子樹,比較子樹的根結點與所給序列的末尾值比較,最後返回左右子樹的遍歷結果便可。遞歸邊界是:curr_root的子樹爲空。或者當前樹的根不等於當前序列末位值。spa

 

3:打印二叉樹中結點值和爲給定值的全部路徑指針

 從根節點開始處理每個結點,把結點入棧,並統計當前路徑耗散值。若是子節點非空,則遞歸處理子節點。當前結點爲葉結點,則比較路徑耗散值與給定和是否相等,是則打印棧中元素(路徑),不然不打印;而後回溯:彈出當前結點。code

Vector<MyTreeNode> path=new Vector<MyTreeNode>();//因爲打印路徑時須要從頭打印,因此這裏用Vector替代了stack。由於vector能夠從頭遍歷,又包含了棧的拋出後入者的功能
    public void printPath(MyTreeNode currRoot,Vector<MyTreeNode> path,int currSum,int total){
        //判斷第一個根結點是否爲空樹,是則直接返回
        if(currRoot==null){
            return;
        }
        //計算當前結點耗散值
        currSum+=currRoot.val;
        //若是當前結點是葉結點而且耗散值等於所求路徑和,則打印這條路徑
        if(currSum==total && currRoot.leftNode==null && currRoot.rightNode==null){
            for(int i=0;i<path.size();++i){
                System.out.print(path.get(i).val+",");
            }
        }
        //若是路徑不是葉結點,而且左子結點存在,則遞歸左子結點
        if(currRoot.leftNode!=null){
            printPath(currRoot.leftNode, path, currSum, total);
        }
        //若是右子結點存在,遞歸右子結點
        if(currRoot.rightNode!=null){
            printPath(currRoot.rightNode, path, currSum, total);
        }
        //回溯:把路徑上當前點刪除
        path.remove(path.size()-1);
    }
 

4:複雜鏈表的複製htm

複雜鏈表是指:每一個結點不只有指向下一結點的指針,還有一個指向任意結點的指針。

傳統解法:首先遍歷一次原鏈表,用next指針把新鏈表創建起來;而後再從頭至尾遍歷原鏈表和新鏈表,把指向任意結點的指針一一賦值給新鏈表對應結點。複雜度O(n^2)

優化解法:既然一個結點對應一個任意指針,那麼咱們在第一次遍歷原鏈表創建新鏈表時把每一個結點對應的任意指針存到一個map中,而後再遍歷新鏈表爲每一個結點經過map.get(當前結點)獲取對應的任意指針進行賦值。

5:把一顆二叉搜索樹轉換成排好序的雙向鏈表,要求只能改變樹的指針,不能新建任何結點

由二叉搜索樹的中序遍歷是遞增序列可知,要把BST轉換成排好序的雙向鏈表其實就是中序遍歷BST並修改指針的過程。

觀察可得:若是一個結點n的左右子樹非空,那麼n的前指針指向左子樹排序後序列的尾結點,n的後指針執行右子樹排列後序列的頭結點。而對於n的左右子樹序列的得出,毫無疑問就是遞歸。

6:求一個字符串全部字符的全排列

首先,求出第一個字符的全部可能狀況:其實就是把字符串的第一個字符依次和後面的字符交換;

而後,在第一個字符肯定的狀況下,把第二個字符依次和後面的字符交換;其實就是遞歸:把從第二個字符起的字符串傳進遞歸函數進行處理;

......直到遞歸到了每種狀況下的字符串的最後一個字符,結束遞歸,輸出此時的字符串。回溯:把當前步的交換復位。

public void Permutation(char[] chars,int begin){
        if(chars==null){
            return;
        }
        //若是以及遞歸到最後一個字符了,則獲得一個排列狀況,進行輸出
        if(begin==chars.length-1){
            System.out.println(chars);
        }else{//不然,與後面的字符逐個交換,並在交換後修改begin位置進行遞歸
            for (int i = begin; i < chars.length; i++) {
                char temp=chars[i];
                chars[i]=chars[begin];
                chars[begin]=temp;
                Permutation(chars, begin+1);
                //回溯
                temp=chars[i];
                chars[i]=chars[begin];
                chars[begin]=temp;
            }
        }        
    }

7:找出數組中出現次數超過數組長度一半的元素

map解法:這種映射關係的題,第一時間想到map——遍歷整個數組,把數字與其出現次數關聯起來。而後把map中value值最大的key輸出便可。

優化解法:咱們能夠設定一個活動開關:每當當前數與保存數相同,則開關值+1;不然開關值-1;檢查開關值,若是開關值爲0,則把當前數設置爲保存數,並把開關值置1,統計新保存數的出現狀況。因爲出現次數超過數組一半,那麼遍歷完後開關值大於等於1時對應的保存數就是數組中出現次數大於一半的元素。

8:輸入一個數組,找出最小的K個數

傳統解法:排序,輸出前K個數。複雜度O(nlogn)

優化解法:最大堆的使用。首先用輸入的數組的前K個數創建其一個有K個結點的最大堆。而後每輸入一個數,先與堆頭比較,若小於堆頭,則把堆頭的值替換成新輸入的值,而後對堆頭進行下沉操做使結點處於合適位置從而更新堆頭最大值。在數組輸入完後,獲得的K結點最大堆就是數組最小的K個數。

9:求連續子數組最大和

這種問題的解與先後步狀況相關的問題,用動態規劃來作:f(i)=Max(nums[i],f(i-1)+nums[i]),f(i)表示以第i個數字結尾的子數組最大和。

雖然動態規劃經常使用遞歸的形式去描述,可是最終咱們都會用循環來編碼。

public int Max_Son(int[] nums,int n){
        //用一個結果數組存放以i結尾的子數組的最大連續和
        int[] res=new int[n];
        //初始化
        res[0]=nums[0];
        //用currMax保存最大連續和數組中的最大值
        int currMax=res[0];
        //用循環求以i結尾的最大連續和
        for(int i=1;i<n;++i){
            if(res[i-1]<=0){
                res[i]=nums[i];
            }else{
                res[i]=res[i-1]+nums[i];
            }
            //更新最大連續和數組中的最大值
            currMax=currMax>res[i]?currMax:res[i];
        }
        //返回最大的連續和
        return currMax;
    }

10:從1數到n,求數字1出現的次數(在任意位上)

傳統解法:定義一個函數專門用於統計一個數n的各位上1的個數。而後循環遍歷 i:1~n ,count+=SumOfOne(i),最後count就是所求。缺點是容易超時。

優化:找規律來求。

相關文章
相關標籤/搜索