劍指offer-附加

題目-求1+2+3+...+n java

求1+2+3+...+n,要求不能使用乘除法、forwhileifelseswitch、case等關鍵字及條件判斷語句(A?B:C)。 

思路node

因爲須要用到的就是一個遞歸,當n>0時,將後面的數字進行一個遞歸的相加。當輸入0時,即n==0,輸出0,直接不進行遞歸。git

這個題的關鍵要求在於不能使用xxxxxx一系列語句。正則表達式

那麼看起來惟一能用到的只有位運算、加減。算法

用到邏輯與的短路特徵:即如($b=false)&&($a=true),當&&前面的語句不成立(false)時,&&後面的語句不會執行;以此來實現遞歸終止。數組

(ps)邏輯或的短路特徵:如($b=true) || ($a=false),當||前面的語句成立(true)時,||後面的語句不會執行。安全

即當n>0時,執行sum+=Sum_Solution(n-1)進行遞歸的累加;當n==0時,執行&&前面語句的判斷(n>0),爲false,而後不對sum作什麼修改處理,直接就是返回第一句的sum=n=0。網絡

解法多線程

public class Solution {
    public int Sum_Solution(int n) {
          int sum = n;
          boolean ans = (n>0)&&((sum+=Sum_Solution(n-1))>0);
          return sum;     
    }
}

 

題目-不用加減乘除作加法 app

寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、*、/四則運算符號。 

思路

那麼這道題的要求也是使用位運算,咱們來回顧一下經常使用的位運算。

1.移位運算:

  左移<<:向左移動,右邊低位補0.左移1位至關於乘以2

  e.g. 3<<2:00000011 ---> 00001100(12)

  無符號右移>>>:向右移動,右邊捨棄,左邊補0

  有符號右移>>:向右移動,右邊捨棄,左邊補的值取決於原來的最高位。原來是1就補1,原來是0就補0.右移1位至關於除以2.

  e.g. 3>>1:00000011 --->  00000001(1)

2.邏輯運算:

  與 & :兩個位都爲1,才爲1

  或 | :只有一位爲1,就爲1

  取反 ~ :1變成0,0變成1

  按位異或  ^ :相異爲真,相同爲假

3.應用場景:

  1)判斷奇偶:(1&i)==1,此時i的最低位爲1,偶數;(1&j)==1,此時j的最低位爲1,奇數。

解法

public class Solution {
    public int Add(int num1,int num2) {
        int sum, carry;
        do{
            //各位相加,不計進位。二進制每位相加至關於各位作異或操做
            sum = num1^num2;
            //記下進位。兩個數各位作與運算,再左移一位。
            carry = (num1 & num2)<<1;
            num1 = sum;
            num2 = carry;
        }while(num2!=0);//直到進位值爲0
        return num1;
    }
}

 

題目-把字符串轉換成整數

將一個字符串轉換成一個整數,要求不能使用字符串轉換整數的庫函數。 數值爲0或者字符串不是一個合法的數值則返回0 

思路

1.這個題須要注意到的是數據的邊界,可能會溢出。int範圍爲[-2147483648,2147483647],能夠用Integer.MAX_VALUE和Integer.MIN_VALUE來表示。

2.邊界條件:數據溢出;空字符串;正負號狀況;非法字符

3.庫函數:int atoi(const char *str );//將字符串轉換爲整型

atol();//將字符串轉換爲長整型值。

char *itoa( int value, char *string,int radix);//整數轉換爲字符串。value:欲轉換的數據;string:目標字符串的地址;radix:轉換後的進制數,能夠是10進制、16進制等。

解法

public class Solution {
    public int StrToInt(String str) {
        if(str==null || str.length()==0)
            return 0;
        //判斷是否爲負數
        boolean isMinus = str.charAt(0)=='-';
        //設置爲long類型,避免溢出
        long ret = 0;
        for(int i=0; i<str.length(); i++){
            char c = str.charAt(i);
            //符號斷定
            if(i==0 && (c=='+' || c=='-'))
                continue;
            //若是不是數字,非法輸入
            if(c<'0' || c>'9')
                return 0;
            //將字符拼接成數字
            ret = ret*10+(c-'0');
        }
        ret=isMinus ? -ret : ret;
        //溢出就返回0,用long類型的res來比較,
        //若是定義爲int res,那再比較就沒有意義了,int範圍爲[-2147483648,2147483647]
        if(ret>Integer.MAX_VALUE|| ret<Integer.MIN_VALUE)
            return 0;
        return (int)ret;
    }
}

 

題目-數組中重複的數字

在一個長度爲n的數組裏的全部數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每一個數字重複幾回。請找出數組中任意一個重複的數字。 例如,若是輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2。 

思路

要求時間複雜度爲O(N),空間複雜度爲O(1)。所以不能使用排序的方法,也不能使用額外的標記數組。

那麼對於這種數組元素在固定範圍的問題。考慮用將值爲i的元素調整到第i個位置來進行求解。

這樣的話,若是當將第i個元素調整到第i位時,咱們發現某個位置i已經有這個元素i,那麼就說明重複。

而且只是要求返回任意重複的一個,賦值duplication[0]

解法

