常見算法合集[java源碼+持續更新中...]

1、引子

本文蒐集從各類資源上搜集高頻面試算法,慢慢填充...每一個算法都親測可運行,原理有註釋。Talk is cheap,show me the code! 走你~java

2、常見算法

2.1 判斷單向鏈表是否有環

 1 package study.algorithm.interview;
 2 
 3 /**
 4  * 判斷單向鏈表是否有環? <p>Q1:判斷是否有環? isCycle </> <p>Q2:環長? count </> <p>Q3: 相遇點? p1.data </> <p>Q4:入環點? 頭結點到入環點距離爲D,入環點到相遇點距離爲S1,相遇點再次回到入環點距離爲S2.
 5  * 相遇時p1走了:D+s1,p2走了D+s1+n(s1+s2),n表示被套的圈數。 因爲P2速度是P1兩倍,D+s1+n(s1+s2)=2(D+s1)--》D=(n-1)(s1+s2)+s2, 即:從相遇點開始,環繞n-1圈,再次回到入環點距離。
 6  * 最終:只須要一個指針從頭結點開始,一個指針從相遇點開始,步長都=1,此次相遇的點即爲入環節點</>
 7  * 時間複雜度:O(n)
 8  * 空間複雜度:O(1),2個指針
 9  *
10  * @author denny
11  * @date 2019/9/4 上午10:07
12  */
13 public class LinkedListIsCycle {
14 
15     /**
16      * 判斷是否有環: 1.有環返回相遇點 2.無環返回空
17      *
18      * @param head 頭結點
19      * @return
20      */
21     private static Node isCycle(Node head) {
22         Node p1 = head;
23         Node p2 = head;
24         // 前進次數
25         int count = 0;
26         while (p2 != null && p2.next != null) {
27             // P1每次前進1步
28             p1 = p1.next;
29             // p2每次前進2步
30             p2 = p2.next.next;
31             count++;
32             if (p1 == p2) {
33                 System.out.println("1.環長=速度差*前進次數=(2-1)*前進次數=count=" + count);
34                 System.out.println("2.相遇點=" + p1.data);
35                 return p1;
36             }
37         }
38         return null;
39     }
40 
41     /**
42      * 獲取環入口
43      *
44      * @param head 頭結點
45      * @return
46      */
47     private static Node getCycleIn(Node head) {
48         // 是否有環
49         Node touch = isCycle(head);
50         Node p1 = head;
51 
52         // 有環,只須要一個指針從頭結點開始,一個指針從相遇點開始,步長都=1,此次相遇的點即爲入環節點
53         if (touch != null) {
54             while (touch != null && p1 != null) {
55                 touch = touch.next;
56                 p1 = p1.next;
57                 if (p1 == touch) {
58                     return p1;
59                 }
60             }
61         }
62         return null;
63     }
64 
65     public static void main(String[] args) {
66         Node node1 = new Node(5);
67         Node node2 = new Node(3);
68         Node node3 = new Node(7);
69         Node node4 = new Node(2);
70         Node node5 = new Node(6);
71         node1.next = node2;
72         node2.next = node3;
73         node3.next = node4;
74         node4.next = node5;
75         node5.next = node2;
76         Node in = getCycleIn(node1);
77         System.out.println(in != null ? "有環返回入口:" + in.data : "無環");
78     }
79 
80     /**
81      * 鏈表節點
82      */
83     private static class Node {
84         int data;
85         Node next;
86 
87         public Node(int data) {
88             this.data = data;
89         }
90     }
91 
92 }

 

2.2 最小棧的實現

 1 package study.algorithm.interview;
 2 
 3 import java.util.Stack;
 4 
 5 /**
 6  * 求最小棧:實現入棧、出棧、取最小值。
 7  * 時間複雜度都是O(1),最壞狀況空間複雜度是O(n)
 8  *
 9  * @author denny
10  * @date 2019/9/4 下午2:37
11  */
12 public class MinStack {
13 
14     private Stack<Integer> mainStack = new Stack<>();
15     private Stack<Integer> minStack = new Stack<>();
16 
17     /**
18      * 入棧
19      *
20      * @param element
21      */
22     private void push(int element) {
23         mainStack.push(element);
24         //若是最小棧爲空,或者新元素<=棧頂最小值,則入最小棧
25         if (minStack.empty() || element <= minStack.peek()) {
26             minStack.push(element);
27         }
28     }
29 
30     /**
31      * 出棧
32      *
33      * @return
34      */
35     private Integer pop() {
36         // 若是主棧棧頂元素和最小棧元素相等,最小棧出棧
37         if (mainStack.peek().equals(minStack.peek())) {
38             minStack.pop();
39         }
40         // 主棧出棧
41         return mainStack.pop();
42     }
43 
44     /**
45      * 取最小值
46      *
47      * @return
48      * @throws Exception
49      */
50     private Integer getMin() {
51         return minStack.peek();
52     }
53 
54     public static void main(String[] args) {
55         MinStack stack = new MinStack();
56         stack.push(3);
57         stack.push(2);
58         stack.push(4);
59         stack.push(1);
60         stack.push(5);
61         //主棧:32415 最小棧:321
62         System.out.println("min=" + stack.getMin());
63         stack.pop();
64         stack.pop();
65         System.out.println("min=" + stack.getMin());
66     }
67 }

 

