1. 程式人生 > 實用技巧 >劍指Offer-2

劍指Offer-2



只能說受益匪淺


1

判定入棧,出棧序列是否匹配

// 思路:用輔助棧來模擬出入棧
import java.util.Stack;
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        int cnt = 0;								// 記錄出棧個數或下標   
        Stack<Integer> stack = new Stack<>();					  // 輔助棧
        
        for(int i = 0; i < pushA.length; i++){
            stack.push(pushA[i]);						// 模擬入棧
            while(!stack.isEmpty() && stack.peek() == popA[cnt]){ // while迴圈模擬出棧
                stack.pop();
                cnt++;
            }
        }
        return stack.isEmpty();							// 判斷輔助棧是否為空
    }
}


2

從上往下層級遍歷二叉樹

// 思路:用一個 佇列 模擬層次
// 用LinkedList模擬佇列
// 棧是addFirst,removeFirst
// 佇列addLast,removeFirst
// 這裡的層次遍歷,不是一層層來的,是一層裡面分左右子樹分開來的
import java.util.ArrayList;
import java.util.LinkedList;
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> list = new ArrayList();      // 存取層次遍歷序列
        LinkedList<TreeNode> queue = new LinkedList();  // 儲存節點模擬層次的
        
        if(root == null) return list;
        queue.addLast(root);
        
        while(!queue.isEmpty()){
            TreeNode temp = queue.removeFirst();  // 出隊
            list.add(temp.val);
            
            if(temp.left != null){
                queue.addLast(temp.left);
            }
            if(temp.right != null){
                queue.addLast(temp.right);
            }
        }
        return list;
    }
}
// 用ArrayList模擬佇列
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
       ArrayList<Integer> list = new ArrayList();
       ArrayList<TreeNode> queue = new ArrayList();
        
       if(root == null) return list;
       queue.add(root);
        
       while(queue.size() != 0){
           TreeNode temp = queue.remove(0);
           list.add(temp.val);
           
           if(temp.left != null){
               queue.add(temp.left);
           }
           if(temp.right != null){
               queue.add(temp.right);
           }
       }
        return list;
    }
}


3

判斷是否後序遍歷

// 現在開始自己規定,凡是自己傳進去的陣列長度這些引數,都是實際長度,就是length-1這種
// 思路:後序中最後一個是根,去除最後一個可以分成兩段。前段小於根,後段大於根,以此類推遞迴
public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence == null || sequence.length == 0) return false;
        return search(sequence,0,sequence.length-1);
    }
    private boolean search(int[] arr,int left,int right){
        
        if(left >= right) return true;  // 遞迴出口,這裡最重要
        
        int mid = left;  // 從左遍歷找分界(對比根),小心越界
        while(arr[mid] < arr[right] && mid < right){
            mid++;
        }
        for(int i = mid; i < right; i++){  // 判斷右端是否符合
            if(arr[i] < arr[right]){
                return false;
            }
        }
        // 左去界(遍歷的時候比根大了才停止的),右去根
        return search(arr,left,mid-1) && search(arr,mid,right-1);
    }
}


4

二叉樹和為某值的路徑

import java.util.ArrayList;
public class Solution {
    
    // 一個儲存當前遍歷的路徑,一個儲存符合的全部路徑
    ArrayList<ArrayList<Integer>> list = new ArrayList<>();
    ArrayList<Integer> path = new ArrayList<>();
    
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        search(root,target);
        return list;
    }
    
    // 遍歷用前序或者DFS
    private void search(TreeNode root, int target){
        if(root == null) return ;
        
        target -= root.val;
        path.add(root.val);
        
        if(root.left == null && root.right == null && target == 0){
            list.add(new ArrayList<Integer>(path));
        }
        
        search(root.left,target);
        search(root.right,target);
        
        // 回溯時不用加回target,因為是個副本
        path.remove(path.size()-1);
    }
}


