【好書推薦】《劍指Offer》之硬技能(程式設計題7~11)
本文例子完整原始碼地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sword
《【好書推薦】《劍指Offer》之軟技能》
《【好書推薦】《劍指Offer》之硬技能(程式設計題1~6)》
持續更新,敬請關注公眾號:coderbuff,回覆關鍵字“sword”獲取相關電子書。
7.重建二叉樹
題目:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建該二叉樹。例如:輸入前序遍歷序列{1, 2, 4, 7, 3, 5, 6, 8}和中序遍歷序列{4, 7, 2, 1, 5, 3, 8, 6}。定義二叉樹節點
1 /** 2 * 二叉樹節點 3 * @author OKevin 4 * @date 2019/5/30 5 **/ 6 public class Node<T> { 7 /** 8 * 左孩子 9 */ 10 private Node left; 11 12 /** 13 * 右孩子 14 */ 15 private Node right; 16 17 /** 18 * 值域 19 */ 20 private T data; 21 22 public Node() { 23 } 24 25 public Node(T data) { 26 this.data = data; 27 } 28 29 //省略getter/setter方法 30 }
解法一:遞迴
1 /** 2 * 根據前序遍歷序列和中序遍歷序列重建二叉樹 3 * @author OKevin 4 * @date 2019/5/30 5 **/ 6 public class Solution { 7 public Node<Integer> buildBinaryTree(Integer[] preorder, Integer[] inorder) { 8 if (preorder.length == 0 || inorder.length == 0) { 9 return null; 10 } 11 Node<Integer> root = new Node<>(preorder[0]); 12 int index = search(0, inorder, root.getData()); 13 root.setLeft(buildBinaryTree(Arrays.copyOfRange(preorder, 1, index + 1), Arrays.copyOfRange(inorder, 0, index))); 14 root.setRight(buildBinaryTree(Arrays.copyOfRange(preorder, index + 1, preorder.length), Arrays.copyOfRange(inorder, index + 1, inorder.length))); 15 return root; 16 } 17 18 /** 19 * 在中序遍歷的序列中查詢根節點所在的位置 20 * @param start 開始查詢的下標 21 * @param inorder 中序遍歷序列 22 * @param rootData 根節點值 23 * @return 節點值在中序遍歷序列中的下標位置 24 */ 25 private int search(int start, Integer[] inorder, Integer rootData) { 26 for (; start < inorder.length; start++) { 27 if (rootData.equals(inorder[start])) { 28 return start; 29 } 30 } 31 return -1; 32 } 33 }
二叉樹的遍歷一共分為:前序遍歷、中序遍歷和後序遍歷
前序遍歷遍歷順序為:根節點->左節點->右節點
中序遍歷遍歷順序為:左節點->根節點->右節點
後序遍歷遍歷順序為:左節點->右節點->根節點
例如二叉樹:
1
/ \
2 3
/ / \
4 5 6
\ /
7 8
前序遍歷結果為:1、2、4、7、3、5、6、8
中序遍歷結果為:4、7、2、1、5、3、8、6
後序遍歷結果為:7、4、2、5、8、6、3、1
此題給出前序和中序的遍歷結果,要求重建二叉樹。從前序遍歷結果得知,第一個節點一定是根節點。從中序遍歷結果可知,根節點左側一定是其左子樹右側一定是其右子樹。
那麼可以得到:
第一次:
根據前序遍歷結果得知,1為根節點,根據中序遍歷結果得知,4、7、2為左子樹,5、3、8、6為右子樹。
第二次:
根據前序遍歷結果得知,2為節點,根據中序遍歷,4、7位節點2的左子樹,節點2沒有右子樹。
第三次:
根據前序遍歷結果得知,4為節點,根據中序遍歷,7為節點4的右子樹,節點4沒有左子樹。
以此類推,根據遞迴即可構建一顆二叉樹。
測試程式
1 /** 2 * 1 3 * / \ 4 * 2 3 5 * / / \ 6 * 4 5 6 7 * \ / 8 * 7 8 9 * @author OKevin 10 * @date 2019/5/30 11 **/ 12 public class Main { 13 public static void main(String[] args) { 14 Integer[] preorder = new Integer[]{1, 2, 4, 7, 3, 5, 6, 8}; 15 Integer[] inorder = new Integer[]{4, 7, 2, 1, 5, 3, 8, 6}; 16 Solution solution = new Solution(); 17 Node<Integer> node = solution.buildBinaryTree(preorder, inorder); 18 } 19 }
8.二叉樹的下一個節點
題目:給定一顆二叉樹和其中的一個節點,如何找出中序遍歷序列的下一個節點?節點中除了兩個分別指向左、右子節點的指標,還有一個指向父節點的指標。
分析:熟悉二叉樹中序遍歷的特點。查詢節點的下一個節點,一共有兩種情況:一、節點有右子樹,節點的下一個節點即為右子樹的最左子節點;二、節點沒有右子樹,此時又要分為兩種情況:1、如果節點位於父節點的左節點,節點的下一個節點即為父節點;2、如果節點位於父節點的右節點,此時向上遍歷,找到它是父節點的左節點。
節點定義
1 /** 2 * 二叉樹節點定義 3 * @author OKevin 4 * @date 2019/6/3 5 **/ 6 public class Node<T> { 7 /** 8 * 值域 9 */ 10 private T data; 11 12 /** 13 * 左節點 14 */ 15 private Node<T> left; 16 17 /** 18 * 右節點 19 */ 20 private Node<T> right; 21 22 /** 23 * 父節點 24 */ 25 private Node<T> parent; 26 27 public Node() { 28 } 29 30 public Node(T data) { 31 this.data = data; 32 } 33 //省略getter/setter方法 34 }
中序遍歷情況下,查詢二叉樹節點的下一個節點
1 /** 2 * 中序遍歷情況下,查詢節點的下一個節點 3 * @author OKevin 4 * @date 2019/6/3 5 **/ 6 public class Solution { 7 public Node getNextNode(Node<Integer> head) { 8 if (head == null) { 9 return null; 10 } 11 Node<Integer> p = head; 12 //第一種情況,節點有右子樹。節點右子樹的最左子節點即為節點中序遍歷的下一個節點 13 if (p.getRight() != null) { 14 p = p.getRight(); 15 while (p.getLeft() != null) { 16 p = p.getLeft(); 17 } 18 return p; 19 } 20 //第二種情況,節點沒有右子樹。仍然有兩種情況:一、節點位於父節點的左節點,此時父節點即為節點中序遍歷的下一個節點;二、節點位於父節點的右節點,此時一直向上查詢,直到是它父節點的左節點 21 while (p.getParent() != null) { 22 if (p == p.getParent().getLeft()) { 23 return p.getParent(); 24 } 25 p = p.getParent(); 26 } 27 return null; 28 } 29 }
測試程式
1 /** 2 * 1 3 * / \ 4 * 2 3 5 * / / \ 6 * 4 5 6 7 * \ / 8 * 7 8 9 * 中序遍歷序列:4,7,2,1,5,3,8,6 10 * @author OKevin 11 * @date 2019/6/3 12 **/ 13 public class Main { 14 public static void main(String[] args) { 15 Node<Integer> node1 = new Node<>(1); 16 Node<Integer> node2 = new Node<>(2); 17 Node<Integer> node3 = new Node<>(3); 18 Node<Integer> node4 = new Node<>(4); 19 Node<Integer> node5 = new Node<>(5); 20 Node<Integer> node6 = new Node<>(6); 21 Node<Integer> node7 = new Node<>(7); 22 Node<Integer> node8 = new Node<>(8); 23 node1.setLeft(node2); 24 node1.setRight(node3); 25 node2.setLeft(node4); 26 node2.setParent(node1); 27 node3.setLeft(node5); 28 node3.setRight(node6); 29 node3.setParent(node1); 30 node4.setRight(node7); 31 node4.setParent(node2); 32 node5.setParent(node3); 33 node6.setLeft(node8); 34 node6.setParent(node3); 35 node7.setParent(node4); 36 node8.setParent(node6); 37 Solution solution = new Solution(); 38 System.out.println(solution.getNextNode(node6).getData()); 39 } 40 }View Code
9.用兩個棧實現佇列
題目:用兩個棧實現一個佇列。
分析:棧的結構是FILO(先進後出),佇列的結構是FIFO(先進先出)。棧s1用於儲存元素,棧s2當執行刪除佇列尾元素時,從s1彈出資料進入s2,再彈出s2,即實現一個佇列。
1 /** 2 * 兩個棧實現一個佇列 3 * @author OKevin 4 * @date 2019/6/3 5 **/ 6 public class MyQueue<T> { 7 private Stack<T> s1 = new Stack<>(); 8 private Stack<T> s2 = new Stack<>(); 9 10 /** 11 * 從隊尾新增元素 12 * @param t 元素 13 * @return 新增的資料 14 */ 15 public T appendTail(T t) { 16 s1.push(t); 17 return t; 18 } 19 20 /** 21 * 對隊頭刪除元素 22 * @return 刪除的元素 23 */ 24 public T deleteTail() { 25 if (s1.empty() && s2.empty()) { 26 return null; 27 } 28 if (s2.empty()) { 29 while (!s1.empty()) { 30 s2.push(s1.pop()); 31 } 32 } 33 T t = s2.pop(); 34 return t; 35 } 36 }
10.斐波那契數列
題目:求斐波那契數列的第n項。
解法一:遞迴
1 /** 2 * 求斐波那契數列的第n項 3 * @author OKevin 4 * @date 2019/6/3 5 **/ 6 public class Solution1 { 7 8 public Integer fibonacci(Integer n) { 9 if (n.equals(0)) { 10 return 0; 11 } 12 if (n.equals(1)) { 13 return 1; 14 } 15 return fibonacci(n - 1) + fibonacci(n - 2); 16 } 17 }
優點:簡單易懂
缺點:如果遞迴呼叫太深,容易導致棧溢位。並且節點有重複計算,導致效率不高。
解法二:迴圈
1 /** 2 * 求斐波那契數列的第n項 3 * @author OKevin 4 * @date 2019/6/3 5 **/ 6 public class Solution2 { 7 8 public Integer fibonacci(Integer n) { 9 if (n.equals(0)) { 10 return 0; 11 } 12 if (n.equals(1)) { 13 return 1; 14 } 15 Integer first = 0; 16 Integer second = 1; 17 Integer result = first + second; 18 for (int i = 2; i <= n; i++) { 19 result = first + second; 20 first = second; 21 second = result; 22 } 23 return result; 24 } 25 }
通過迴圈計算斐波那契數列能避免重複計算,且不存在呼叫棧過深的問題。
11. 旋轉陣列的最小數字
題目:把一個數組最開始的若干個元素搬到陣列的末尾,我們稱之為陣列的旋轉。輸入一個遞增的陣列的一個旋轉,輸出旋轉陣列的最小元素。例如,陣列{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該陣列的最小值為1。
*題中本意是希望能找到陣列中的最小數字,直接暴力解法遍歷即可。
引子:通過“二分查詢”演算法查詢有序陣列中的數字。
二分查詢有序陣列是否存在數字
1 /** 2 * 二分查詢有序陣列中的數字 3 * @author OKevin 4 * @date 2019/6/3 5 **/ 6 public class BinarySearch { 7 8 public boolean find(Integer[] array, Integer target) { 9 Integer start = 0; 10 Integer end = array.length - 1; 11 return partition(array, start, end, target); 12 } 13 14 private boolean partition(Integer[] array, Integer start, Integer end, Integer target) { 15 if (target < array[start] || target > array[end] || start > end) { 16 return false; 17 } 18 19 int middle = (end + start) / 2; 20 21 if (target > array[middle]) { 22 return partition(array, middle + 1, end, target); 23 } else if (target < array[middle]) { 24 return partition(array, start, middle - 1, target); 25 } else { 26 return true; 27 } 28 } 29 }
利用二分法思想查詢旋轉陣列中的最小數字,注意當出現原始陣列為:{0,1,1,1,1}時,{1,1,1,0,1}和{1,0,1,1,1}均是旋轉陣列,這兩種情況left=middle=right都是1,不能區別,此時只能按照順序查詢的方式。
1 /** 2 * 找到旋轉陣列中的最小值 3 * @author OKevin 4 * @date 2019/6/3 5 **/ 6 public class Solution { 7 8 public Integer find(Integer[] array) { 9 if (array.length == 0) { 10 return -1; 11 } 12 int left = 0; 13 int right = array.length - 1; 14 int middle = 0; 15 while (array[left] >= array[right]) { 16 if (right - left == 1) { 17 middle = right; 18 break; 19 } 20 middle = (left + right) / 2; 21 if (array[left].equals(array[right]) && array[left].equals(array[middle])) { 22 return min(array, left, right); 23 } 24 if (array[middle] >= array[left]) { 25 left = middle; 26 } else { 27 right = middle; 28 } 29 } 30 return array[middle]; 31 } 32 33 /** 34 * 當出現原始陣列為:{0,1,1,1,1}時,{1,1,1,0,1}和{1,0,1,1,1}均是旋轉陣列,這兩種情況left=middle=right都是1,不能區別 35 * @param array 陣列 36 * @param left 起始 37 * @param right 結束 38 * @return 39 */ 40 private Integer min(Integer[] array, int left, int right) { 41 int min = array[left]; 42 for (int i = left + 1; i < right; i++) { 43 if (array[i] < min) { 44 min = array[i]; 45 } 46 } 47 return min; 48 } 49 }
本文例子完整原始碼地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sword
《【好書推薦】《劍指Offer》之軟技能》
《【好書推薦】《劍指Offer》之硬技能(程式設計題1~6)》
持續更新,敬請關注公眾號:coderbuff,回覆關鍵字“sword”獲取相關電子書。
這是一個能給程式設計師加buff的公眾號 (CoderBuff)