【好書推薦】《劍指Offer》之硬技能(編程題7~11)

本文例子完整源碼地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/swordhtml

《【好書推薦】《劍指Offer》之軟技能》node

《【好書推薦】《劍指Offer》之硬技能(編程題1~6)》git

持續更新,敬請關注公衆號:coderbuff,回覆關鍵字「sword」獲取相關電子書。程序員

 

7.重建二叉樹

題目:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建該二叉樹。例如:輸入前序遍歷序列{1, 2, 4, 7, 3, 5, 6, 8}和中序遍歷序列{4, 7, 2, 1, 5, 3, 8, 6}。

  定義二叉樹節點github

 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 }

二叉樹的遍歷一共分爲:前序遍歷、中序遍歷和後序遍歷編程

前序遍歷遍歷順序爲:根節點->左節點->右節點數組

中序遍歷遍歷順序爲:左節點->根節點->右節點app

後序遍歷遍歷順序爲:左節點->右節點->根節點ide

例如二叉樹:

           1

        /     \

      2       3

     /       /   \

    4    5     6

      \        /

       7   8 

前序遍歷結果爲:一、二、四、七、三、五、六、8

中序遍歷結果爲:四、七、二、一、五、三、八、6

後序遍歷結果爲:七、四、二、五、八、六、三、1

此題給出前序和中序的遍歷結果,要求重建二叉樹。從前序遍歷結果得知,第一個節點必定是根節點。從中序遍歷結果可知,根節點左側必定是其左子樹右側必定是其右子樹。

那麼能夠獲得:

第一次:

根據前序遍歷結果得知,1爲根節點,根據中序遍歷結果得知,四、七、2爲左子樹,五、三、八、6爲右子樹。

第二次:

根據前序遍歷結果得知,2爲節點,根據中序遍歷,四、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        //第二種狀況,節點沒有右子樹。仍然有兩種狀況:1、節點位於父節點的左節點,此時父節點即爲節點中序遍歷的下一個節點;2、節點位於父節點的右節點,此時一直向上查找,直到是它父節點的左節點
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)

相關文章
相關標籤/搜索