5

複雜連結串列的複製

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
// 思路:
// 1. 複製每個節點(暫不處理隨機指向),將新複製的節點插入原節點後面:A->A1
// 2. 處理隨機指向
// 3. 複製連結串列和原連結串列分離
public class Solution {
    public RandomListNode Clone(RandomListNode pHead){
        
        if(pHead == null) return null;
        
        // 1. 複製連結串列,複製節點插入到原節點後面
        RandomListNode node = pHead;
        while(node != null){
            RandomListNode next = node.next;
            RandomListNode cloneNode = new RandomListNode(node.label);
            node.next = cloneNode;  // 連結串列插入過程
            cloneNode.next = next;
            node = next;  // 節點插入後,當前節點記得跳轉到next
        }
        
        // 2. 遍歷處理隨機指向
        node = pHead;
        while(node != null){
            if(node.random != null){
                // 重點:指向隨機的下一個(因複製時插入到後一個去了)
                node.next.random = node.random.next;
            }
            node = node.next.next;  // 複製插入要跳多一個
        }
        
        // 3. 分離節點,奇偶分離
        RandomListNode oldNode = pHead;
        RandomListNode newHead = pHead.next;  // 新表頭
        while(oldNode != null){
            RandomListNode newNode = oldNode.next;
            oldNode.next = newNode.next;
            if(newNode.next != null){
                newNode.next = newNode.next.next;
            }
            oldNode = oldNode.next; // 上面已經更新了舊節點指向,已經跳過一個節點了
        }
        return newHead;
    }
}

// 3. 分離節點,奇偶分離
// RandomListNode oldNode = pHead;
// RandomListNode newNode = pHead.next;  // 因為有複製,所以後一個節點一定不為空
// RandomListNode newHead = newNode;  // 新表頭
// while(newNode.next != null){
//     oldNode.next = newNode.next;
//     oldNode = oldNode.next;
//     newNode.next = oldNode.next;
//     newNode = newNode.next;
// }


6

二叉搜尋樹轉變雙向連結串列

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
/**
 * 遞迴中序遍歷:左 根 右
 * 下面if、else中的意思
 *    4
     / \
 *  3   5
 * 第一步if:head與temp賦值3節點;
 * 第二步else:改動temp節點互相指向,最後head賦值4節點:3 <--> 4
 * 第三步else:改動temp節點互相指向,最後head賦值5節點:4 <--> 5
 * 綜上:3 <--> 4 <--> 5,連結串列完成
 */
public class Solution {
    TreeNode temp = null;          // 臨時節點,幫助形成雙向連結串列
    TreeNode head = null;          // 表頭,用於返回
    public TreeNode Convert(TreeNode pRootOfTree) {
        
        if(pRootOfTree == null) return null;  // 遞迴出口
        
        Convert(pRootOfTree.left); // 左子樹遍歷
        
        if (head == null) {        // 首次要處理根節點
            head = pRootOfTree;    // 第一次訪問,記錄頭節點,用於訪問返回
            temp = pRootOfTree;
        } else {
            temp.right = pRootOfTree;  // 按中序遍歷順序連成連結串列,詳情看上面圖
            pRootOfTree.left = temp;   // 中序就是有序,只需將當前temp指向下一個即可
            temp = temp.right;        // 然後移動當前節點到下一個
        }
        
        Convert(pRootOfTree.right); // 右子樹遞迴
        return head;
    }
}


7

字串的排列(標準的DFS + 交換 / 回溯)

// 思路:根據字串的排列的特點,選擇深度優先搜尋,可通過字元交換實現,重複字元用剪枝
// 1. 分成兩部分,首個字元、後面的全部字元
// 2. 每個字元都可在首位,即首字元和後面的進行交換
// 3. 固定第一個字元,求後面的排列,即遞迴進行2,3步,出口為到了陣列長度
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Collections;

public class Solution {

