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

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

前一篇《【好書推薦】《劍指Offer》之軟技能》中提到了面試中的一些軟技能,簡歷的如何寫等。《劍指Offer》在後面的章節中主要是一些編程題並配以講解。就算不面試,這些題多作也無妨。惋惜的是書中是C++實現,我又從新用Java實現了一遍,若是有錯誤或者更好的解法,歡迎提出交流。node

1.賦值運算符函數

  Java不支持賦值運算符重載,略。git

2.實現Singleton模式

  餓漢模式程序員

 1 /**
 2 * 餓漢模式
 3 * @author OKevin
 4 * @date 2019/5/27
 5 **/
 6 public class Singleton {
 7 
 8    private static Singleton singleton = new Singleton();
 9 
10    private Singleton() {
11 
12    }
13    public static Singleton getInstance() {
14        return singleton;
15    }
16 }

  優勢:線程安全、不易出錯、性能較高。github

  缺點:在類初始化的時候就實例化了一個單例,佔用了內存。面試

  飽漢模式一正則表達式

 1 /**
 2 * 飽漢模式一
 3 * @author OKevin
 4 * @date 2019/5/27
 5 **/
 6 public class Singleton {
 7 
 8    private static Singleton singleton ;
 9 
10    private Singleton() {
11 
12    }
13    public static synchronized Singleton getInstance() {
14        if (singleton == null) {
15            singleton = new Singleton();
16        }
17        return singleton;
18    }
19 }

  飽漢模式二算法

 1 /**
 2 * 飽漢模式二
 3 * @author OKevin
 4 * @date 2019/5/27
 5 **/
 6 public class Singleton {
 7 
 8    private static Singleton singleton ;
 9 
10    private Singleton() {
11 
12    }
13    public static Singleton getInstance() {
14        if (singleton == null) {
15            synchronized (Singleton.class) {
16               if (singleton == null) {
17                  singleton = new Singleton();
18               }
19            }
20        }
21        return singleton;
22    }
23 }

  優勢:線程安全,節省內存,在須要時才實例化對象,比在方法上加鎖性能要好。編程

  缺點:因爲加鎖,性能仍然比不上餓漢模式。數組

  枚舉模式

 1 /**
 2 * 枚舉模式
 3 * @author OKevin
 4 * @date 2019/5/27
 5 **/
 6 public enum Singleton {
 7    INSTANCE;
 8 
 9    Singleton() {
10 
11    }
12 }

  在《Effective Java》書中,做者強烈建議經過枚舉來實現單例。另外枚舉從底層保證了線程安全,這點感興趣的讀者能夠深刻了解下。儘管枚舉方式實現單例看起來比較「另類」,但從多個方面來看,這是最好且最安全的方式。

3.數組中重複的數字

題目:給定一個數組,找出數組中重複的數字。

  解法一:時間複雜度O(n),空間複雜度O(n)

 1 /**
 2 * 找出數組中重複的數字
 3 * @author OKevin
 4 * @date 2019/5/27
 5 **/
 6 public class Solution {
 7 
 8    public void findRepeat(Integer[] array) {
 9        Set<Integer> noRepeat = new HashSet<>();
10        for (Integer number : array) {
11            if (!noRepeat.contains(number)) {
12                noRepeat.add(number);
13            } else {
14                System.out.println("重複數字:" + number);
15            }
16        }
17    }
18 }

  *Set底層實現也是一個Map

  經過Map散列結構,能夠找到數組中重複的數字,此算法時間複雜度爲O(n),空間複雜度爲O(n)(須要額外定義一個Map)。

  解法二:時間複雜度O(n^2),空間複雜度O(1)

 1 /**
 2 * 找出數組中重複的數字
 3 * @author OKevin
 4 * @date 2019/5/27
 5 **/
 6 public class Solution {
 7 
 8    public void findRepeat(Integer[] array) {
 9        for (int i = 0; i < array.length; i++) {
10            Integer num = array[i];
11            for (int j = i + 1; j < array.length; j++) {
12                if (num.equals(array[j])) {
13                    System.out.println("重複數字:" + array[j]);
14                }
15            }
16        }
17    }
18 }

  解法二經過遍歷的方式找到重複的數組元素,解法一相比於解法二是典型的「以空間換取時間」的算法