2.3 求2個整數的最大公約數

  1 package study.algorithm.interview;
  2 
  3 /**
  4  * 求2個整數的最大公約數 <p>1.暴力枚舉法:時間複雜度O(min(a,b))</> <p>2.展轉相除法(歐幾里得算法): O(log(max(a,b))),可是取模運算性能較差</> <p>3.更相減損術:避免了取模運算,但性能不穩定,最壞時間複雜度:O(max(a,b))</>
  5  * <p>4.更相減損術與位移結合:避免了取模運算,算法穩定,時間複雜度O(log(max(a,b)))</>
  6  *
  7  * @author denny
  8  * @date 2019/9/4 下午3:22
  9  */
 10 public class GreatestCommonDivisor {
 11 
 12     /**
 13      * 暴力枚舉法
 14      *
 15      * @param a
 16      * @param b
 17      * @return
 18      */
 19     private static int getGCD(int a, int b) {
 20         int big = a > b ? a : b;
 21         int small = a < b ? a : b;
 22         // 能整除,直接返回
 23         if (big % small == 0) {
 24             return small;
 25         }
 26         // 從較小整數的一半開始~1,試圖找到一個整數i,能被a和b同時整除。
 27         for (int i = small / 2; i > 1; i--) {
 28             if (small % i == 0 && big % i == 0) {
 29                 return i;
 30             }
 31         }
 32         return 1;
 33     }
 34 
 35     /**
 36      * 展轉相除法(歐幾里得算法):兩個正整數a>b,最大公約數=a/b的餘數c和b之間的最大公約數,一直到能夠整除爲止
 37      *
 38      * @param a
 39      * @param b
 40      * @return
 41      */
 42     private static int getGCD2(int a, int b) {
 43         int big = a > b ? a : b;
 44         int small = a < b ? a : b;
 45 
 46         // 能整除,直接返回
 47         if (big % small == 0) {
 48             return small;
 49         }
 50 
 51         return getGCD2(big % small, small);
 52     }
 53 
 54     /**
 55      * 更相減損術:兩個正整數a>b,最大公約數=a-b和b之間的最大公約數,一直到兩個數相等爲止。
 56      *
 57      * @param a
 58      * @param b
 59      * @return
 60      */
 61     private static int getGCD3(int a, int b) {
 62         if (a == b) {
 63             return a;
 64         }
 65 
 66         int big = a > b ? a : b;
 67         int small = a < b ? a : b;
 68 
 69         return getGCD3(big - small, small);
 70     }
 71 
 72     /**
 73      * 更相減損術結合位移
 74      *
 75      * @param a
 76      * @param b
 77      * @return
 78      */
 79     private static int getGCD4(int a, int b) {
 80         if (a == b) {
 81             return a;
 82         }
 83         // 都是偶數,gcd(a,b)=2*gcd(a/2,b/2)=gcd(a>>1,b>>1)<<1
 84         if ((a & 1) == 0 && (b & 1) == 0) {
 85             return getGCD4(a >> 1, b >> 1) << 1;
 86             // a是偶數,b是奇數,gcd(a,b)=gcd(a/2,b)=gcd(a>>1,b)
 87         } else if ((a & 1) == 0 && (b & 1) == 1) {
 88             return getGCD4(a >> 1, b);
 89             // a是奇數,b是偶數
 90         } else if ((a & 1) == 1 && (b & 1) == 0) {
 91             return getGCD4(a, b >> 1);
 92             // 都是奇數
 93         } else {
 94             int big = a > b ? a : b;
 95             int small = a < b ? a : b;
 96             return getGCD4(big - small, small);
 97         }
 98 
 99     }
100 
101     public static void main(String[] args) {
102         System.out.println("最大公約數=" + getGCD4(99, 21));
103     }
104 }

 

2.4 判斷是不是2的整數次冪

 1 package study.algorithm.interview;
 2 
 3 /**
 4  * 判斷是不是2的整數次冪:時間複雜度
 5  * 時間複雜度是O(1)
 6  *
 7  * @author denny
 8  * @date 2019/9/4 下午5:18
 9  */
10 public class PowerOf2 {
11 
12     /**
13      * 判斷是不是2的整數次冪: 2的整數次冪轉換成二進制(1+n個0)& 二進制-1(n個1)=0
14      * @param num
15      * @param a
16      * @return boolean
17      * @author denny
18      * @date 2019/9/5 上午11:14
19      */
20     private static boolean isPowerOf2(int num, int a) {
21         return (num & (num - 1)) == 0;
22     }
23 
24     public static void main(String[] args) {
25         System.out.println("是否2的整數次冪=" + isPowerOf2(16, 1));
26     }
27 }

 

2.5 無序數組排序後的最大相鄰差

 1 package study.algorithm.interview;
 2 
 3 /**
 4  * 無序數組排序後的最大相鄰差: 使用桶排序思想,每一個桶元素遍歷一遍便可,不須要再排序,
 5  * 時間複雜度O(n)
 6  *
 7  * @author denny
 8  * @date 2019/9/4 下午5:38
 9  */