    // 儲存所有排列
    ArrayList<String> res = new ArrayList();
    
    public ArrayList<String> Permutation(String str) {
        char[] arr = str.toCharArray();  // 轉成陣列容易遍歷
        dfs(arr,0);                      // 實現排列
        Collections.sort(res);           // 排序
        return (ArrayList) res;
    }
    
    private void dfs(char[] arr,int index){
        if(index == arr.length - 1){     // 到葉子節點認為一個排列,遞迴出口
            res.add(String.valueOf(arr));
            return ;
        }
        HashSet<Character> set = new HashSet<>(); // 用來做剪枝的,防止重複
        for(int i = index; i < arr.length; i++){  // 遍歷交換:使得每個元素都在首位
            if(set.contains(arr[i])){
                continue; // 重複,因此剪枝(假設每個元素不重複)
            }
            set.add(arr[i]);
            swap(arr,index,i);  //  1. 首位和後面的全部逐個交換,即每個元素都有在首位的可能
            dfs(arr,index + 1); //  2. 固定首位,排列後面的字元
            swap(arr,index,i);  //  3. 回溯,不影響後面的每個元素都排在首位
        }
    }
    
    // 交換
    private void swap(char[] arr,int i,int j){
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}


8

找出陣列中的一個出現的次數超過陣列長度的一半的數

// 思路1:遍歷多次,儲存每個元素出現的次數
// 思路2:排序後,眾數肯定出現在中間

// 最優解
// 思路:摩爾投票法,查詢超過1/2的數,肯定只有一個
// 流程:依次從序列中選擇兩個數字,若不同則抵消(票數-1),相同當前數值的票數+1,最後剩下的數字就是所找
// 步驟:1.對抗兩兩抵消、2.計算結果是否有效
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        
        int major = 0;
        int count = 0;	// 當前major的票數
        
        for(int i = 0; i < array.length; i++){  // 從頭到尾遍歷
            if(count == 0){
                major = array[i];
            }
            if(major == array[i]){
                count++;
            }else{
                count--;
            }
        }
        
        int countRs = 0;
        for(int num : array){
            if(major == num){
                countRs++;
            }
        }
        return (countRs > array.length/2) ? major : 0;
    }
}



9

找出其中最小的K個數,TopK問題(快排,堆排)

// 無腦解法
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        Arrays.sort(input);
        ArrayList<Integer> list = new ArrayList<>();
        for(int i = 0; i < k; i++){
            list.add(input[i]);
        }
        return list;
    }
}

// 快排,我叫為哨兵排
import java.util.ArrayList;
public class Solution {
    
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        if(input == null || k > input.length) return list;
        
        quickSort(input,0,input.length-1);
        
        ArrayList<Integer> list = new ArrayList();
        for(int i = 0; i < k; i++){
            list.add(input[i]);
        }
        return list;
    }
    
    private void quickSort(int[] arr, int left,int right){
        
        if(left > right) return ;
        int base = arr[left];
        int i = left,j = right;
        
        while(i < j){  // 選基準交換
            while(i < j && base <= arr[j]){
                j--;
            }
            while(i < j && base >= arr[i]){
                i++;
            }
            if(i < j){
                int temp = arr[i];
				arr[i] = arr[j];
				arr[j] = temp;
            }
        }
        
        arr[left] = arr[i];  // 基準歸位
        arr[i] = base;
        
        quickSort(arr,left,i-1);  // 二分治
        quickSort(arr,i+1,right);
    }
}



10

計算連續子向量的最大和,有負數

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        
        if(array == null) return 0;

        // 注意:題目指的連續,不一定從下標0開始,可以視窗滑動的
        // maxSum不初始化為0,存在全負數情況,所以初始值array[0]
        // maxSum儲存最大和
        int curSum,maxSum;
        curSum = maxSum = array[0];
        
        for(int i = 1; i < array.length; i++){
            
            // 一旦遇到和為負數,證明前面的正數效果作廢了
            // 當前和小於0,拋棄前面的和,重新從現在加起
            if(curSum < 0){
                curSum = array[i];
            }else if(curSum > 0){
                curSum += array[i];
            }
            
            // 更新最大和
            if(curSum > maxSum){
                maxSum = curSum;
            }
        }
        return maxSum;
    }
}



