常见算法合集[java源码+持续更新中...]

一、引子

本文搜集从各种资源上搜集高频面试算法,慢慢填充...每个算法都亲测可运行,原理有注释。Talk is cheap,show me the code! 走你~

二、常见算法

2.1 判断单向链表是否有环

 1 package study.algorithm.interview;
 2 
 3 /**
 4  * 判断单向链表是否有环? 

Q1:判断是否有环? isCycle

Q2:环长? count

Q3: 相遇点? p1.data

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 mainStack = new Stack<>();
15     private Stack 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个整数的最大公约数 

1.暴力枚举法:时间复杂度O(min(a,b))

2.辗转相除法(欧几里得算法): O(log(max(a,b))),但是取模运算性能较差

3.更相减损术:避免了取模运算,但性能不稳定,最坏时间复杂度:O(max(a,b)) 5 *

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 stackIn = new Stack<>();
15     // 出队
16     private Stack 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 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 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 openList = new ArrayList<>();
 64         // 已走list
 65         List 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 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 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 findNeighbors(Grid grid,List openList,List closeList){
120         ArrayList 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 openList,List 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 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 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){
194             // 列遍历
195             for(int j=0;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 divideRedPackage(Integer totalAMount, Integer totalPeopleNum) {
 29         List 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 divideRedPackage2(Integer totalAmount, Integer totalPeopleNum) {
 56         // 切割点list
 57         List indexList = new ArrayList<>();
 58         // 红包list
 59         List 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 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 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 }

 

 

 

 

 

=====参考=====

书籍:《漫画算法》

你可能感兴趣的:(常见算法合集[java源码+持续更新中...])