10 public class MaxSortedDistance {
11 
12     private static class Bucket {
13         Integer max;
14         Integer min;
15     }
16 
17     private static int getMaxSortedDistance(int[] array) {
18         //1.求最大值最小值
19         int max = array[0];
20         int min = array[0];
21         for (int i = 0; i < array.length; i++) {
22             if (array[i] > max) {
23                 max = array[i];
24             }
25             if (array[i] < min) {
26                 min = array[i];
27             }
28         }
29         int d = max - min;
30         // 若是max=min,全部元素都相等,直接返回0
31         if (d == 0) {
32             return 0;
33         }
34 
35         // 2. 初始化桶
36         int bucketNum = array.length;
37         Bucket[] buckets = new Bucket[bucketNum];
38         for (int i = 0; i < bucketNum; i++) {
39             buckets[i] = new Bucket();
40         }
41         // 3.遍歷原始數組,肯定每一個桶的最大值最小值
42         for (int i = 0; i < array.length; i++) {
43             // 桶下標=當前元素偏移量/跨度  跨度=總偏移量/桶數-1
44             int index = (array[i] - min) / (d / (bucketNum - 1));
45             if (buckets[index].min == null || buckets[index].min > array[i]) {
46                 buckets[index].min = array[i];
47             }
48             if (buckets[index].max == null || buckets[index].max > array[i]) {
49                 buckets[index].max = array[i];
50             }
51         }
52         // 4.遍歷桶,找到最大差值
53         int leftMax = buckets[0].max;
54         int maxDistance = 0;
55         // 從第二個桶開始計算
56         for (int i = 1; i < buckets.length; i++) {
57             if (buckets[i].min == null) {
58                 continue;
59             }
60             // 桶最大差值=右邊最小值-左邊最大值
61             if (buckets[i].min - leftMax > maxDistance) {
62                 maxDistance = buckets[i].min - leftMax;
63             }
64             // 更新左邊最大值爲當前桶max
65             leftMax = buckets[i].max;
66         }
67         return maxDistance;
68     }
69 
70     public static void main(String[] args) {
71         int[] array = new int[] {3, 4, 5, 9, 5, 6, 8, 1, 2};
72         System.out.println(getMaxSortedDistance(array));
73     }
74 }

2.6 棧實現隊列

 1 package study.algorithm.interview;
 2 
 3 import java.util.Stack;
 4 
 5 /**
 6  * 棧實現隊列:
 7  * 時間複雜度:入隊O(1) 出隊O(1)(均攤時間複雜度)
 8  *
 9  * @author denny
10  * @date 2019/9/5 上午11:14
11  */
12 public class StackQueue {
13     // 入隊
14     private Stack<Integer> stackIn = new Stack<>();
15     // 出隊
16     private Stack<Integer> stackOut = new Stack<>();
17 
18     /**
19      * 入隊:直接入棧
20      *
21      * @param element
22      */
23     private void enQueue(int element) {
24         stackIn.push(element);
25     }
26 
27     /**
28      * 出隊
29      *
30      * @return
31      */
32     private Integer deQueue() {
33         // 出隊爲空
34         if (stackOut.isEmpty()) {
35             // 若是入隊爲空,直接返回空
36             if (stackIn.isEmpty()) {
37                 return null;
38             }
39             // 入隊不爲空,IN元素所有轉移到OUT
40             transfer();
41         }
42         // 出隊不爲空,直接彈出
43         return stackOut.pop();
44     }
45 
46     /**
47      * 入隊元素轉到出隊
48      */
49     private void transfer() {
50         while (!stackIn.isEmpty()) {
51             stackOut.push(stackIn.pop());
52         }
53     }
54 
55     public static void main(String[] args) {
56         StackQueue stackQueue = new StackQueue();
57         stackQueue.enQueue(1);
58         stackQueue.enQueue(2);
59         stackQueue.enQueue(3);
60         System.out.println("出隊:" + stackQueue.deQueue());
61         System.out.println("出隊:" + stackQueue.deQueue());
62         stackQueue.enQueue(4);
63         System.out.println("出隊:" + stackQueue.deQueue());
64         System.out.println("出隊:" + stackQueue.deQueue());
65 
66     }
67 
68 }

2.7 尋找全排列的下一個數

  1 package study.algorithm.interview;
  2 
  3 import com.alibaba.fastjson.JSONObject;
  4 
  5 import java.util.Arrays;
  6 
  7 /**
  8  * 尋找全排列的下一個數,又叫字典序算法,時間複雜度爲O(n) 全排列:12345->54321
  9  * 核心原理:
 10  * 1.最後2位交換行不行?不行再最後3位.....從右往左找相鄰array[index]>array[index-1] ,
 11  * 2.index-1和逆序列,從右往左中第一個比它大的值,交換 由於越往左邊,交換後數越大,只有第一個才知足相鄰。
 12  * 例如 12345-》12354   12354-第一步找到54數列,交換3和4-》12453--》12435
 13  * 12765->15762->15267
 14  * 時間複雜度:O(n)
 15  *
 16  * @author denny
 17  * @date 2019/9/5 下午2:18
 18  */
 19 public class FindNextSortedNumber {
 20 
 21     private static int[] findNextSortedNumber(int[] numbers) {
 22         // 1.找到置換邊界:從後向前查看逆序區域,找到逆序區域的第一位
 23         int index = findTransferPoint(numbers);
 24         System.out.println("index=" + index);
 25         // 整個數組逆序,沒有更大的數了
 26         if (index == 0) {
 27             return null;
 28         }
 29 
 30         // copy一個新的數組,避免修改入參
 31         int[] numbersCopy = Arrays.copyOf(numbers, numbers.length);
 32         // 2.把逆序區域的前一位和逆序區域中大於它的最小數交換位置
 33         exchangHead(numbersCopy, index);
 34 
 35         // 3.把原來的逆序轉爲順序
 36         reverse(numbersCopy, index);
 37         return numbersCopy;
 38     }
 39 
 40     /**
 41      * 找到置換邊界
 42      *
 43      * @param numbers
 44      * @return
 45      */
 46     private static int findTransferPoint(int[] numbers) {
 47         for (int i = numbers.length - 1; i > 0; i--) {
 48             if (numbers[i] > numbers[i - 1]) {
 49                 return i;
 50             }
 51         }
 52         return 0;
 53     }
 54 
 55     /**
 56      * 把逆序區域的前一位和逆序區域中大於它的最小數交換位置
 57      *
 58      * @param numbers
 59      * @param index
 60      * @return
 61      */
 62     private static int[] exchangHead(int[] numbers, int index) {
 63         // 逆序區域前一位
 64         int head = numbers[index - 1];
 65         // 從後往前遍歷
 66         for (int i = numbers.length - 1; i > 0; i--) {
 67             // 找到第一個大於head的數,和head交換。由於是逆序區域,第一個數就是最小數,因此找到第一個大於head的數,就是比head大的數中的最小數
 68             if (head < numbers[i]) {
 69                 numbers[index - 1] = numbers[i];
 70                 numbers[i] = head;
 71                 break;
 72             }
 73         }
 74         return numbers;
 75     }
 76 
 77     /**
 78      * 逆序
 79      *
 80      * @param num
 81      * @param index
 82      * @return
 83      */
 84     private static int[] reverse(int[] num, int index) {
 85         for (int i = index, j = num.length - 1; i < j; i++, j--) {
 86             int temp = num[i];
 87             num[i] = num[j];
 88             num[j] = temp;
 89         }
 90         return num;
 91     }
 92 
 93     public static void main(String[] args) {
 94         int[] numbers = {1, 2, 3, 5, 4};
 95 
 96         numbers = findNextSortedNumber(numbers);
 97         System.out.println(JSONObject.toJSONString(numbers));
 98 
 99     }
100 }