11

計算整數中1出現的次數

// 暴力轉成字串判斷
public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        StringBuffer str = new StringBuffer();
        for(int i = 1;i <= n; i++){
            str.append(i);
        }
        
        int count = 0;
        String s = str.toString();
        
        for(int i = 0;i < s.length(); i++){
            if(s.charAt(i) == '1'){
                count++;
            }
        }
        return count;
    }
}

// 區分位數:i表示當前位,其餘表示為高位和低位
// 每次迴圈取當前位,即高位模10(high % 10),分別有三種情況。如下:
public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int count = 0;
        for(int i = 1; i <= n; i *= 10){ // 只需迴圈位數次
            
            int high = n / i;
            int low  = n % i;
            
            if(high % 10 == 0){
                count += high / 10 * i;
            }else if (high % 10 == 1){
                count += (high / 10 * i) + (low + 1);
            }else {
                count += (high / 10 + 1) * i;
            }
        }
        return count;
    }
}

// 最優解,上面的優化
// 當百位 = 0,則high / 10 == (high + 8) / 10
// 當百位 > 1,取8就進位,效果等於(high / 10 + 1)
public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int count = 0;
        for(int i = 1; i <= n; i *=10){
            
            int high = n / i;
            int low  = n % i;
            
            if(high % 10 == 1){
                count += low + 1;
            }
            
            count += (high + 8) / 10 * i;
        }
        return count;
    }
}



12

把陣列排成最小

// 本質是排序問題,一般用快排
public class Solution {
    public String PrintMinNumber(int [] numbers) {
        
        for(int i = 0; i < numbers.length-1; i++)  // 氣泡排序
            for(int j = 0; j < numbers.length-i-1; j++){
                String str1 = numbers[j] + "" + numbers[j+1];
                String str2 = numbers[j+1] + "" + numbers[j];
                if(str1.compareTo(str2) > 0){  // 排到最後的是最大
                    int temp = numbers[j];
                    numbers[j] = numbers[j+1];
                    numbers[j+1] = temp;
                }
            }
        
        String str = "";
        for(int i = 0; i < numbers.length; i++){
            str += numbers[i];
        }
        return str;
    }
}

// 思路二
// 數字m、n拼接成 mn 和 nm
// 若mn>nm,則m大於n
// 若mn<nm,則m小於n
// 若mn=nm,則m等於n



13

醜數:把只包含質因子2、3和5的數

// 思路:一個醜數一定由另一個醜數乘以2或3或5得到
// 這就是動態規劃??
import java.util.ArrayList;
public class Solution {
    public int GetUglyNumber_Solution(int index) {
        
        if(index <= 0) return 0;
        ArrayList<Integer> list = new ArrayList();
        list.add(1);  // 預設第一個醜數為1
        
        // 用三個下標來模擬三個佇列的尾部,加入list證明已經排好序
        int i2 = 0,i3 = 0,i5 = 0;
        while(list.size() < index){  // 從各自的佇列取出
            int m2 = list.get(i2)*2;
            int m3 = list.get(i3)*3;
            int m5 = list.get(i5)*5;
            int min = Math.min(m2,Math.min(m3,m5));
            list.add(min);
            if(min == m2) i2++;
            if(min == m3) i3++;
            if(min == m5) i5++;
        }
        return list.get(list.size()-1);
    }
}



14

第一個只出現一次的字元位置

