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


    一、引子

    本文搜集从各种资源上搜集高频面试算法,慢慢填充...每个算法都亲测可运行,原理有注释。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 }

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

    书籍:《漫画算法》

  • 相关阅读:
    scrapy基础知识之 使用FormRequest.from_response()方法模拟用户登录:
    scrapy基础知识之发送POST请求:
    scrapy基础知识之 CrawlSpiders(爬取腾讯校内招聘):
    scrapy基础知识之 CrawlSpiders:
    scrapy基础知识之 Logging:
    scrapy基础知识之将item 通过pipeline保存数据到mysql mongoDB:
    scrapy基础知识之 parse()方法的工作机制思考:
    scrapy基础知识之scrapy自动下载图片pipelines
    scrapy基础知识之将item写入JSON文件:
    scrapy基础知识之制作 Scrapy 爬虫 一共需要4步:
  • 原文地址:https://www.cnblogs.com/dennyzhangdd/p/11468931.html
Copyright © 2020-2023  润新知