public class Solution {
    // Parameters:
    //    numbers:     an array of integers
    //    length:      the length of array numbers
    //    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
    //                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
    //    這裏要特別注意~返回任意重複的一個,賦值duplication[0]
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers==null || length==0)
            return false;
        //將值爲1的元素調整到第i個位置上求解
        for(int i=0; i<length; i++){
            while(numbers[i]!=i){
                //好比在第二個位置,已經有一個2的值了,那麼就記錄重複
                if(numbers[i]==numbers[numbers[i]]){
                    duplication[0]=numbers[i];
                    return true;
                }
                swap(numbers, i, numbers[i]);
            }
        }
        return false;
    }
    private void swap(int numbers[], int i, int j){
        int temp = numbers[i];
        numbers[i] = numbers[j];
        numbers[j] = temp;
    }
}

 

題目-構建乘積數組

給定一個數組A[0,1,...,n-1],請構建一個數組B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。(注意:規定B[0] = A[1] * A[2] * ... * A[n-1],B[n-1] = A[0] * A[1] * ... * A[n-2];) 

思路

要求不能使用除法,最簡單的思路固然就是連乘,可是時間複雜度爲O(N^2),效率低。

那咱們將每一個B[i] 的計算式列出來,考慮B[i] 之間的聯繫。

列出上表,能夠發現B[i] 其實不一樣之處就是在於第i位是爲1,那麼能夠將B[i]的計算以哪一位爲1爲分割線。

B[i]的左半部分與B[i-1]有關,即B[i]的左半部分=B[i-1]的左半部分 * A[i-1]

B[i]的右半部分與B[i+1]有關,即B[i]的右半部分=B[i+1]的右半部分 * A[i+1]

而後分別計算左右半部分,再乘起來。

解法

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
        int n = A.length;
        int[] B = new int[n];
        B[0] = 1;
        //B[i]的左半部分和B[i-1]有關
        for(int i=1; i<n; i++){
            B[i] = B[i-1]*A[i-1];
        }
        //temp表示右半部分的乘積
        int temp=1;
        //B[i]的右半部分和B[i+1]有關
        for(int i=n-2; i>=0; i--){
            temp *= A[i+1];
            B[i] *= temp;
        }
        return B;
    }
}

 

題目-正則表達式匹配

請實現一個函數用來匹配包括'.''*'的正則表達式。模式中的字符'.'表示任意一個字符,而'*'表示它前面的字符能夠出現任意次(包含0次)。 在本題中,匹配是指字符串的全部字符匹配整個模式。例如,字符串"aaa"與模式"a.a""ab*ac*a"匹配,可是與"aa.a""ab*a"均不匹配 

思路

比較值得注意的就是*的處理。由於它可能表示前面的字符出現任意次(包含0次)

出現的狀況可能有:

  1.同時匹配到末尾,就匹配成功;

  2.模式串到了末尾,可是字符串尚未到末尾。那說明字符串後面的那一部分沒辦法被匹配,匹配失敗;

  3.模式串中的下一個字符是*:

    1)若是當前字符不匹配,將模式串後移兩位,繼續匹配。

     如模式串爲a*b,字符串爲bbb.

     後移兩位是由於,若是當前不匹配,那麼只能是說明*表示的是當前字符出現0次,至關於a*被忽略。那麼模式串就應該跳過這兩個字符,從*後面的b開始匹配字符串。

    2)若是當前字符匹配 或者 模式串的當前字符爲 ‘.’(因爲模式中的字符'.'表示任意一個字符),那麼根據*的匹配方式,那麼可能出現:

     如模式串爲a*b,字符串爲aaa.

     若*表示前面字符出現0次,那麼模式串後移兩位,字符串不動;

     若*表示前面字符出現1次,那麼模式串後移兩位,字符串後移一位。至關於a*當作一個總體,與字符串的第一個a,來匹配。匹配完後,就都日後移,匹配下一位。

     若*表示前面字符出現屢次,那麼模式串不動,字符串後移一位。至關於*也就是a,與字符串的第二個a去匹配。由於*不能表示直接來匹配,只能依據它前面依附的字符來匹配。

     將這三種可能的匹配方式,來進行或處理。由於*確定是出如今這三種狀況裏。     

  4.模式串中下一個字符不是*:

    1)當前字符匹配 或者 模式串的當前字符爲 ‘.’ ,那麼後移匹配下一字符;

    2)當前字符不匹配,則匹配失敗。

同時還須要在java裏,還要時刻注意檢查數組是否越界。(好比須要去找,字符串和模式串是否匹配到末尾。由於在匹配過程當中會有須要字串後移的操做)    

解法

public class Solution {
    public boolean match(char[] str, char[] pattern)
    {
        //空串則返回false
        if(str==null || pattern==null){
            return false;
        }
        //遞歸地匹配
        return matchCore(str, 0, pattern, 0);
    }
    //設置兩個指針,分別指向字符串和模式串的第一個字符
    private boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex){
        int m=str.length, n=pattern.length;
        //若是同時匹配到末尾,那麼則匹配成功
        if(strIndex==m && patternIndex==n){
            return true;
        }
        //若是模式串先掃描到末尾,則匹配失敗
        if(strIndex!=m && patternIndex==n){
            return false;
        }
        //若是下一個字符是"*"
        if(patternIndex+1<n && pattern[patternIndex+1]=='*'){
            //字符串和模式串當前字符匹配
            if(strIndex!=m && (str[strIndex]==pattern[patternIndex] || pattern[patternIndex] == '.')){
                return matchCore(str, strIndex, pattern, patternIndex+2)|| //‘*’前的字符出現0次,那麼pattern就要多走*和*前面的字符
                    matchCore(str, strIndex+1, pattern, patternIndex+1)|| //‘*’前的字符只出現1次,那麼pattern和str都進入下一步的匹配
                    matchCore(str, strIndex+1, pattern, patternIndex);  //‘*’前的字符出現屢次
            }
            else{
                //若是當前字符串不匹配,則認爲‘*’前的字符出現0次
                //由於只有模式串當前字符不出現時,纔可能字符串當前字符和模式串下一字符匹配
                return matchCore(str, strIndex, pattern, patternIndex+2);
            }
        }
        //若是下一個字符不是'*'
        //當前字符匹配
        if(strIndex!=m && (str[strIndex]==pattern[patternIndex] || pattern[patternIndex]=='.')){
            return matchCore(str, strIndex+1, pattern, patternIndex+1);
        }
        else{//當前字符不匹配
            return false;
        }
    }
}

 