2.8 刪除整數的k個數字,使得留下的數字最小

 1 package study.algorithm.interview;
 2 
 3 /**
 4  * 刪除整數的k個數字,使得留下的數字最小
 5  * 時間複雜度:O(n)
 6  *
 7  * @author denny
 8  * @date 2019/9/5 下午4:43
 9  */
10 public class removeKDigits {
11 
12     /**
13      * 刪除整數的k個數字,使得留下的數字最小
14      *
15      * @param num
16      * @param k
17      * @return
18      */
19     private static String removeKDigits(String num, int k) {
20         // 新長度
21         int newLength = num.length() - k;
22         // 建立棧,接收全部數字
23         char[] stack = new char[num.length()];
24         int top = 0;
25         // 遍歷,一開始先入棧第一個數字。第一輪循環先給stack入棧一個數,且top++,日後循環,top-1纔是棧頂
26         for (int i = 0; i < num.length(); ++i) {
27             // 當前數字
28             char c = num.charAt(i);
29             // 當棧頂數字 > 當前數字時,棧頂數字出棧,只要沒刪除夠K個就一直往左邊刪除
30             while (top > 0 && stack[top - 1] > c && k > 0) {
31                 // 這裏top-1,就是出棧,忽略top
32                 top -= 1;
33                 k -= 1;
34             }
35             // 後一個數字入棧
36             stack[top++] = c;
37         }
38         // 找到棧中第一個非0數字的位置,以此構建新的字符串0000123->123
39         int offset = 0;
40         while (offset < newLength && stack[offset] == '0') {
41             offset++;
42         }
43         return offset == newLength ? "0" : new String(stack, offset, newLength - offset);
44     }
45 
46     public static void main(String[] args) {
47         System.out.println(removeKDigits("1593212", 3));
48         System.out.println(removeKDigits("10", 2));
49     }
50 }

2.9 大整數相加求和

 1 package study.algorithm.interview;
 2 
 3 /**
 4  * 大整數相加求和:可優化點:int -21-21億,9位數妥妥的計算。拆分大整數每9位數一個元素,分別求和。效率可極大提高。
 5  * 時間複雜度:O(n)
 6  *
 7  * @author denny
 8  * @date 2019/9/5 下午5:50
 9  */
10 public class BigNumberSum {
11     private static String bigNumberSum(String bigA, String bigB) {
12         // 1.把2個大整數用數組逆序存儲,數組長度等於較大整數位數+1
13         int maxLength = bigA.length() > bigB.length() ? bigA.length() : bigB.length();
14         int[] arrayA = new int[maxLength + 1];
15         for (int i = 0; i < bigA.length(); i++) {
16             arrayA[i] = bigA.charAt(bigA.length() - 1 - i) - '0';
17         }
18         int[] arrayB = new int[maxLength + 1];
19         for (int i = 0; i < bigB.length(); i++) {
20             arrayB[i] = bigB.charAt(bigB.length() - 1 - i) - '0';
21         }
22         // 2. 構建result數組
23         int[] result = new int[maxLength + 1];
24 
25         // 3. 遍歷數組,按位相加
26         for (int i = 0; i < result.length; i++) {
27             int temp = result[i];
28             temp += arrayA[i];
29             temp += arrayB[i];
30             //是否進位
31             if (temp >= 10) {
32                 temp = temp - 10;
33                 result[i + 1] = 1;
34             }
35             result[i] = temp;
36         }
37 
38         //4.轉成數組
39         StringBuilder stringBuilder = new StringBuilder();
40         // 是否找到大整數的最高有效位
41         boolean findFirst = false;
42         // 倒序遍歷,即從最高位開始找非零數,找到一個就能夠開始append了
43         for (int i = result.length - 1; i >= 0; i--) {
44             if (!findFirst) {
45                 if (result[i] == 0) {
46                     continue;
47                 }
48                 findFirst = true;
49             }
50             stringBuilder.append(result[i]);
51         }
52         return stringBuilder.toString();
53 
54     }
55 
56     public static void main(String[] args) {
57         System.out.println(bigNumberSum("4534647452342423", "986568568789664"));
58     }
59 }