變形:給定一個長度爲n的數組,數組中的數字值大小範圍在0~n-1,找出數組中重複的數字。

  變形後的題目也可採用上面兩種方法,數字值大小範圍在0~n-1的特色,不借助額外空間(空間複雜度O(1)),遍歷一次(時間複雜度爲O(n))的算法

 1 /**
 2 * 找出數組中重複的數字,數組中的數字值大小範圍在0~n-1
 3 * @author OKevin
 4 * @date 2019/5/27
 5 **/
 6 public class Solution {
 7    public void findRepeat(Integer[] array) {
 8        for (int i = 0; i < array.length; i++) {
 9            while (array[i] != i) {
10                if (array[i].equals(array[array[i]])) {
11                    System.out.println("重複數字:" + array[i]);
12                    break;
13                }
14                Integer temp = array[i];
15                array[i] = array[temp];
16                array[temp] = temp;
17            }
18        }
19    }
20 }

  分析:變形後的題目中條件出現了,數組中的值範圍在數組長度n-1之內,且最小爲0。也就是說,數組中的任意值在做爲數組的下標都不會越界,這是一個潛在的條件。根據這個潛在的條件,咱們能夠把每一個值放到對應的數組下標,使得數組下標=數組值。例如:4,2,1,4,3,3。遍歷第一個值4,此時下標爲0,數組下標≠數組值,比較array[0]與array[4]不相等->交換,4放到了正確的位置上,獲得3,2,1,4,4,3。此時第一個值爲3,數組下標仍然≠數組值,比較array[0]與array[3]不想等->交換,3放到了正確的位置,獲得4,2,1,3,4,3。此時數組下標仍然≠數組值,比較array[0]與array[4]相等,退出當前循環。依次類推,開始數組下標int=1的循環。

4.二維數組中的查找

題目:給定一個二維數組,每一行都按照從左到右依次遞增的順序排序,每一列都按照從上到下依次遞增的順序排序。輸入一個二維數組和一個整數,判斷該整數是否在二維數組中。

  解法一:遍歷n*m大小的二維數組,時間複雜度O(n*m),空間複雜度O(1)

 1 /**
 2 * 二維數組中查找
 3 * @author OKevin
 4 * @date 2019/5/27
 5 **/
 6 public class Solution {
 7 
 8    public boolean isExist(Integer[][] twoArray, Integer target) {
 9        for (int i = 0; i < twoArray.length; i++) {
10            for (int j = 0; j < twoArray[i].length; j++) {
11                if (twoArray[i][j].equals(target)) {
12                    return true;
13                }
14            }
15        }
16        return false;
17    }
18 }

  優勢:簡單暴力。

  缺點:性能不是最優的,時間複雜度較高,沒有充分利用題目中「有序」的條件。

  解法二:時間複雜度O(n+m),空間複雜度O(1)

 1 /**
 2 * 二維數組中查找
 3 * @author OKevin
 4 * @date 2019/5/27
 5 **/
 6 public class Solution {
 7 
 8    public boolean isExist(Integer[][] twoArray, Integer target) {
 9        int x = 0;
10        int y = twoArray[0].length - 1;
11        for (int i = 0; i < twoArray.length-1 + twoArray[0].length-1; i++) {
12            if (twoArray[x][y].equals(target)) {
13                return true;
14            }
15            if (twoArray[x][y] > target) {
16                y--;
17                continue;
18            }
19            if (twoArray[x][y] < target) {
20                x++;
21            }
22        }
23        return false;
24    }
25 }

  分析:經過舉一個實例,找出規律,從右上角開始查找。

  Integer[][] twoArray = new Integer[4][4];

  twoArray[0] = new Integer[]{1, 2, 8, 9};

  twoArray[1] = new Integer[]{2, 4, 9, 12};

  twoArray[2] = new Integer[]{4, 7, 10, 13};

  twoArray[3] = new Integer[]{6, 8, 11, 15};

