本文例子完整源碼地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/swordhtml
前一篇《【好書推薦】《劍指Offer》之軟技能》中提到了面試中的一些軟技能,簡歷的如何寫等。《劍指Offer》在後面的章節中主要是一些編程題並配以講解。就算不面試,這些題多作也無妨。惋惜的是書中是C++實現,我又從新用Java實現了一遍,若是有錯誤或者更好的解法,歡迎提出交流。node
Java不支持賦值運算符重載,略。git
餓漢模式程序員
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》書中,做者強烈建議經過枚舉來實現單例。另外枚舉從底層保證了線程安全,這點感興趣的讀者能夠深刻了解下。儘管枚舉方式實現單例看起來比較「另類」,但從多個方面來看,這是最好且最安全的方式。
題目:給定一個數組,找出數組中重複的數字。
解法一:時間複雜度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的循環。
題目:給定一個二維數組,每一行都按照從左到右依次遞增的順序排序,每一列都按照從上到下依次遞增的順序排序。輸入一個二維數組和一個整數,判斷該整數是否在二維數組中。
解法一:遍歷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};
題目:將字符串中的空格替換爲「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 }
題目:輸入一個鏈表的頭節點,從尾到頭反過來打印出每一個節點的值。
*因爲《劍指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)