2.10 求解金礦問題

 1 package study.algorithm.interview;
 2 
 3 /**
 4  * 求金礦最優收益(動態規劃)
 5  * 時間複雜度:O(n*w)n爲人數 w爲金礦數
 6  * 空間複雜度:O(n)
 7  *
 8  * @author denny
 9  * @date 2019/9/6 下午4:21
10  */
11 public class GetMaxGold {
12 
13     /**
14      * 求金礦最優收益
15      *
16      * @param w 工人數量
17      * @param p 金礦開採所需的工人數量
18      * @param g 金礦金子儲藏量
19      * @return
20      */
21     private static int getMaxGold(int w, int[] p, int[] g) {
22         // 構造數組
23         int[] results = new int[w + 1];
24         // 遍歷全部金礦
25         for (int i = 1; i < g.length; i++) {
26             // 遍歷人數:w->1
27             for (int j = w; j >= 1; j--) {
28                 // 若是人數夠這個金礦所需的人數,i-1是由於下標從0開始
29                 if (j >= p[i - 1]) {
30                     // 當前人數,最大收益=Max(採用當前礦,不採用當前礦)
31                     results[j] = Math.max(results[j], results[j - p[i - 1]] + g[i - 1]);
32                 }
33             }
34         }
35         // 返回最後一個格子的值
36         return results[w];
37     }
38 
39     public static void main(String[] args) {
40         System.out.println(getMaxGold(10, new int[] {5, 5, 3, 4, 3}, new int[] {400, 500, 200, 300, 350}));
41     }
42 }

2.11 尋找缺失的整數

 1 package study.algorithm.interview;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 /**
 7  * 無序數組裏有99個不重複整數,1-100,缺乏一個。如何找到這個缺失的整數?
 8  *
 9  * @author denny
10  * @date 2019/9/9 上午11:05
11  */
12 public class FindLostNum {
13     /**
14      * 直接求和而後遍歷減去所有元素便可 時間複雜度:O(1) 空間複雜度:
15      *
16      * @param array
17      * @return
18      */
19     private static int findLostNum(Integer[] array) {
20         // 1-100求和
21         int sum = ((1 + 100) * 100) / 2;
22         for (int a : array) {
23             sum -= a;
24         }
25         return sum;
26     }
27 
28     /**
29      * 一個無序數組裏有若干個正整數,範圍是1~100,其中98個整數都出現了偶數次。只有2個整數出現了奇數次,求奇數次整數? 利用異或運算的"相同爲0,不一樣爲1",出現偶數次的偶數異或變0,最後只有奇數次的整數留下。 時間複雜度:O(n) 空間複雜度:O(1)
30      *
31      * @param array
32      * @return
33      */
34     private static int[] findLostNum2(Integer[] array) {
35         // 存儲2個出現奇數次的整數
36         int result[] = new int[2];
37         // 第一次進行總體異或運算
38         int xorResult = 0;
39         for (int i = 0; i < array.length; i++) {
40             xorResult ^= array[i];
41         }
42         //肯定2個整數的不一樣位,以此來作分組
43         int separtor = 1;
44         //xorResult=0000 0110B ,A^B=>倒數第二位=1即,倒數第二位不一樣。一個是0一個是1.=》原數組可拆分紅2個,一組倒數第二位是0,一組是1。& 01 、10 倒數第二位爲1,separtor左移一位
45         while (0 == (xorResult & separtor)) {
46             separtor <<= 1;
47         }
48         //第二次分組進行異或運算
49         for (int i = 0; i < array.length; i++) {
50             // 按位與 10 ==0一組,一直異或計算,就是那個奇數次整數(由於偶數次整數,異或後=1相互抵消掉了)
51             if (0 == (array[i] & separtor)) {
52                 result[0] ^= array[i];
53                 // 按位與 10 !=0另外一組,一直異或計算,就是那個奇數次整數
54             } else {
55                 result[1] ^= array[i];
56             }
57         }
58         return result;
59     }
60 
61     public static void main(String[] args) {
62         List<Integer> list = new ArrayList<>();
63         // 除了85,其它賦值
64         for (int i = 0; i < 100; i++) {
65             list.add(i + 1);
66         }
67         list.remove(10);
68         System.out.println("缺失的數=" + findLostNum(list.toArray(new Integer[99])));
69 
70         Integer[] array = new Integer[] {4, 1, 2, 2, 5, 1, 4, 3};
71         int[] result = findLostNum2(array);
72         System.out.println(result[0] + "," + result[1]);
73     }
74 }

2.12 位圖Bitmap的實現

 1 package study.algorithm.interview;
 2 
 3 /**
 4  * 實現一個位圖BitMap(海量數據查找、去重存儲)
 5  *
 6  * @author denny
 7  * @date 2019/9/9 下午4:04
 8  */
 9 public class MyBitMap {
10     // 64位二進制數據
11     private long[] words;
12     // Bitmap的位數
13     private int size;
14 
15     public MyBitMap(int size) {
16         this.size = size;
17         this.words = new long[getWordIndex(size - 1) + 1];
18     }
19 
20     /**
21      * 判斷某一位的狀態
22      *
23      * @param index
24      * @return
25      */
26     public boolean getBit(int index) {
27         if (index < 0 || index > size - 1) {
28             throw new IndexOutOfBoundsException("index 無效!");
29         }
30         int wordIndex = getWordIndex(index);
31         // 位與:都是1纔是1,不然是0.   index對應值爲1返回true
32         return (words[wordIndex] & (1L << index)) != 0;
33     }
34 
35     /**
36      * 設置bitmap 在index處爲1(true)
37      *
38      * @param index
39      */
40     public void setBit(int index) {
41         if (index < 0 || index > size - 1) {
42             throw new IndexOutOfBoundsException("index 無效!");
43         }
44         int wordIndex = getWordIndex(index);
45         // 位或:只要有一個1就是1,2個0纔是0 ,由於1L << index就是1,因此|=就是在index位置,賦值1
46         words[wordIndex] |= (1L << index);
47     }
48 
49     /**
50      * 定位Bitmap某一位對應的word
51      *
52      * @param index
53      * @return
54      */
55     private int getWordIndex(int index) {
56         // 右移6位即除以64
57         return index >> 6;
58     }
59 
60     public static void main(String[] args) {
61         MyBitMap bitMap = new MyBitMap(128);
62         bitMap.setBit(126);
63         bitMap.setBit(88);
64         System.out.println(bitMap.getBit(126));
65         System.out.println(bitMap.getBit(88));
66     }
67 
68 }