// 雜湊表,VALUE存放次數
import java.util.HashMap;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        
        if(str.length() == 0) return -1;
        
        HashMap<Character,Integer> map = new HashMap();
        char arr = str.toCharArray();
        
        
        for(int i = 0; i < str.length(); i++){
            if( map.containsKey(str.charAt(i)) ){
                int num = map.get(str.charAt(i));
                map.put(str.charAt(i),num+1);
            }else{
                map.put(str.charAt(i),1);
            }
        }
        for(int i = 0; i < str.length(); i++){
            if( map.get(str.charAt(i)) == 1 ){
                return i;
            }
        }
        return 0;
    }
}

// 變形體,返回第一個只出現一次的字元
// 雜湊表,VALUE存放次數,這裡一次的話可以存放TRUE/FALSE
import java.util.HashMap;
public class Solution {
    public int FirstNotRepeatingChar(String str) {

        char[] arr = str.toCharArray();
        HashMap<Character,Boolean> hashMap = new HashMap<>();
        
        for(char c : arr){
            hashMap.put(c,!hashMap.containsKey(c));
        }
        
        for(char c : arr){
            if(hashMap.get(c)){
                return c;
            }
        }
        return "";
    }
}



15

陣列的逆序對

// 暴力破解法,雙層for迴圈,內層以i+1開頭(因為當前元素的前面才能構成逆序)
public class Solution {
    public int reversePairs(int[] nums) {
        int cnt = 0;
        for (int i = 0; i < nums.length - 1; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[i] > nums[j]) {
                    cnt++;
                }
            }
        }
        return cnt;
    }
}

// 最優解
// 歸併排序的利用,分治過程中前後數字可對比,是統計的最佳時機
public class Solution {
    
    int count = 0;  // 統計逆序對
    
    public int InversePairs(int [] array) {
        if(array == null || array.length == 0) return 0;
        mergeSort(array,0,array.length-1);
        return count;
    }
    
    private void mergeSort(int[] arr,int start,int end){
        if(start < end){  // 拆分分治的過程,遞迴出口,長度為1預設排好序
            int mid = start + (end - start) / 2;
            mergeSort(arr,start,mid);
            mergeSort(arr,mid+1,end);
            merge(arr,start,mid,end);  // 最後合併
        }
    }
    
    private void merge(int[] arr,int start,int mid,int end){
        int[] temp = new int[end - start + 1];	// 輔助陣列,最後賦值回原陣列
        
        int i = start,j = mid + 1;
        int index = 0;
        while(i <= mid && j <= end){
            if(arr[i] > arr[j]){
                temp[index++] = arr[j++];
                
                // 與歸併排序就多了下面這兩句
                // 合併陣列時,array[i]大於後面array[j]時
                // 則array[i]~array[mid]都是大於array[j]的,所以count += mid + 1 - i
                count += mid - i + 1;
                count = count > 1000000007 ? count % 1000000007 : count;
            }else{
                temp[index++] = arr[i++];
            }
        }
        
        while(i <= mid)
            temp[index++] = arr[i++];
        while(j <= end)
            temp[index++] = arr[j++];
        
        for (int k = 0;k < temp.length;k++)
            arr[start+k] = temp[k];
    }
}



16

兩個連結串列的第一個公共結點

// 先走連結串列二者長度差,然後同步走到相同節點
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        
        // 0. 移動節點要記得復位,這裡卡了好久,不然NPE
        ListNode temp1 = pHead1;
        ListNode temp2 = pHead2;
        
        // 1. 記錄二者的長度
        int p1 = 0, p2 = 0;
        while(pHead1 != null){
            p1++;
            pHead1 = pHead1.next;
        }
        while(pHead2 != null){
            p2++;
            pHead2 = pHead2.next;
        }
        
        if(pHead1 != pHead2) return null;  // 尾節點都不相交,下面也無需遍歷了,簡化操作可忽略
        
        // 2. 上面移動指標要復位
        //    移動長連結串列,移動距離為二者長度差
        pHead1 = temp1;
        pHead2 = temp2;
        if(p1 > p2){
            int temp = p1 - p2;
            while(temp > 0){
                pHead1 = pHead1.next;
                temp--;
            }
        }else{
            int temp = p2 - p1;
            while(temp > 0){
                pHead2 = pHead2.next;
                temp--;
            }
        }
        
        // 3. 二者並行找相同節點
        while(pHead1 != null || pHead2 != null){
            if(pHead1 == pHead2){
                return pHead1;
            }
            pHead1 = pHead1.next;
            pHead2 = pHead2.next;
        }
        
        // 4. 沒有公共節點
        return null;
    }
}