題目-表示數值的字符串

請實現一個函數用來判斷字符串是否表示數值(包括整數和小數)。例如,字符串"+100","5e2","-123","3.1416""-1E-16"都表示數值。 可是"12e","1a3.14","1.2.3","+-5""12e+4.3"都不是。 

思路

這裏是使用正則匹配。給出一些經常使用的正則表示:

[xyz]:字符集,匹配括號中包含的任一字符。

[^xyz]:反向字符集,匹配括號中未包含的任一字符。

?:零次或一次匹配前面的字符或子表達式。

+:一次或屢次匹配前面的字符或子表達式。

*:零次或屢次匹配前面的字符或子表達式。

^:匹配輸入字符串開始的位置。

$:匹配輸入字符串結尾的位置。

\:將下一字符標記爲特殊字符、文本、反向引用或八進制轉義符。

\d:數字字符匹配,[0-9]。(可是java中\是轉移字符前導符,所以在字符串中書寫\必須寫成\\

\D:非數字匹配,[^0-9]。

\w:匹配任何字類字符,[A-Za-z0-9]。

經常使用的函數:

public boolean matches();//嘗試將整個區域與模式匹配;

public boolean find();//從當前位置開始匹配;

public boolean lookingAt();//嘗試將從區域開頭開始的輸入序列 與 該模式匹配。從第一個字符開始匹配,可是不要求整句都匹配。

解法

public class Solution {
    public boolean isNumeric(char[] str) {
        if(str==null || str.length==0)
            return false;
        return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
    }
}

 

題目-字符流中第一個不重複的字符

請實現一個函數用來找出字符流中第一個只出現一次的字符。例如,當從字符流中只讀出前兩個字符"go"時,第一個只出現一次的字符是"g"。當從該字符流中讀出前六個字符「google"時,第一個只出現一次的字符是"l"

思路

時間複雜度O(1),空間複雜度O(n)

1.構建一個大小爲128的數組,統計每一個字符ch出現的次數;

2.構建一個隊列,記錄全部第一次出現的ch字符。若是當前字符出現2次,那麼彈出;

3.此時隊列中全都是隻出現一次的字符。若是是空,那麼就輸出「#」;若是不是空,那麼彈出隊首。

解法

import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    private int[] cnts = new int[256];
    private Queue<Character> queue = new LinkedList<>();
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        //記錄各元素的出現次數
        cnts[ch]++;
        //在隊列中記錄全部出現過的元素
        queue.add(ch);
        //若是這個元素的次數大於2,那麼彈出
        while(!queue.isEmpty() && cnts[queue.peek()]>1){
            //返回第一個元素,並在隊列中刪除
            queue.poll();
        }
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        //若當前字符流沒有存在出現一次的字符,返回#
        return queue.isEmpty() ? '#' : queue.peek();
    }
}

 

題目-鏈表中環的入口結點

給一個鏈表,若其中包含環,請找出該鏈表的環的入口結點,不然,輸出null。 

思路 

設置快慢指針,從表頭同時出發。快指針每次走兩步,慢指針每次走一步。

1.若是有環,快慢指針必定會相遇在環中某點。(因爲有環,fast先進入環,那麼low進入環,能夠將二者當作是一個追趕的過程)

2.兩個指針分別從鏈表頭和相遇點繼續出發,每次走一步,最終必定會相遇在環入口。

設置AC=a,CB=b,BC=c。

相遇時,快指針的路程=a+(b+c)*k+b;慢指針的路程=a+b.

而快指針的速度=2*慢指針的速度。所以2*(a+b)=a+(b+c)*k+b,化簡獲得a=(k-1)(b+c)+c,即 鏈表頭到環入口的距離=相遇點到環入口的距離+(k-1)圈環長度。

得證。

解法

