一、引子
本文搜集从各种资源上搜集高频面试算法,慢慢填充...每个算法都亲测可运行,原理有注释。Talk is cheap,show me the code! 走你~
二、常见算法
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 红包拆分算法
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 }
=====参考=====
书籍:《漫画算法》