一、引子
本文搜集从各种资源上搜集高频面试算法,慢慢填充...每个算法都亲测可运行,原理有注释。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 }
=====参考=====
书籍:《漫画算法》