2.13 LRU算法的實現

  1 package study.algorithm.interview;
  2 
  3 import study.algorithm.base.Node;
  4 
  5 import java.util.HashMap;
  6 
  7 /**
  8  * LRU(Least Recently Used)最近最少使用算法(非線程安全) head(最少使用)<-->*<-->*<-->end(最近使用) 注:JDK中LinkedHashMap實現了LRU哈希鏈表,構造方法:LinkedHashMap(int
  9  * initialCapacity容量,float
 10  * loadFactor負載,boolean accessOrder是否LRU訪問順序,true表明LRU)
 11  *
 12  * @author denny
 13  * @date 2019/9/9 下午6:01
 14  */
 15 public class LRUCache {
 16 
 17     // 雙向鏈表頭節點(最後時間)
 18     private Node head;
 19     // 雙向鏈表尾節點(最先時間)
 20     private Node end;
 21     // 緩存儲存上限
 22     private int limit;
 23     // 無序key-value映射。只有put操做纔會寫hashMap
 24     private HashMap<String, Node> hashMap;
 25 
 26     public LRUCache(int limit) {
 27         this.limit = limit;
 28         hashMap = new HashMap<>();
 29     }
 30 
 31     /**
 32      * 插入
 33      *
 34      * @param key
 35      * @param value
 36      */
 37     public void put(String key, String value) {
 38         Node node = hashMap.get(key);
 39         // key 不存在,插入新節點
 40         if (node == null) {
 41             // 達到容量上限
 42             if (hashMap.size() >= limit) {
 43                 // 移除頭結點
 44                 String oldKey = removeNode(head);
 45                 //同步hashMap
 46                 hashMap.remove(oldKey);
 47             }
 48             // 構造節點
 49             node = new Node(key, value);
 50             // 添加到尾節點
 51             addNodeToEnd(node);
 52             // 同步hashmap
 53             hashMap.put(key, node);
 54         } else {
 55             // key存在,刷新key-value
 56             node.value = value;
 57             // 刷新被訪問節點的位置
 58             refreshNode(node);
 59         }
 60     }
 61 
 62     /**
 63      * 獲取
 64      *
 65      * @param key
 66      * @return
 67      */
 68     public String get(String key) {
 69         Node node = hashMap.get(key);
 70         if (node == null) {
 71             return null;
 72         }
 73         //刷新節點(提高該節點爲尾結點,即最新使用節點)
 74         refreshNode(node);
 75         return node.value;
 76     }
 77 
 78     /**
 79      * 刷新被訪問節點的位置
 80      *
 81      * @param node
 82      */
 83     private void refreshNode(Node node) {
 84         // 若是訪問的是尾結點,則無須移動節點
 85         if (node == end) {
 86             return;
 87         }
 88         //移除節點
 89         removeNode(node);
 90         //尾部插入節點,表明最新使用
 91         addNodeToEnd(node);
 92     }
 93 
 94     /**
 95      * 移除節點
 96      *
 97      * @param node
 98      * @return
 99      */
100     private String removeNode(Node node) {
101         // 若是就一個節點,把頭尾節點置空
102         if (node == head && node == end) {
103             head = null;
104             end = null;
105         } else if (node == end) {
106             // 移除尾結點
107             end = end.next;
108             end.next = null;
109         } else if (node == head) {
110             //移除頭結點
111             head = head.next;
112             head.pre = null;
113         } else {
114             // 移除中間節點
115             node.pre.next = node.next;
116             node.next.pre = node.pre;
117         }
118         return node.key;
119     }
120 
121     /**
122      * 尾部插入節點
123      *
124      * @param node
125      */
126     private void addNodeToEnd(Node node) {
127         if (head == null && end == null) {
128             head = node;
129             end = node;
130         }
131         // 添加節點
132         end.next = node;
133         // pre=以前的end
134         node.pre = end;
135         // node next不存在
136         node.next = null;
137         // 新節點爲尾結點
138         end = node;
139     }
140 
141     public static void printLRUCache(LRUCache lruCache) {
142         for (Node node = lruCache.head; node != null; node = node.next) {
143             System.out.println("key=" + node.key + ",value=" + node.value);
144         }
145         System.out.println("===========================");
146     }
147 
148     public static void main(String[] args) {
149         // 構造一個容量爲5的LRU緩存
150         LRUCache lruCache = new LRUCache(5);
151         lruCache.put("001", "value1");
152         lruCache.put("002", "value2");
153         lruCache.put("003", "value3");
154         lruCache.put("004", "value4");
155         lruCache.put("005", "value5");
156         // 打印
157         System.out.println("1. 插入 5個節點");
158         printLRUCache(lruCache);
159 
160         // 002到尾結點
161         lruCache.get("002");
162         // 打印
163         System.out.println("2. 002到尾結點");
164         printLRUCache(lruCache);
165 
166         // 004到尾結點,且value更新
167         lruCache.put("004", "value4更新");
168         // 打印
169         System.out.println("3. 004到尾結點,且value更新");
170         printLRUCache(lruCache);
171 
172         // 001倍移除,006在尾結點
173         lruCache.put("006", "value6");
174         // 打印
175         System.out.println("4. 超長,001倍移除,006在尾結點");
176         printLRUCache(lruCache);
177     }
178 
179 }