// 思路二,兩條y狀的連結串列,從尾遍歷到頭,第一個不相同的就是交點,使用棧/遞迴實現

// 思路三:最優解,雙指標
// 兩個指標同步走,哪個到了連結串列尾,就設定為對方的頭節點繼續遍歷,最後會相遇
// 長度相同有公共結點,第一次就遍歷到;沒有公共結點,走到尾部NULL相遇,返回NULL
// 長度不同有公共結點,第一遍差值就出來了,第二遍一起到公共結點;沒有公共,一起到結尾NULL
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        
        while(p1 != p2){
            p1 = (p1 == null ? pHead2 : p1.next);
            p2 = (p2 == null ? pHead1 : p2.next);
        }
        return p1;
    }
}



17

統計一個數字在排序陣列中出現的次數(排序就二分)

// 思路:傻子做法
public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        int cnt = 0;
        for(int i = 0; i < array.length; i++){
            if(k == array[i]){
                cnt++;
            }
        }
        return cnt;
    }
}

// 思路:首先二分法,找到之後向前向後找
public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        
        int cnt = 0;
        int left = 0;
        int right = array.length - 1;
        int mid = -1;
        
        while(left <= right){
            mid = left + (right-left) / 2;
            if(array[mid] == k){
                cnt++;
                break;
            }else if(array[mid] < k){
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        
        if(mid == -1) return cnt;  // 沒找到相同的,先退出了
        
        for(int i = mid+1; i < array.length; i++){
            if(array[i] == k) cnt++;
            else break;
        }
        for(int i = mid-1; i >= 0; i--){
            if(array[i] == k) cnt++;
            else break;
        }
        return cnt;
    }
}

// 思路三:最優,二分左右邊界,相減即可
public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        if(array == null || array.length == 0) return 0;
        
        int first = getFirstK(array,k);
        int last = getLastK(array,k);
        
        if(first == -1 || last == -1) return 0;
        else return last - first + 1;
    }
    private int getFirstK(int [] array, int k){
        int low = 0;
        int high = array.length - 1;
        while(low <= high){
            int mid = low + (high-low) / 2;
            if(array[mid] == k){
                high = mid - 1;
            }else if(array[mid] > k){
                high = mid - 1;
            }else{
                low = mid + 1;
            }
        }
        if(low == array.length) return -1;  // 這裡最重要
        return array[low] == k ? low : -1;
    }
    private int getLastK(int [] array, int k){
        int low = 0;
        int high = array.length - 1;
        while(low <= high){
            int mid = low + (high - low) / 2;
            if(array[mid] == k){
                low = mid + 1;
            }else if(array[mid] > k){
                high = mid - 1;
            }else{
                low = mid + 1;
            }
        }
        if(high == -1) return -1;
        return array[high] == k ? high : -1;
    }
}



18

求樹深

// 遞迴
public class Solution {
    public int TreeDepth(TreeNode root) {
        // 遞迴出口
        if(root == null) return 0;
        
        // 遞迴條件
        return Math.max( TreeDepth(root.left)+1 , TreeDepth(root.right)+1 );
        
    }
}