/*
 public class ListNode {
    int val;
    ListNode next = null;
 
    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
 
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead==null || pHead.next==null){
            return null;
        }
        ListNode slow=pHead, fast=pHead;
        //由於存在環,兩個指針一定相遇在環中的某個節點
        do{
            fast=fast.next.next;
            slow=slow.next;
        }while(slow!=fast);
        //讓fast從頭開始移動,速度變爲一次移動一個節點
        fast=pHead;
        while(slow!=fast){
            slow=slow.next;
            fast=fast.next;
        }
        return slow;
    }
}

 

題目-刪除鏈表中重複的結點

在一個排序的鏈表中,存在重複的結點,請刪除該鏈表中重複的結點,重複的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理後爲 1->2->5 

思路

使用遞歸

1)若是當前結點是重複結點,那麼刪除這個重複結點,從第一個與當前結點不一樣的結點開始遞歸。

2)若是當前結點不重複,那麼直接從下一結點開始遞歸。

解法

/*
 public class ListNode {
    int val;
    ListNode next = null;
 
    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        //若是鏈表爲空
        if(pHead==null || pHead.next==null)
            return pHead;
        ListNode next = pHead.next;
        //若是出現重複結點,刪除重複結點且不保留
        if(next.val == pHead.val){
            while(next!=null && next.val==pHead.val){
                next = next.next;
            }
            return deleteDuplication(next);
        }
        //若是沒有重複結點,那麼就判斷下一個結點
        else{
            pHead.next = deleteDuplication(pHead.next);
            return pHead;
        }
    }
}

 

題目-二叉樹的下一個結點

給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點而且返回。注意,樹中的結點不只包含左右子結點,同時包含指向父結點的指針。

思路

根據中序遍歷的規則:左根右。獲得遍歷的規律

1.若是有右子樹,那麼下一個結點就是右子樹最左結點;

2.若是沒有右子樹,那麼下一結點是:a)父結點,當前結點爲父結點的左孩子時;b)向上找到第一個左子樹指向的樹,好比J,其下一節點是父結點的父結點的父結點,直到當前結點是這個父結點的左子樹部分。由於左根右,當前結點是右結點的時候,說明已經遍歷到這棵樹的最後一個結點,應該開始遍歷另外半棵樹。

解法

/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null; //next爲指向父結點的指針
 
    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode)
    {
        //中序遍歷:左根右
        //若是一個結點的右子樹不爲空,那麼該結點的下一個結點是右子樹的最左結點
        if(pNode.right != null){
            TreeLinkNode node = pNode.right;
            while(node.left != null){
                node = node.left;
            }
            return node;
        }
        //不然,向上找第一個左連接指向的樹 包含 該結點的祖先結點
        else{
            while(pNode.next != null){
                TreeLinkNode parent = pNode.next;
                if(parent.left == pNode){
                    return parent;
                }
                pNode = pNode.next;
            }
        }
        return null;
    }
}

 

題目-對稱的二叉樹

請實現一個函數,用來判斷一顆二叉樹是否是對稱的。注意,若是一個二叉樹同此二叉樹的鏡像是一樣的,定義其爲對稱的。 

思路

使用遞歸,只是須要不斷判斷結點的left和right是否對稱;

即左右結點的值要相等&&對稱子樹left.left與right.right、left.right與right.left也要對稱。

解法

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
 
    }
 
}
*/
public class Solution {
    boolean isSymmetrical(TreeNode pRoot)
    {
        if(pRoot == null){
            return true;
        }
        return isSymmetrical(pRoot.left, pRoot.right);
    }
     
    boolean isSymmetrical(TreeNode t1, TreeNode t2){
        if(t1 == null && t2 == null)
            return true;
        if(t1 == null || t2 == null)
            return false;
        if(t1.val != t2.val)
            return false;
        return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left);
    }
}

 

題目-按之字形順序打印二叉樹

請實現一個函數按照之字形打印二叉樹,即第一行按照從左到右的順序打印,第二層按照從右至左的順序打印,第三行按照從左到右的順序打印,其餘行以此類推。

思路

1.將每層數據存入ArrayList,當偶數層時經過Collections.reverse(list);翻轉list。可是其實咱們給出來的這種方法使用reverse,處理海量數據時,效率過低。

2.能夠利用LinkedList實現的是雙向鏈表的特色,作成雙向遍歷。

  使用queue.iterator();//從前日後遍歷

  queue.descendingIterator();//從後往前遍歷

  而且須要在queue中添加null作層分隔符

解法

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Collections;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
 
    }
 
}
*/
public class Solution {
    public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(pRoot);
        //用變量記錄奇數層仍是偶數層,是否反向
        boolean reverse = false;
        while(!queue.isEmpty()){
            ArrayList<Integer> list = new ArrayList<>();
            int cnt = queue.size();
            //若是隊列不爲空,就循環取值
            while(cnt-- > 0){
                TreeNode node = queue.poll();
                if(node == null)
                    continue;
                list.add(node.val);
                queue.add(node.left);
                queue.add(node.right);
            }
            //若是是從右到左,就反轉list
            if(reverse == true)
                Collections.reverse(list);
            //反向
            reverse = !reverse;
            if(list.size() != 0){
                ret.add(list);
            }
        }
        return ret;
    }
 
}

 

題目-把二叉樹打印成多行

從上到下按層打印二叉樹,同一層結點從左至右輸出。每一層輸出一行。 

思路

這個題與上一題「按照之字形打印二叉樹」相似。

只是去掉了須要翻轉的部分。

構建一個隊列來存儲二叉樹,而後將一個結點從隊列中取出並存入list的同時,將結點的左右結點添加到隊列。不斷作輸出。

解法

import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
    }
 
}
*/
public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
        //構造隊列來存儲二叉樹
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(pRoot);
        while(!queue.isEmpty()){
            ArrayList<Integer> list = new ArrayList<>();
            int cnt = queue.size();
            while(cnt-- > 0){
                TreeNode node = queue.poll();
                if(node == null)
                    continue;
                //將當前結點存儲到list中,待輸出
                list.add(node.val);
                //加入當前結點的左右子樹
                queue.add(node.left);
                queue.add(node.right);
            }
            if(list.size() != 0)
                ret.add(list);
        }
        return ret;
    }
     
}

 

題目-序列化二叉樹

請實現兩個函數,分別用來序列化和反序列化二叉樹 