2.14 A*尋路算法

  1 package study.algorithm.interview;
  2 
  3 import java.util.ArrayList;
  4 import java.util.List;
  5 
  6 /**
  7  *  A*尋路算法
  8  * @author denny
  9  * @date 2019/9/10 下午5:28
 10  */
 11 public class AStarSearch {
 12 
 13     /**
 14      * 迷宮地圖,1表明障礙物不可走 0表明可走
 15      */
 16     private static final int[][] MAZE={
 17         {0,0,0,0,0,0,0},
 18         {0,0,0,1,0,0,0},
 19         {0,0,0,1,0,0,0},
 20         {0,0,0,1,0,0,0},
 21         {0,0,0,0,0,0,0}
 22     };
 23 
 24     static class Grid{
 25         // X軸座標
 26         public int x;
 27         // Y軸座標
 28         public int y;
 29         // 從起點走到當前格子的成本(一開始,當前格子=起點,日後走一步,下一個格子就是當前格子)
 30         public int g;
 31         // 在不考慮障礙狀況下,從當前格子走到目標格子的步數
 32         public int h;
 33         // f=g+h,從起點到當前格子,再從當前格子走到目標格子的總步數
 34         public int f;
 35         public Grid parent;
 36 
 37         public Grid(int x, int y) {
 38             this.x = x;
 39             this.y = y;
 40         }
 41 
 42         public void initGrid(Grid parent,Grid end){
 43             //標記父格子,用來記錄軌跡
 44             this.parent=parent;
 45             if(parent!=null){
 46                 this.g = parent.g+1;
 47             }else {
 48                 this.g=1;
 49             }
 50             this.h = Math.abs(this.x-end.x)+Math.abs(this.y-end.y);
 51             this.f = this.g+this.h;
 52         }
 53     }
 54 
 55     /**
 56      * A*尋路主邏輯
 57      * @param start 起點
 58      * @param end 終點
 59      * @return
 60      */
 61     public static Grid aStarSearch(Grid start,Grid end){
 62         // 可走list
 63         List<Grid> openList = new ArrayList<>();
 64         // 已走list
 65         List<Grid> closeList = new ArrayList<>();
 66         // 把起點加入openList
 67         openList.add(start);
 68         // 可走list不爲空,一直循環
 69         while(openList.size()>0){
 70             // 在openList中查找F值最小的節點,將其做爲當前格子節點
 71             Grid currentGrid = findMinGird(openList);
 72             // 將選中格子從openList中移除
 73             openList.remove(currentGrid);
 74             // 將選中格子塞進closeList
 75             closeList.add(currentGrid);
 76             // 找到全部鄰近節點
 77             List<Grid> neighbors = findNeighbors(currentGrid,openList,closeList);
 78             for(Grid grid:neighbors){
 79                 // 鄰近節點不在可走list中,標記"父節點",GHF,並放入可走格子list
 80                 if(!openList.contains(grid)){
 81                     grid.initGrid(currentGrid,end);
 82                     openList.add(grid);
 83                 }
 84             }
 85             // 若是終點在openList中,直接返回終點格子
 86             for(Grid grid:openList){
 87                 if((grid.x==end.x) && (grid.y==end.y)){
 88                     return grid;
 89                 }
 90             }
 91         }
 92         // 找不到路徑,終點不可達
 93         return null;
 94     }
 95 
 96     /**
 97      * 求當前可走格子的最小f的格子
 98      * @param openList
 99      * @return
100      */
101     private static Grid findMinGird(List<Grid> openList){
102         Grid tempGrid = openList.get(0);
103         // 遍歷求最小f的Grid
104         for (Grid grid : openList){
105             if(grid.f< tempGrid.f){
106                 tempGrid =grid;
107             }
108         }
109         return tempGrid;
110     }
111 
112     /**
113      * 查找能夠走的格子集合
114      * @param grid 當前格子
115      * @param openList 可走list
116      * @param closeList 已走list
117      * @return
118      */
119     private static ArrayList<Grid> findNeighbors(Grid grid,List<Grid> openList,List<Grid> closeList){
120         ArrayList<Grid> grids = new ArrayList<>();
121         if(isValidGrid(grid.x,grid.y-1,openList,closeList)){
122             grids.add(new Grid(grid.x,grid.y-1));
123         }
124         if(isValidGrid(grid.x,grid.y+1,openList,closeList)){
125             grids.add(new Grid(grid.x,grid.y+1));
126         }
127         if(isValidGrid(grid.x-1,grid.y,openList,closeList)){
128             grids.add(new Grid(grid.x-1,grid.y));
129         }
130         if(isValidGrid(grid.x+1,grid.y,openList,closeList)){
131             grids.add(new Grid(grid.x+1,grid.y));
132         }
133         return grids;
134     }
135 
136     /**
137      * 非法校驗
138      * @param x
139      * @param y
140      * @param openList
141      * @param closeList
142      * @return
143      */
144     private static boolean isValidGrid(int x,int y,List<Grid> openList,List<Grid> closeList){
145         // 座標有效校驗
146         if(x<0 || x>=MAZE.length || y<0 || y>=MAZE[0].length){
147             return false;
148         }
149         // 存在障礙物,非法
150         if(MAZE[x][y]==1){
151             return false;
152         }
153         // 已經在openList中,已判斷過
154         if(containGrid(openList,x,y)){
155             return false;
156         }
157         // 已經在closeList中,已走過
158         if(containGrid(closeList,x,y)){
159             return false;
160         }
161         return true;
162     }
163 
164     /**
165      * 是否包含座標對應的格子
166      * @param grids
167      * @param x
168      * @param y
169      * @return
170      */
171     private static boolean containGrid(List<Grid> grids,int x,int y){
172         for(Grid grid:grids){
173             if((grid.x==x) && (grid.y==y)){
174                 return true;
175             }
176         }
177         return false;
178     }
179 
180     public static void main(String[] args) {
181         Grid start = new Grid(2,1);
182         Grid end = new Grid(2,5);
183         // 搜索迷宮終點
184         Grid resultGrid = aStarSearch(start,end);
185         //回溯迷宮路徑
186         List<Grid> path = new ArrayList<>();
187         // 追溯parent
188         while(resultGrid!=null){
189             path.add(new Grid(resultGrid.x,resultGrid.y));
190             resultGrid =resultGrid.parent;
191         }
192         // 行遍歷
193         for(int i=0;i<MAZE.length;i++){
194             // 列遍歷
195             for(int j=0;j<MAZE[0].length;j++){
196                 // 路徑打印
197                 if(containGrid(path,i,j)){
198                     System.out.print("*,");
199                 } else {
200                     System.out.print(MAZE[i][j]+",");
201                 }
202             }
203             System.out.println();
204         }
205 
206 
207 
208     }
209 
210 
211 }

 2.15 紅包拆分算法node

  1 package study.algorithm.interview;
  2 
  3 import java.math.BigDecimal;
  4 import java.util.ArrayList;
  5 import java.util.Collections;
  6 import java.util.List;
  7 import java.util.Random;
  8 
  9 /**
 10  * 紅包拆分算法
 11  * 要求:
 12  * 1.每一個人至少搶到一分錢。
 13  * 2.全部人搶到金額之和等於紅包金額,不能超過,也不能少於。
 14  * 3.要保證全部人搶到金額的概率相等。
 15  *
 16  * @author denny
 17  * @date 2019/9/11 上午10:37
 18  */
 19 public class RedPackage {
 20 
 21     /**
 22      * 拆分成包:二分均值法(每次搶紅包的平均值是相等的)
 23      * 注:除最後一個紅包外,其它紅包<剩餘人均金額的2倍,不算徹底自由隨機搶紅包
 24      * @param totalAMount    總金額,單位:分
 25      * @param totalPeopleNum 總人數
 26      * @return
 27      */
 28     public static List<Integer> divideRedPackage(Integer totalAMount, Integer totalPeopleNum) {
 29         List<Integer> amountList = new ArrayList<>();
 30         // 餘額
 31         Integer restAmount = totalAMount;
 32         // 沒搶人數
 33         Integer restPeopleNum = totalPeopleNum;
 34         Random random = new Random();
 35         // 遍歷totalPeopleNum-1遍,最後一我的直接把餘下的紅包都給他
 36         for (int i = 0; i < totalPeopleNum - 1; i++) {
 37             // [1,剩餘人均金額的2倍-1]
 38             int amount = random.nextInt(restAmount / restPeopleNum * 2 - 1) + 1;
 39             restAmount -= amount;
 40             restPeopleNum--;
 41             amountList.add(amount);
 42         }
 43         // 最後一我的,餘下的紅包都給他
 44         amountList.add(restAmount);
 45         return amountList;
 46     }
 47 
 48     /**
 49      * 線段切割法:紅包金額隨機性好 1.當隨機切割點出現重複時,再繼續隨機一個
 50      *
 51      * @param totalAmount
 52      * @param totalPeopleNum
 53      * @return
 54      */
 55     public static List<Integer> divideRedPackage2(Integer totalAmount, Integer totalPeopleNum) {
 56         // 切割點list
 57         List<Integer> indexList = new ArrayList<>();
 58         // 紅包list
 59         List<Integer> amountList = new ArrayList<>();
 60         Random random = new Random();
 61 
 62         // 構造n-1個切割點
 63         while (indexList.size() <= totalPeopleNum - 1) {
 64             // 總金額隨機+1分
 65             int i = random.nextInt(totalAmount - 1) + 1;
 66             // i不在list中,非重複切割添加到集合
 67             if (indexList.indexOf(i) < 0) {
 68                 indexList.add(i);
 69             }
 70         }
 71         // 排序.升序排列,從小到大,恰好n-1個切割點把總金額切割成n份。
 72         Collections.sort(indexList);
 73         // 上一次index
 74         int flag = 0;
 75         // 紅包之和
 76         int fl = 0;
 77         // 遍歷所有切割點
 78         for (int i = 0; i < indexList.size(); i++) {
 79             // 當前紅包=index-上一次index
 80             int temp = indexList.get(i) - flag;
 81             // 記錄index
 82             flag = indexList.get(i);
 83             // 求和
 84             fl += temp;
 85             // 當前紅包添加進list
 86             amountList.add(temp);
 87         }
 88         //最後一個紅包=總金額-已發紅包之和
 89         amountList.add(totalAmount - fl);
 90         return amountList;
 91     }
 92 
 93     public static void main(String[] args) {
 94         //1.=====二分均值法======
 95         System.out.println("========二分均值法===========");
 96         // 把10元紅包拆分給10我的
 97         List<Integer> amountList = divideRedPackage(1000, 10);
 98         for (Integer amount : amountList) {
 99             System.out.println("搶到金額:" + new BigDecimal(amount).divide(new BigDecimal(100)));
100         }
101 
102         System.out.println("===================");
103         //2.=====線段切割法======
104         System.out.println("========線段切割法===========");
105         List<Integer> amountList2 = divideRedPackage2(1000, 10);
106         BigDecimal total = BigDecimal.ZERO;
107         for (Integer amount : amountList2) {
108             total = total.add(new BigDecimal(amount));
109             System.out.println("搶到金額:" + new BigDecimal(amount).divide(new BigDecimal(100)));
110         }
111         System.out.println("總金額=" + total + "分");
112     }
113 
114 
115 }

 

 

 

 

 

=====參考=====git

書籍:《漫畫算法》面試

相關文章
相關標籤/搜索