本文蒐集從各類資源上搜集高頻面試算法,慢慢填充...每一個算法都親測可運行,原理有註釋。Talk is cheap,show me the code! 走你~java
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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
書籍:《漫畫算法》面試