二叉樹的序列化是指:把一棵二叉樹按照某種遍歷方式的結果以某種格式保存爲字符串,從而使得內存中創建起來的二叉樹能夠持久保存。序列化能夠基於先序、中序、後序、層序的二叉樹遍歷方式來進行修改,序列化的結果是一個字符串,序列化時經過 某種符號表示空節點(#),以 ! 表示一個結點值的結束(value!)。

二叉樹的反序列化是指:根據某種遍歷順序獲得的序列化字符串結果str,重構二叉樹。

思路 

1.序列化:就是使用一些遍歷規則來將二叉樹遍歷爲字符串,好比前序遍歷「根左右」。就按照這個順序讀結點的值、遞歸左結點、遞歸右結點。

2.反序列化:按照遍歷順序,重構二叉樹。

  根據字符串序列,因爲序列化時,不一樣結點之間有分隔符,經過分隔符,獲取每一個結點。

  將該結點的字符截取出來,而後截斷原字符。根據結點的值構建二叉樹、遞歸重構左結點、遞歸重構右結點。

3.public String substring(int beginIndex);//返回一個新字符串,它是此字符串的一個子字符串。該子字符串始於指定索引處的字符,一直到此字符串末尾。

public String substring(int beginIndex, int endIndex);//返回一個新字符串,它是此字符串的一個子字符串。該子字符串從指定的 beginIndex 處開始, endIndex:到指定的 endIndex-1處結束。

解法 

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
 
    }
 
}
*/
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if(root==null){
            return "null,";
        }
        String res=root.val+",";
        res+=serialize(root.left);
        res+=serialize(root.right);
        return res;
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String str[]=data.split(",");
        Queue<String> queue=new LinkedList<String>();
        for(int i=0;i<str.length;i++){
            queue.offer(str[i]);
        }
        return helper(queue); 
    }

    public TreeNode helper(Queue<String> queue){
        String val=queue.poll();
        if(val.equals("null")){
            return null;
        }
        TreeNode root=new TreeNode(Integer.valueOf(val));
        root.left=helper(queue);
        root.right=helper(queue);
        return root;
    }
}
public class Solution {
    String Serialize(TreeNode root) {
        //序列化時經過 某種符號表示空節點(#)
        if(root == null)
            return "#";
        return root.val + " " + Serialize(root.left) + " " + Serialize(root.right);
    }
    private String deserializeStr;
TreeNode Deserialize(String str) { deserializeStr
= str; return Deserialize(); } TreeNode Deserialize(){ if(deserializeStr.length() == 0) return null; //獲取一個結點 int index = deserializeStr.indexOf(" "); //該子字符串從指定的 beginIndex 處開始, endIndex:到指定的 endIndex-1處結束。 String node = index==-1 ? deserializeStr : deserializeStr.substring(0, index); //將字符串截掉已經存入node的那一部分。substring該子字符串始於指定索引處的字符,一直到此字符串末尾。 deserializeStr = index==-1 ? deserializeStr : deserializeStr.substring(index+1); //"#"爲空結點 if(node.equals("#")){ return null; } int val = Integer.valueOf(node); TreeNode t = new TreeNode(val); t.left = Deserialize(); t.right = Deserialize(); return t; } }

 

題目-二叉搜索樹的第k個結點

給定一棵二叉搜索樹,請找出其中的第k小的結點。例如,(5372468)中,按結點數值大小順序第三小結點的值爲4。

思路 

二叉搜索樹(二叉查找樹、二叉排序樹):根節點的值大於其左子樹中任意一個節點的值,小於其右節點中任意一節點的值,這一規則適用於二叉查找樹中的每個節點。

根據二叉搜索樹的特色,咱們能夠發現其中序遍歷(左根右)是有序的。即按照中序遍歷的順序,找到的第k個結點就是結果。

解法

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
 
    public TreeNode(int val) {
        this.val = val;
 
    }
 
}
*/
public class Solution {
    //二叉搜索樹的特性:根結點的值大於其左子樹中任意一個結點的值,小於其右子樹中任意一結點的值。
    //所以中序遍歷(左根右)是有序的
    private TreeNode ret;
    private int cnt = 0;
    TreeNode KthNode(TreeNode pRoot, int k)
    {
        inOrder(pRoot, k);
        return ret;
    }
    private void inOrder(TreeNode pRoot, int k){
        if(pRoot == null || cnt >= k){
            return;
        }
//左 inOrder(pRoot.left, k);
//根,標記第k個結點 cnt++; if(cnt == k){ ret = pRoot; }
   //右 inOrder(pRoot.right, k); } }

 

題目-數據流中的中位數

如何獲得一個數據流中的中位數?若是從數據流中讀出奇數個數值,那麼中位數就是全部數值排序以後位於中間的數值。若是從數據流中讀出偶數個數值,那麼中位數就是全部數值排序以後中間兩個數的平均值。咱們使用Insert()方法讀取數據流,使用GetMedian()方法獲取當前讀取數據的中位數。

傳入的數據爲:[5,2,3,4,1,6,7,0,8],那麼按照要求,輸出是"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "。

思路

1.也就是不斷根據獲取的數據流,得到當前數據的中位數。注意中位數是須要排序的。

2.那麼咱們能夠經過優先隊列PriorityQueue來設置大頂堆和小頂堆。

其中大頂堆用來存較小的數,從大到小排序;小頂堆用來村較大的數,從小到大排序。

這樣的話中位數也就是大頂堆的根結點(較小數中的最大) 和 小頂堆的根結點(較大數中的最小)的平均數。也就是中位數依據的 全部數值排序後中間的兩個數

3.構造兩個堆的時候,須要保證小頂堆的元素都大於等於大頂堆的元素&&須要保證兩個堆的數據處於平衡狀態(元素個數相差不超過1)

每一個堆加一個的順序。

當數目爲偶數時,將這個值插入大頂堆中,再從大頂堆中poll出根結點(最大值),插入到小頂堆;

當數目爲奇數時,將這個值插入小頂堆中,再從小頂堆中poll出根結點(最小值),插入到大頂堆;

所以取中位數時,若當前個數爲偶數,那麼就是取大頂堆和小頂堆的根結點的平均值;若當前個數爲奇數時,就是取小頂堆的根結點。

4.隊列遵循先進先出原則,可是有時須要在隊列中基於優先級處理對象。PriorityQueue是基於優先堆的一個無界隊列,這個優先隊列中的元素能夠默認天然排序(從小到大)或者經過提供的Comparator(比較器)在隊列實例化的時排序。優先隊列不容許空值,且隊列的大小不受限制,在建立時能夠指定初始大小(以下面定義maxHeap時給出的11)。當咱們向優先隊列增長元素的時候,隊列大小會自動增長。

PriorityQueue是非線程安全的,因此Java提供了PriorityBlockingQueue(實現BlockingQueue接口)用於Java多線程環境。

解法

import java.util.Comparator;
import java.util.PriorityQueue;
public class Solution {
    //當前數據流讀入的元素個數
    public int count = 0;
    //用兩個堆來保存數據
    //小頂堆,存儲右半邊元素,右半邊元素都大於左半邊
    public PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    //大頂堆,存儲左半邊元素。
    public PriorityQueue<Integer> maxHeap = new PriorityQueue<>(11,
            new Comparator<Integer>(){
                public int compare(Integer o1, Integer o2){
                    return o2 - o1;
                }
            });
    public void Insert(Integer num) {
        count++;
        //插入過程,須要保證兩個堆的數據處於平衡狀態(元素個數相差不超過1)
        if(count % 2 ==0){//偶數
            //由於右半邊元素都要大於左半邊,可是新插入的元素不必定比左半邊元素大
            //所以須要先將元素插入左半邊,而後利用左半邊爲大頂堆的特色(堆頂元素即爲最大值),插入右半邊)
            //即偶數狀況 都插入到右半邊
            if(!maxHeap.isEmpty() && num < maxHeap.peek()){
                maxHeap.add(num);
                num = maxHeap.poll();              
            }
            minHeap.add(num);  
        }
        else{//奇數
            //將新加入的元素加入小頂堆
           if(!minHeap.isEmpty() && num > minHeap.peek()){
                minHeap.add(num);
                num = minHeap.poll();              
            }
            maxHeap.add(num); 
        }
    }
 
    public Double GetMedian() {
        if(maxHeap.size() == minHeap.size())
            return (minHeap.peek() + maxHeap.peek()) / 2.0;
        else if(maxHeap.size() > minHeap.size())
            return maxHeap.peek()*1.0;
        else
            return minHeap.peek()*1.0;
        /*if(count % 2 == 0){//總量是偶數
            return (minHeap.poll() + maxHeap.poll()) / 2.0;
        }
        else{//總量是奇數
            return (double)minHeap.poll();
        }*/
    }
 
 
}

 

題目-滑動窗口的最大值 

給定一個數組和滑動窗口的大小,找出全部滑動窗口裏數值的最大值。例如,若是輸入數組{2,3,4,2,6,2,5,1}及滑動窗口的大小3,那麼一共存在6個滑動窗口,他們的最大值分別爲{4,4,6,6,6,5}; 針對數組{2,3,4,2,6,2,5,1}的滑動窗口有如下6個。 

{[2,3,4],2,6,2,5,1},

{2,[3,4,2],6,2,5,1},

{2,3,[4,2,6],2,5,1},

{2,3,4,[2,6,2],5,1},

{2,3,4,2,[6,2,5],1},

{2,3,4,2,6,[2,5,1]}

思路

1.因爲題目要求輸出滑動窗口中數值最大值。一樣使用java的PriorityQueue,使用大頂堆。經過滑動窗口裏的值來維護這個大頂堆。

滑動窗口滑動時,爲了維護滑動窗口值組成的大頂堆,將當前元素加入到大頂堆,堆頂元素就是最大值。將滑動窗口最左邊的值去掉。

最後把該元素添加到Arraylist中待輸出。

2.Queue的用法:

  offer()&add():add()在失敗時會拋出異常。

  poll()&remove():返回第一個元素,並在隊列中刪除。remove()在失敗時會拋出異常。

  peek()&element():返回第一個元素.element()在隊列爲空時,會拋出異常。

解法

import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> ret = new ArrayList<>();
        if(size > num.length || size < 1){
            return ret;
        }
        //構建大頂堆.
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(11,
               new Comparator<Integer>(){
                   public int compare(Integer o1, Integer o2){
                       return o2 - o1;
                   }
               });
        //最初添加,滑動窗口滑動時,將當前元素加入大頂堆中,堆頂元素便是最大值。把頭三個元素加入到maxHeap中,而且輸出最大元素到Arraylist
        for(int i = 0; i < size ; i++){
            maxHeap.add(num[i]);
        }
        //同時還要判斷當前元素是否存在於當前窗口中,不存在的話彈出,最後將該元素添加到Arraylist
        ret.add(maxHeap.peek());
        //窗口開始滑動。維護一個大小爲size的大頂堆
        for(int i=0, j=i+size; j < num.length; i++, j++){
            //按照窗口,添加元素。maxHeap中始終爲窗口內的元素
            maxHeap.remove(num[i]);
            maxHeap.add(num[j]);
            ret.add(maxHeap.peek());
        }
        return ret;
    }
}

 

題目-矩陣中的路徑

請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串全部字符的路徑。路徑能夠從矩陣中的任意一個格子開始,每一步能夠在矩陣中向左,向右,向上,向下移動一個格子。若是一條路徑通過了矩陣中的某一個格子,則該路徑不能再進入該格子。 例如   矩陣中包含一條字符串"bcced"的路徑,可是矩陣中不包含"abcb"路徑,由於字符串的第一個字符b佔據了矩陣中的第一行第二個格子以後,路徑不能再次進入該格子。 

思路

1.這是一個典型的回溯法應用場景。

2.首先須要建立一個輔助數組 來 標記 每一個位置是不是字符串中的字符;再建立一個變量,用來存儲當前路徑的長度。當其長度與字符串同樣時,說明已經完成。

3.先遍歷矩陣,找到和字符串第一個字符相同的字符,做爲路徑的開始;

4.函數中首先要進行合法性判斷&當前路徑的字符和字符串上對應位置的字符是否相等。符合條件才能進行下面的遞歸遍歷;

5.矩陣路徑此時+1,而且標記輔助數組中的該位置爲已訪問;

6.因爲「每一步能夠在矩陣中向左,向右,向上,向下移動一個格子」。接下來對該位置的上下左右四個位置進行遞歸遍歷,在遞歸中,對於各位置找尋下一個位置。若是找到了,就可以一條順序下去找到完整的路徑,返回true;

7.若是沒有找到,須要將路徑-1,且標記該位置從新爲未訪問(清除以前的標記狀態)。由於要從新尋找。

解法

public class Solution {
    private final static int[][] next = {{0,-1},{0,1},{-1,0},{1,0}};
    private int rows;
    private int cols;
     
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        if(matrix==null || rows<=0 || cols<=0 || str==null){
            return false;
        }
        if(str.length==0){
            return true;
        }
        //用來標記路徑中已經訪問過的結點
        boolean[] marked=new boolean[matrix.length];
        for(int i=0; i<rows; i++){
            for(int j=0; j<cols; j++){
                //先遍歷矩陣,找到和字符串第一個字符相同的字符,做爲路徑的開始
                if(backtracking(matrix, str, marked, 0, i, j, rows, cols))
                    return true;
            }
        }
        return false;
    }
    //嘗試尋找路徑
    public boolean backtracking(char[] matrix, char[] str, boolean[] marked, int k, int row, int col, int rows, int cols){
        //合法性判斷&當前路徑的字符和字符串上對應位置的字符是否相等
        if(row<0 || row>=rows || col<0 || col>=cols ||
                  str[k]!=matrix[row*cols+col] || marked[row*cols+col]){
            return false;
        }
        //直到路徑字符串上全部字符都在矩陣中找到合適的位置
        if(k==str.length-1){
            return true;
        }
        //要搜索這個結點,首先須要將其標記爲已經使用,防止重複使用
        marked[row*cols+col]=true;
        //對當前位置的上下左右4個相鄰的格子進行遍歷,匹配到下一個字符
        if(backtracking(matrix, str, marked, k+1, row+1, col, rows, cols) ||
          backtracking(matrix, str, marked, k+1, row, col+1, rows, cols) ||
          backtracking(matrix, str, marked, k+1, row-1, col, rows, cols) ||
          backtracking(matrix, str, marked, k+1, row, col-1, rows, cols)){
            return true;
        }
        //若是4個相鄰的格子都沒有匹配字符串中下一個的字符,代表當前路徑字符串中字符在矩陣中的定位不正確,咱們須要回到前一個,而後從新定位。
        //這一次搜索結束後,須要將該結點的已經使用狀態清除
        marked[row*cols+col]=false;
        return false;
    }

}

 