// 層次遍歷解決,此層次和之前的不同
// 每層次遍歷一遍,即深度+1
// 遍歷完就深度也出來了
// 注意,遍歷一次就要一層全部處理完,否則就不是樹深+1了
import java.util.LinkedList;
public class Solution {
    public int TreeDepth(TreeNode root) {
        
        int cnt = 0;                                    // 層數
        LinkedList<TreeNode> queue = new LinkedList();  // 儲存節點模擬層次的
        
        if(root == null) return cnt;
        queue.addLast(root);
        
        while(!queue.isEmpty()){
            int size = queue.size();
            for(int i = 0; i < size; i++){              // for將當前層處理完
                TreeNode temp = queue.removeFirst();    // 出隊
                if(temp.left != null) queue.addLast(temp.left);
                if(temp.right != null) queue.addLast(temp.right);
            }
            cnt++;
        }
        return cnt;
    }
}



19

驗證平衡二叉樹平衡(任何結點的兩個子樹的高度差小於等於1),可以結合17題

// 遞迴
public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        // 空也是一個平衡樹
        if(root == null) return true;
        return getDepth(root) != -1;
    }
    
    // 後序遍歷算深度,每個節點只用算一次
    private int getDepth(TreeNode node){
        
        if(node == null) return 0;
        
        // 左樹的深度,+1動作放到最後,因為下面要判斷-1
        int left = getDepth(node.left);
        if(left == -1) return -1;
        
        // 右樹的深度
        int right = getDepth(node.right);
        if(right == -1) return -1;
        
        // 左右樹深度比較,也就是遞迴
        if(Math.abs(left - right) > 1) return -1;
        
        // 當前
        return Math.max(left,right) + 1;
    }
}



20

陣列中只出現一次的數字

// num1,num2分別為長度為1的陣列。傳出引數,將num1[0],num2[0]設定為返回結果即可,C語言題目垃圾
// 用HashSet去重特性
import java.util.HashSet;
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        HashSet<Integer> set = new HashSet();
        
        // set.add,如果存在返回true,如為false則插入元素
        for(int i = 0; i < array.length; i++){
            if(set.contains(array[i])){
                set.remove(array[i]);
            }else{
                set.add(array[i]);
            }
        }
        Object[] temp = set.toArray();
        num1[0] = (int) temp[0];
        num2[0] = (int) temp[1];
    }
}

// 最優解
// 使用陣列的異或運算,相同為0,不同為1,任何數與0異或為本身
// 如果一個數字只出現一次,其餘兩次,那麼全體異或過程中兩兩相同的就會抵消變為0,剩下的數和0異或得出本身
// eg:
int res = 0;
int[] nums = {1,1,2,3,4,5,3,4,5};
for(int value : nums){
    res ^= value;
}
System.out.println(res); // 2

// ——————————————————————————————————————————————————————————————————————————————————————

// 如果出現了兩次,那麼就要進行分組異或 
// 假如兩個不同的數為 a、b,那麼所有數字異或結果就是 a^b 的結果,記為x
// x轉成二進位制,其中的0和1表示a、b二進位制中相同和不同的部分
// 若選二進位制x中,不為0的位,按照該位分組,預設選不為0的最低位
// 流程:
// 1.對所有數字異或,得出x
// 2.在x中找不為0的最低位
// 3.根據這位對所有的數字進行分組
// 4.在每個組內進行異或操作,得到兩個數字
//num1,num2分別為長度為1的陣列。傳出引數
//將num1[0],num2[0]設定為返回結果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        
        int ret = 0;
        for(int num : array){
            ret ^= num;
        }
        
        int div = 1;
        while((div & ret) == 0){
            div <<= 1;		// div兩倍關係,才能在二進位制上逐步變成1,所以分組也只能是2,4,6來分
        }
        
        int a = 0;
        int b = 0;
        for(int num : array){
            if ((div & num) != 0) {
                a ^= num;
            } else {
                b ^= num;
            }
        }

        num1[0] = a;
        num2[0] = b;
    }
}