5.替換空格

題目:將字符串中的空格替換爲「20%」。

  解法一:根據Java提供的replaceAll方法直接替換

 1 /**
 2 * 字符串空格替換
 3 * @author OKevin
 4 * @date 2019/5/28
 5 **/
 6 public class Solution {
 7    public String replaceSpace(String str) {
 8        return str.replaceAll(" ", "20%");
 9    }
10 }

  這種解法沒什麼可說。但能夠了解一下replaceAll的JDK實現。replaceAll在JDK中的實現是根據正則表達式匹配要替換的字符串。

  解法二:利用空間換時間的方式替換

 1 /**
 2 * 字符串空格替換
 3 * @author OKevin
 4 * @date 2019/5/28
 5 **/
 6 public class Solution {
 7    public String replaceSpace(String str, String target) {
 8        StringBuilder sb = new StringBuilder();
 9        for (char c : str.toCharArray()) {
10            if (c == ' ') {
11                sb.append(target);
12                continue;
13            }
14            sb.append(c);
15        }
16        return sb.toString();
17    }
18 }

6.從尾到頭打印鏈表

題目:輸入一個鏈表的頭節點,從尾到頭反過來打印出每一個節點的值。

*因爲《劍指Offer》採用C++編程語言,這題須要咱們先構造出一個節點,模擬出鏈表的結構。

  定義節點

 1 /**
 2 * 鏈表節點定義
 3 * @author OKevin
 4 * @date 2019/5/29
 5 **/
 6 public class Node {
 7    /**
 8     * 指向下一個節點
 9     */
10    private Node next;
11    /**
12     * 表示節點的值域
13     */
14    private Integer data;
15 
16    public Node(){}
17 
18    public Node(Integer data) {
19        this.data = data;
20    }
21    //省略getter/setter方法
22 }

  解法一:利用棧先進後出的特色,遍歷鏈表放入棧中,再從棧推出數據

 1 /**
 2 * 逆向打印鏈表的值
 3 * @author OKevin
 4 * @date 2019/5/29
 5 **/
 6 public class Solution {
 7    public void tailPrint(Node head) {
 8        Stack<Node> stack = new Stack<>();
 9        while (head != null) {
10            stack.push(head);
11            head = head.getNext();
12        }
13        while (!stack.empty()) {
14            System.out.println(stack.pop().getData());
15        }
16    }
17 }

  這種解法「不幸」地藉助了額外的空間。

  解法二:既然使用棧的結構,實際上也就可使用遞歸的方式逆向打印鏈表

 1 /**
 2 * 逆向打印鏈表的值
 3 * @author OKevin
 4 * @date 2019/5/29
 5 **/
 6 public class Solution {
 7    public void tailPrint(Node head) {
 8        if (head.getNext() != null) {
 9            tailPrint(head.getNext());
10        }
11        System.out.println(head.getData());
12    }
13 }

  使用遞歸雖然避免了藉助額外的內存空間,但若是鏈表過長,遞歸過深易致使調用棧溢出。

  測試程序:

 1 /**
 2 * @author OKevin
 3 * @date 2019/5/29
 4 **/
 5 public class Main {
 6    /**
 7     * 1->2->3->4->5
 8     * @param args
 9     */
10    public static void main(String[] args) {
11        Node node1 = new Node(1);
12        Node node2 = new Node(2);
13        Node node3 = new Node(3);
14        Node node4 = new Node(4);
15        Node node5 = new Node(5);
16        node1.setNext(node2);
17        node2.setNext(node3);
18        node3.setNext(node4);
19        node4.setNext(node5);
20 
21        Node head = node1;
22 
23        Solution solution = new Solution();
24        solution.tailPrint(head);
25    }
26 }

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

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

 

 

這是一個能給程序員加buff的公衆號 (CoderBuff)

相關文章
相關標籤/搜索