題目-機器人的運動範圍

地上有一個m行和n列的方格。一個機器人從座標0,0的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,可是不能進入行座標和列座標的數位之和大於k的格子。 例如,當k爲18時,機器人可以進入方格(35,37),由於3+5+3+7 = 18。可是,它不能進入方格(35,38),由於3+5+3+8 = 19。請問該機器人可以達到多少個格子? 

思路

這道題與上一題思路基本類似,使用回溯法。

一點點小區別在於:

1.本題規定直接從「座標0,0的各自開始移動」,上一題須要咱們去遍歷數組,獲得一個合適的路徑開始點;

2.本題中有一些格子不可以走(行座標和列座標的數位之和大於k的格子)。每一步移動時,還須要添加一步來判斷這個各自是否合法(再合法性判斷時添加便可)。也就是至關於上一題中須要判斷結點是否匹配的效果。

解法 

public class Solution {
    public int movingCount(int threshold, int rows, int cols)
    {
        if(rows <= 0 || cols <= 0){
            return 0;
        }
        //標記已經訪問過的位置
        boolean[] marked = new boolean[rows*cols];
        for(int i=0; i<marked.length; i++){
            marked[i] = false;
        }
        //從(0,0)座標開始移動
        int count = movingCountCore(threshold, rows, cols, 0, 0, marked);
        return count;
    }
     
    public int movingCountCore(int threshold, int rows, int cols, int row, int col, boolean[] marked){
        int count = 0;
        if(row>=0 && row<rows && col>=0 && col<cols &&
          getDigitSum(row)+getDigitSum(col)<=threshold &&
          !marked[row*cols+col]){
            marked[row*cols+col] = true;
            count = 1+movingCountCore(threshold, rows, cols, row-1, col, marked)+
                    movingCountCore(threshold, rows, cols, row+1, col, marked)+
                    movingCountCore(threshold, rows, cols, row, col-1, marked)+
                    movingCountCore(threshold, rows, cols, row, col+1, marked);
        }
        return count;
    }
     
    public int getDigitSum(int num){
        int sum = 0;
        while(num > 0){
            sum += (num%10);
            num /= 10;
        }
        return sum;
    }
     
     
}

 

題目-剪繩子

給你一根長度爲n的繩子,請把繩子剪成整數長的m段(m、n都是整數,n>1而且m>1),每段繩子的長度記爲k[0],k[1],...,k[m]。請問k[0]xk[1]x...xk[m]可能的最大乘積是多少?例如,當繩子的長度是8時,咱們把它剪成長度分別爲二、3、3的三段,此時獲得的最大乘積是18。 

思路 

1.本題可使用貪婪算法,可是並非全部問題均可以經過 貪婪算法 獲得最優解,依賴於數學證實。貪婪算法的效率也更高。

2.咱們使用動態規劃(每一步都是從新將子問題的最優值相加計算出的)來解決這個問題。雖然不少子問題會重複求解,效率不高。

  要求的是乘積最大值,那麼定義一個函數f(n)表示長度爲n的繩子剪成若干段後 各段 乘積 的最大值。

  當n=1時,最大乘積只能爲0=1*0;

  當n=2時,最大乘積只能爲1=1*1;

  當n=3時,最大乘積只能爲2=1*2;

  當n=4時,能夠分爲以下幾種狀況:1*1*1*1,1*2*1,1*3,2*2,最大乘積爲4。

  剪第一刀時,選擇下第一刀的地方有1~n-1這些地方,有n-1種可能。剪出來的第一段的繩子可能長度爲1,2,3,...,n-1。假設在第i處下刀,繩子將分爲[0,i]和[i,n],長度分別爲i與n-i。那麼找出第一刀最合適的位置,就是找i在哪裏下刀,可使得[0,i]與[i,n]的乘積最大。

  即f(n)=max( f(i)*f(n-i) ),0<i<n。

  從下往上推,先計算f(1)接着計算f(2)...直到獲得f(n)。大的問題經過按照小問題的最優組合獲得。

3.好比要求長度爲10的繩子,就計算1-9這9種長度的繩子,每種長度的最大乘積是多少。要求長度爲9的繩子,就計算1-8這8種長度的繩子,每種長度的最大乘積是多少。以此類推。

解法

public class Solution {
    public int cutRope(int target) {
     //dp用來存放最大乘積值
int[] dp = new int[target+1]; dp[1] = 1; //記長度爲n的最大乘積爲f(n),易知f(n)=max{ f(i)*f(n-i) },其中0<i<n。 for(int i=2; i<=target; i++){ for(int j=1; j<i; j++){ dp[i] = Math.max(dp[i], Math.max(j*(i-j), dp[j]*(i-j))); } } return dp[target]; } }

 

public int cutRope(int length) {
        if (length < 2) {
            return 0;
        }
        if (length == 2) {
            return 1;
        }
        if (length == 3) {
            return 2;
        }
        //存儲長度從 0-len 的最大結果
        int[] products = new int[length + 1];  // 將最優解存儲在數組中
        // 數組中第i個元素表示把長度爲i的繩子剪成若干段以後的乘積的最大值
        products[0] = 0;
        products[1] = 1;
        products[2] = 2;
        products[3] = 3;
        int max = 0;
        for (int i = 4; i <= length; i++) {  //i表示長度
            max = 0;
            for (int j = 1; j <= i / 2; j++) {
                //因爲長度i存在(1,i-1)和(i-1,1)的重複,因此只須要考慮前一種
                int product = products[j] * products[i - j];
                if (product > max) {
                    max = product;
                }
                products[i] = max;
            }
        }
        max = products[length]
        return max;
    }
}

 

題目-圖的廣度優先遍歷

用隊列記錄下一步的走向。

1.訪問下一個未訪問的鄰接點,這個頂點必須是當前頂點的鄰接點,標記它,加入隊列

2.若是由於已經沒有 未訪問頂點,而不能執行上一條規則,那麼從隊列頭取一個頂點,使其成爲當前頂點

3.直到隊列爲空,完成搜索

題解

public void searchTraversing(GraphNode node){
         ArrayList<GraphNode> visited=new ArrayList<GraphNode>();
         ArrayList<GraphNode> list=new ArrayList<GraphNode>();
         Queue<GraphNode> queue = new LinkedList<GraphNode>();
         queue.add(node);
         while(!queue.isEmpty()){
                 GraphNode curNode=queue.poll();
                  if(!visited.contain(curNode)){
                           visited.add(curNode);
                           list.add(curNode.getLabel());
                           for(int i=0;i<curNode.edgeList.size();i++){
                                    queue.offer(curNode.edgeList.get(i).getNodeRight());
                            }

                  }

          } 
 
}

 

(思路部分的圖均源於網絡,侵刪)

相關文章
相關標籤/搜索