Comics Algorithm Notes

The gods are silent-personal CSDN blog post directory

Oh, I wrote this three years ago, and I almost forgot about the Java grammar now...
Anyway, I should post the previous blog post. This is because I used Youdaoyun notes instead of directly using the CSDN editor to write this, so some content in the back is messed up, because I can’t understand it after a quick look, so... I will understand it after a while Come back and change the layout again, sauce.

Article Directory

Chapter1: Common sense of algorithms and data structures

  1. Linear structure: array, linked list stack, queue, hash table
    tree: binary tree binary heap
    graph
  2. Time complexity
     - the number of executions of basic operations T n T_{n}Tn  ( log ⁡ 2 n \log_2{n} log2n )
     - asymptotic time complexity
       Big O notation (highest order term)
       if there is a functionf ( n ) f_{(n)}f(n), so that when n → ∞ n\to\inftyn时, T ( n ) f ( n ) \frac{T_{(n)}}{f_{(n)}} f(n)T(n)The limit value of is a constant not equal to 0, then it is called f ( n ) f_{(n)}f(n)is T ( n ) T_{(n)}T(n)functions of the same magnitude . Write T ( n ) = O ( f ( n ) ) T_{(n)}=O(f_{(n)})T(n)=O(f(n)) , calledO ( f ( n ) ) O(f_{(n)})O(f(n))OOO is the asymptotic time complexityof the algorithm, referred to astime complexity
  3. Space complexity   S ( n ) = O ( f ( n ) ) S_{(n)}=O(f_{(n)})S(n)=O(f(n))
spatial properties space complexity
constant space O(1) O(1)O(1)
linear space O ( n ) O(n)O ( n )
Two-dimensional space O ( n 2 ) O(n^2)O ( n2)
recursion space

Chapter 2: Linear Data Structures

  1. Array array sequential storage sequence table
    The method of reading elements according to the subscript: random reading
    Time complexity of reading and updating: O ( 1 ) O(1)O ( 1 )
    insertion/deletion:O ( n ) O(n)O ( n )
    more read operations, less write operations
  2. Linked list random storage
    2.1 one-way linked listinsert image description here

2.2 Doubly linked list Linked insert image description here
list

  • Finding nodes: worst case time complexity is O ( n ) O(n)O ( n )
  • Updating nodes: O ( 1 ) O(1)O(1)
  • Insert node: tail/head/middle insert   O ( 1 ) O(1)O(1)
  • Delete elements: tail/head/middle delete   O ( 1 ) O(1)O ( 1 )  (Java automatic garbage collection mechanism)
  1. Physical Structure and Logical Structure

  2. Stack FILO
    • The storage position of the earliest entered element: the bottom of the stack
    • The location where the last entered element is stored: top of the stack
    • Push into the stack
    • pop out
    • O(1) O(1)O(1)
    • Used for: Backtracking through history - recursion, breadcrumb navigation

  3. Queue FIFO
    • The exit end of the queue: the front of the queue
    • The entry port of the queue: rear of the queue
    • enqueue
    • Dequeue deque ( circular queue )
    • O(1) O(1)O(1)
    • Used for: playback of history - waiting queues for fair locks in multi-threading, crawlers to achieve web crawling

  4. Double-ended queue deque

  5. Priority queue Who has the highest priority and who goes out of the queue first

  6. Hashtable/Hashtable Key-Value Essence: Array
    • Hash function  Key and array subscript are converted.
      Modulus 
      index = H ash Code ( Key ) % A array . length index=HashCode(Key)\%Array.lengthindex=HashCode(Key)%Array.length
    • Read get, write put (entry): close to O ( 1 ) O(1)O(1)
    • Write operation: hash collision
      • Open Addressing: Next Empty Location ThreadLocal
      • Linked list method: HashMap
    • Expansion resize:
扩容
重新哈希

Chapter 3: Trees

  1. A tree is a finite set of n (n≥0) nodes. When n=0, it is called an empty tree. In any non-empty tree, there are the following characteristics:
    • There is one and only one specific node called the root.
    • When n>1, the remaining nodes can be divided into m (m>0) finite sets that are not intersecting each other, each set itself is a tree, and is called the subtree of the root.
根节点root
叶子节点leaf
子树
层级
父节点parent
孩子节点child
兄弟节点sibling
最大层级数:高度或深度
  1. Binary tree: each node has at most two child nodes
左孩子left child
右孩子right child
  • Full binary tree: all non-leaf nodes have left and right children, and all leaf nodes are at the same level
  • Complete binary tree: For a binary tree with n nodes, numbered in hierarchical order, all nodes are numbered from 1 to n. If all the nodes of this tree are in the same position as the nodes numbered 1 to n of the full binary tree of the same depth, then this binary tree is a complete binary tree. A complete binary tree
    is a full binary tree missing the last few leaf nodes.
  1. Storage method of binary tree
    3.1 Linked storage structure
    3.2 Array
    parent left child 2parent+1 right child 2parent+2
    leftChild parent node (leftChild-1)/2

    application: binary heap

  2. Binary search tree (sorting)
    (1) If the left subtree is not empty, the values ​​of all nodes on the left subtree are less than the value of the root node
    (2) If the right subtree is not empty, all nodes on the right subtree The value of the node is greater than the value of the root node
    (3) The left and right subtrees are also binary search
    trees with relatively balanced distribution of nodes: search time complexity O ( n ) O(n)O ( n )
    self-balancing

  3. Depth-first traversal stack of binary search tree  
    • Preorder traversal: root node, left subtree, right subtree
    • Inorder traversal: left subtree, root node, right subtree
    • Post-order traversal: left subtree, right subtree, root node

  4. Breadth-first traversal queue of binary search tree  

  5. binary heap
    • Essence: complete binary tree
    • Classification
       (1) Maximum heap: the value of any parent node is greater than or equal to the value of the left and right child nodes
       (2) Min heap: the value of any parent node is less than or equal to the value of the left and right child nodes
    • root node: the top of the heap
    • array storage

  6. Binary heap self-adjustment
    • Insertion O ( log ⁡ n ) O(\log{n})O(logn ) : last position → float up
    • Delete O ( log ⁡ n ) O(\log{n})O(logn ) : (Delete the top of the heap) The last node is temporarily added to the top of the heap → sinking
    • Building a binary heap O ( n ) O(n)O ( n ) : Adjusting the unordered complete binary tree into a binary heap, the essence is to let all non-leaf nodes "sink" in turn (starting from the last non-leaf node)

  7. priority queue
    • Maximum priority queue: the current largest element is dequeued first and   the largest heap
    • Minimum priority queue: the current minimum element is first dequeued   minimum heap

Chapter4: Sorting Algorithms

Category one

time complexity algorithm
O ( n 2 ) O(n^2)O ( n2) Bubble, Insert, Select, Hill?
O ( n log ⁡ n ) O(n\log{n}) O ( nlogn) fast, heap, merge
linear Count, Bucket, Cardinality

The worst time complexity of quick sort is O ( n 2 ) O(n^2)O ( n2)

Category two

stability algorithm
Stablize Elements with the same value remain in the same order after sorting
unstable

insert image description here

1. Bubble sort

  1. Swap two by two, largest to smallest
  2. stable sort
  3. optimization
    • Sorted mark: isSorted
    • Sequence sequence area (record the position of the last element exchange)
  4. Cocktail sorting: two-way
     (when most elements are already sorted)

2. Quick Sort

(Swap sort)   average space complexity is O ( log ⁡ n ) O(\log{n})O(logn)

  1. Selection of Datum Elements
  2. exchange of elements
    • Two-sided round-robin method:left and right pointer(Compared at the same time, then exchange) One side is compared with the reference element and the other is close, and finally exchanged with the reference element
public class Main {
    
    
	//递归实现双边循环法的快速排序
	
	public static void quickSort(int[] arr,int startIndex,int endIndex){
    
    
		//递归结束条件:startIndex大于或等于endIndex时
		if(startIndex>=endIndex){
    
    
			return;
		}
		//得到基准元素位置
		int pivotIndex=partition(arr,startIndex,endIndex);
		//根据基准元素,分成两部分进行递归排序
		quickSort(arr,startIndex,pivotIndex-1);
		quickSort(arr,pivotIndex+1,endIndex);
	}
	
	/*
	 * 分治(双边循环法)
	 * @param arr 待交换的数组
	 * @param startIndex 起始下标
	 * @param endIndex 结束下标
	 * */
	private static int partition(int[] arr,int startIndex,int endIndex){
    
    
		//取第1个位置(也可以选择随机位置)的元素作为基准元素
		int pivot=arr[startIndex];
		int left=startIndex;
		int right=endIndex;
		
		while(left!=right){
    
    
			//控制right指针比较并左移
			while(left<right&&arr[right]>pivot){
    
    
				right--;
			}
			//控制left指针比较并右移
			while(left<right&&arr[left]<=pivot){
    
    
				left++;
			}
			//交换left和right指针所指向的元素
			if(left<right){
    
    
				int p=arr[left];
				arr[left]=arr[right];
				arr[right]=p;
			}
		}
		
		//pivot和指针重合点交换
		arr[startIndex]=arr[left];
		arr[left]=pivot;
		
		return left;
	}
	
    public static void main(String[] args) {
    
    
    	int[] arr=new int[] {
    
    4,4,6,5,3,2,8,1};
    	quickSort(arr,0,arr.length-1);
    	System.out.println(Arrays.toString(arr));
    }
}

 2.2.2 Unilateral loop method: Use the mark pointer to mark the area smaller than the reference element, and put all the items smaller than the reference element into this area. Finally put the benchmark element in the mark position

//仅有partition函数变化
private static int partition(int[] arr,int startIndex,int endIndex){
    
    
    //取第1个位置(也可以选择随机位置)的元素作为基准元素
    int pivot=arr[startIndex];
    int mark=startIndex;
    
    for(int i=startIndex+1;i<=endIndex;i++){
    
    
        if(arr[i]<pivot){
    
    
            mark++;
            int p=arr[mark];
            arr[mark]=arr[i];
            arr[i]=p;
        }
    }
    
    arr[startIndex]=arr[mark];
    arr[mark]=pivot;
    return mark;
}

 2.2.3 Methods without recursion

//仅有quickSort方法变化
public static void quickSort(int[] arr,int startIndex,int endIndex){
    
    
    //用一个集合栈来代替递归的函数栈
    Stack<Map<String,Integer>> quickSortStack=new Stack<Map<String,Integer>>();
    //整个数列的起止下标,以哈希的形式入栈
    Map rootParam=new HashMap();
    rootParam.put("startIndex", startIndex);
    rootParam.put("endIndex", endIndex);
    quickSortStack.push(rootParam);
    
    //循环结束条件:栈为空时
    while(!quickSortStack.isEmpty()){
    
    
        //栈顶元素出栈,得到起止下标
        Map<String,Integer> param=quickSortStack.pop();
        //得到基准元素位置
        int pivotIndex=partition(arr,param.get("startIndex"),param.get("endIndex"));
        //根据基准元素分成两部分,把每一部分的起止下标入栈
        if(param.get("startIndex")<pivotIndex-1){
    
    
            Map<String,Integer> leftParam=new HashMap<String,Integer>();
            leftParam.put("startIndex", param.get("startIndex"));
            leftParam.put("endIndex", pivotIndex-1);
            quickSortStack.push(leftParam);
        }
        if(pivotIndex+1<param.get("endIndex")){
    
    
            Map<String,Integer> rightParam=new HashMap<String,Integer>();
            rightParam.put("startIndex", pivotIndex+1);
            rightParam.put("endIndex", param.get("endIndex"));
            quickSortStack.push(rightParam);
        }
    }
}

3. Heap sort

3.1 Build the unordered array into a binary heap. If you need to sort from small to large, you can build a maximum heap; if you need to sort from large to small, you can build a minimum heap.
3.2 Circularly delete the top element of the heap, replace it to the end of the binary heap, and adjust the heap to generate a new top of the heap

public class Main {
    
    
	/**
	 * “下沉”调整
	 * @param array 待调整的堆
	 * @param parentIndex 要下沉的父节点
	 * @param length 堆的有效大小
	 */
	 public static void downAdjust(int[] array,int parentIndex,int length){
    
    
		 //temp保存父节点值,用于最后的赋值
		 int temp=array[parentIndex];
		 int childIndex=2*parentIndex+1;
		 while(childIndex<length){
    
    
			 //如果有右孩子,且右孩子大于左孩子的值,则定位到右孩子
			 if(childIndex+1<length&&array[childIndex+1]>array[childIndex]){
    
    
				 childIndex++;
			 }
			 //如果父节点大于任何一个孩子的值,则直接跳出
			 if(temp>=array[childIndex])
				 break;
			 //无须真正交换,单向赋值即可
			 array[parentIndex]=array[childIndex];
			 parentIndex=childIndex;
			 childIndex=2*childIndex+1;
		 }
		 array[parentIndex]=temp;
	 }
	 
	 /**
	  * 堆排序(升序)
	  * @param array 待调整的堆
	  */
	 public static void heapSort(int[] array){
    
    
		 //1.把无序数组构建成最大堆
		 for(int i=(array.length-2)/2;i>=0;i--){
    
    
			 downAdjust(array,i,array.length);
		 }
		 System.out.println(Arrays.toString(array));
		 //2.循环删除堆顶元素,移到集合尾部,调整堆产生新的堆顶
		 for(int i=array.length-1;i>0;i--){
    
    
			 //最后1个元素和第1个元素进行交换
			 int temp=array[i];
			 array[i]=array[0];
			 array[0]=temp;
			 //“下沉”调整最大堆
			 downAdjust(array,0,i);
		 }
	 }
	 
	 public static void main(String[] args){
    
    
		 int[] arr=new int[] {
    
    1,3,2,6,5,7,8,9,10,0};
		 heapSort(arr);
		 System.out.println(Arrays.toString(arr));
	 }
}

Heap sort
space complexity: O ( 1 ) O(1)O ( 1 )
Time Complexity:
 Step 1: Construct the unordered array into a binary heapO ( n ) O(n)O ( n )
 The second step: need to performn − 1 n-1n1 cycle. The downAdjust method is called once in each loop, so the calculation scale of step 2 is( n − 1 ) × log ⁡ n (n-1)\times\log{n}(n1)×logn , the time complexity isO ( n log ⁡ n ) O(n\log{n})O ( nlogn )
 The two steps are in parallel, so the overalltime complexity isO ( n log ⁡ n ) O(n\log{n})O ( nlogn)

4. Counting sort

4.1 Original version and optimized step1

Use the array subscript to determine the correct position of the element
(1) random integer, with a value range, create an array whose length is the value range
(2) traverse the sequence, each element corresponds to the subscripted array element + 1
(3) traverse the array , output the subscript value of the array element (the element value is the output count)

public static int[] countSort(int[] array){
    
    
    //1.得到数列的最大值
    int max=array[0];
    for(int i=1;i<array.length;i++){
    
    
        if(array[i]>max){
    
    
            max=array[i];
        }
    }
    //2.根据数列最大值确定统计数组的长度
    /* 算法改进step1:用最大值-最小值+1作为数组的长度,
    *  数组的最小值作为偏移量,用于统计整数在统计数组中的下标
    */
    int[] countArray=new int[max+1];
    //3.遍历数列,填充统计数组
    for(int i=0;i<array.length;i++){
    
    
        countArray[array[i]]++;
    }
    //4.遍历统计数组,输出结果
    int index=0;
    int[] sortedArray=new int[array.length];
    for(int i=0;i<countArray.length;i++){
    
    
        for(int j=0;j<countArray[i];j++){
    
    
            sortedArray[index++]=i;
        }
    }
    return sortedArray;
}

public static void main(String[] args){
    
    
    int[] array=new int[]{
    
    4,4,6,5,3,2,8,1,7,5,6,0,10};
    int[] sortedArray=countSort(array);
    System.out.println(Arrays.toString(sortedArray));
}

4.2 Optimize step2

It is required to follow the inherent order of the original table (becoming a stable sort)

Method: Starting from the second element of the statistical array, add the sum of all previous elements to each element

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-XZjeDK0k-1595582632835) (comic algorithm 3.JPG)]

The purpose of adding: Let the element value stored in the statistics array be equal to the serial number of the final sorting position of the corresponding integer
Next, create the output array sortedArray with the same length as the input array, and then traverse the input array from back to front. (Subscript → position, statistics array corresponds to -1)

public static int[] countSort(int[] array){
    
    
    //1. 得到数列的最大值和最小值,并算出差值
    int max=array[0];
    int min=array[0];
    for(int i=1;i<array.length;i++){
    
    
        if(array[i]>max){
    
    
            max=array[i];
        }
        if(array[i]<min){
    
    
            min=array[i];
        }
    }
    int d=max-min;
    
    //2. 创建统计数组并统计对应元素的个数
    int[] countArray=new int[d+1];
    for(int i=0;i<array.length;i++){
    
    
        countArray[array[i]-min]++;
    }
    
    //3. 统计数组做变形,后面的元素等于前面的元素之和
    for(int i=1;i<countArray.length;i++){
    
    
        countArray[i]+=countArray[i-1];
    }
    
    //4. 倒序遍历原始数列,从统计数组找到正确位置,输出到结果数组
    int[] sortedArray=new int[array.length];
    for(int i=array.length-1;i>=0;i--){
    
    
        sortedArray[countArray[array[i]-min]-1]=array[i];
        countArray[array[i]-min]--;
    }
    
    return sortedArray;
}

public static void main(String[] args) {
    
    
    int[] array=new int[]{
    
    95,94,91,98,99,90,99,93,91,92};
    int[] sortedArray=countSort(array);
    System.out.println(Arrays.toString(sortedArray));
}

If the size of the original sequence is n, the difference between the largest and smallest integers is m, and the time complexity is O ( n + m ) O(n+m)O ( n+m ) , the space complexity (if you don't consider the result array, only consider the statistics array) isO ( m ) O(m)O(m)

5. Bucket Sort

  1. Each bucket (bucket) represents an interval range, which can carry one or more elements.
  2. Step
    2.1 step1: bucketing
    2.2 step2: put the array elements into buckets
    2.3 step3: sort the elements inside each bucket separately
    2.4 step4: traverse the buckets and output all elements
public static double[] bucketSort(double[] array){
    
    
    //1. 得到数列的最大值和最小值,并算出差值
    double max=array[0];
    double min=array[0];
    for(int i=1;i<array.length;i++){
    
    
        if(array[i]>max){
    
    
            max=array[i];
        }
        if(array[i]<min){
    
    
            min=array[i];
        }
    }
    double d=max-min;
    
    //2. 初始化桶
    int bucketNum=array.length;
    ArrayList<LinkedList<Double>> bucketList=new ArrayList<LinkedList<Double>>(bucketNum);
    for(int i=0;i<bucketNum;i++){
    
    
        bucketList.add(new LinkedList<Double>());
    }
    
    //3. 遍历原始数组,将每个元素放入桶中
    for(int i=0;i<array.length;i++){
    
    
        int num=(int)((array[i]-min)*(bucketNum-1)/d);
        bucketList.get(num).add(array[i]);
    }
    
    //4. 对每个桶内部进行排序
    for(int i=0;i<bucketList.size();i++){
    
    
        //JDK底层采用了归并排序或归并的优化版本
        Collections.sort(bucketList.get(i));
    }
    
    //5. 输出全部元素
    double[] sortedArray=new double[array.length];
    int index=0;
    for(LinkedList<Double> list:bucketList){
    
    
        for(double element:list){
    
    
            sortedArray[index]=element;
            index++;
        }
    }
    
    return sortedArray;
}

public static void main(String[] args) {
    
    
    double[] array=new double[]{
    
    4.12,6.421,0.0023,3.0,2.123,8.122,4.12,10.09};
    double[] sortedArray=bucketSort(array);
    System.out.println(Arrays.toString(sortedArray));
}

The performance of bucket sort is not absolutely stable. If the distribution of elements is extremely uneven, in extreme cases, there are n-1 elements in the first bucket and 1 element in the last bucket. At this time, the time complexity will degenerate to O ( n log ⁡ n ) O(n\log{n})O ( nlogn ) , and also create many empty buckets for nothing.

Chapter5: Algorithms in Interviews

1. How to judge that a linked list has a ring

singly linked list

1.1 Method 1: Violent traversal

Method: Traverse the nodes in turn, every time a new node is traversed, check all the nodes before the new node from the beginning, and compare the new node with all the previous nodes. If it is found that the new node is the same as a previous node, it means that the node has been traversed twice, and the linked list has a ring.
Time complexity: O ( n 2 ) O(n^2)O ( n2 )
Space complexity:O ( 1 ) O(1)O(1)

1.2 Method 2: Hash Table

Method: Create a HashSet collection with the node ID as the key to store the traversed nodes.
Time complexity: O ( n ) O(n)O ( n )
space complexity:O ( n ) O(n)O ( n )

1.3 Method 3: Two pointers

Method: First create two pointers p1 and p2 (two object references in Java), let them point to the head node of this linked list at the same time. Then start a big loop. In the loop body, let the pointer p1 move backward 1 node each time, let the pointer p2 move backward 2 nodes each time, and then compare whether the nodes pointed by the two pointers are the same. If they are the same, it can be judged that the linked list has a cycle, and if they are different, continue to the next cycle.
Principle: catch up problem (because it is circular, so it can definitely catch up)
time complexity: O ( n ) O(n)O ( n )
space complexity:O ( 1 ) O(1)O(1)

/*
    * 判断是否有环
    * @param head 链表头节点
    * */
public static boolean isCycle(Node head){
    
    
    Node p1=head;
    Node p2=head;
    while(p2!=null&&p2.next!=null){
    
    
        p1=p1.next;
        p2=p2.next.next;
        if(p1==p2){
    
    
            return true;
        }
    }
    return false;
}

/*
    * 链表节点
    * */
public static class Node{
    
    
    int data;
    Node next;
    Node(int data){
    
    
        this.data=data;
    }
}

public static void main(String[] args) {
    
    
    Node node1=new Node(5);
    Node node2=new Node(3);
    Node node3=new Node(7);
    Node node4=new Node(2);
    Node node5=new Node(6);
    node1.next=node2;
    node2.next=node3;
    node3.next=node4;
    node4.next=node5;
    node5.next=node2;
    
    System.out.println(isCycle(node1));
}

1.4 Extended problem: the length of the ring

Method: When the two pointers meet for the first time, which proves that the linked list has a ring, let the two pointers continue to cycle forward from the meeting point, and count the number of cycles until the two pointers meet for the second time. At this point, the counted number of advances is the ring length.

principle:

  • The pointer p1 takes 1 step each time, and the pointer p2 takes 2 steps each time, and the speed difference between the two is 1 step. When the two pointers meet again, p2 walks one more lap than p1.
  • Therefore, the length of the ring = each speed difference × the number of forwards = the number of forwards

1.5 Extended Questions: Entry and Exit Ring Nodes

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-sWGTmMQW-1595582632839) (comic algorithm 5.PNG)]

2. Minimal stack

Topic: Implement a stack with three methods: pop, push, and getMin. To ensure that the time complexity of these three methods are O ( 1 ) O(1)O(1)

2.1 The problematic method: temporarily store the subscript of the smallest element in the stack

Question: What should I do if this element is popped out of the stack?

2.2 Method: store the smallest value ever in the stack

Method: create a spare tire stack B, for each element pushed into the stack, push the latest minimum value/stack A out of the stack, and let stack B out of the stack according to the situation Worst case space complexity
: O ( n ) O(n)O ( n )

public static class MinStack{
    
    
    private Stack<Integer> mainStack=new Stack<Integer>();
    private Stack<Integer> minStack=new Stack<Integer>();
    
    /**
        * 入栈操作
        * @param element 入栈的元素
        * */
    public void push(int element){
    
    
        mainStack.push(element);
        //如果辅助栈为空,或者新元素小于或等于辅助栈栈顶,则将新元素压入辅助栈
        if(minStack.empty()||element<=minStack.peek()){
    
    
            minStack.push(element);
        }
    }
    
    /**
        * 出栈操作
        * */
    public Integer pop(){
    
    
        //如果出栈元素和辅助栈栈顶元素值相等,辅助栈出栈
        if(mainStack.peek().equals(minStack.peek())){
    
    
            minStack.pop();
        }
        return mainStack.pop();
    }
    
    /**
        * 获取栈的最小元素
        * */
    public int getMin() throws Exception{
    
    
        if(mainStack.empty()){
    
    
            throw new Exception("stack is empty");
        }
        return minStack.peek();
    }
}

public static void main(String[] args) throws Exception{
    
    
    MinStack stack=new MinStack();
    stack.push(4);
    stack.push(9);
    stack.push(7);
    stack.push(3);
    stack.push(8);
    stack.push(5);
    System.out.println(stack.getMin());
    
    stack.pop();
    stack.pop();
    stack.pop();
    System.out.println(stack.getMin());
}

3. Greatest Common Divisor

Question: Find the greatest common divisor of two integers

3.1 Method 1: Violent enumeration method

public static int getGreatestCommonDivisor(int a,int b){
    
    
    int big=a>b?a:b;
    int small=a<b?a:b;
    if(big%small==0){
    
    
        return small;
    }
    for(int i=small/2;i>1;i--){
    
    
        if(small%i==0&&big%i==0){
    
    
            return i;
        }
    }
    return 1;
}

public static void main(String[] args){
    
    
    System.out.println(getGreatestCommonDivisor(25,5));
    System.out.println(getGreatestCommonDivisor(100,80));
    System.out.println(getGreatestCommonDivisor(27,14));
}

Time complexity: O ( min ( a , b ) ) O(min(a,b))O(min(a,b))

3.2 Method 2: Rolling and dividing method/Euclidean algorithm

Qin Jiushao Algorithm/Horner Algorithm

Theorem: For two positive integers a and b (a>b), their greatest common divisor is equal to the greatest common divisor between c and b, the remainder of dividing a by b.

public static int getGrestestCommonDivisorV2(int a,int b){
    
    
    int big=a>b?a:b;
    int small=a<b?a:b;
    if(big%small==0){
    
    
        return small;
    }
    return getGrestestCommonDivisorV2(big%small,small);
}

public static void main(String[] args){
    
    
    System.out.println(getGrestestCommonDivisorV2(25,5));
    System.out.println(getGrestestCommonDivisorV2(100,80));
    System.out.println(getGrestestCommonDivisorV2(27,14));
}

Problem: The modulo operation performs poorly when the two integers are large.
The time complexity can be approximated as: O ( log ⁡ ( max ( a , b ) ) ) O(\log{(max(a,b))})O(log(max(a,b)))

3.3 Method 3: Rolling and turning subtraction method/more phase reduction method

Theorem: Two positive integers a and b (a>b), their greatest common divisor is equal to the greatest common divisor between the difference c and b of ab.

public static int getGrestestCommonDivisorV3(int a,int b){
    
    
    if(a==b){
    
    
        return a;
    }
    int big=a>b?a:b;
    int small=a<b?a:b;
    return getGrestestCommonDivisorV3(big-small,small);
}

public static void main(String[] args){
    
    
    System.out.println(getGrestestCommonDivisorV3(25,5));
    System.out.println(getGrestestCommonDivisorV3(100,80));
    System.out.println(getGrestestCommonDivisorV3(27,14));
}

Problem: Phase reduction is an unstable algorithm, and the number of operations may be extremely large. The
worst time complexity is O ( max ( a , b ) ) O(max(a,b))O ( max x ( a ,b))

3.4 Method Four: Shift Operation

  1. When both a and b are even numbers, gcd(a,b)=2×gcd(a/2,b/2)=2×gcd(a>>1,b>>1).
  2. When a is odd and b is even, gcd(a,b)=gcd(a,b/2)=gcd(a,b>>1).
  3. When a and b are both odd numbers, use the more phase subtraction operation first, gcd(a,b)=gcd(b,ab), at this time ab must be even, and then continue to perform shift operations
public static int gcd(int a,int b){
    
    
    if(a==b){
    
    
        return a;
    }
    if((a&1)==0&&(b&1)==0){
    
        //都是偶数
        return gcd(a>>1,b>>1)<<1;
    }else if((a&1)==0&&(b&1)!=0){
    
    
        return gcd(a>>1,b);
    }else if((a&1)!=0&&(b&1)==0){
    
    
        return gcd(a,b>>1);
    }else{
    
    
        int big=a>b?a:b;
        int small=a<b?a:b;
        return gcd(big-small,small);
    }
}

public static void main(String[] args){
    
    
    System.out.println(gcd(25,5));
    System.out.println(gcd(100,80));
    System.out.println(gcd(27,14));
}

Time complexity: O ( log ⁡ ( max ( a , b ) ) ) O(\log{(max(a,b))})O(log(max(a,b)))

4. Integer powers of 2

Topic: Determine if a positive integer is a power of 2

4.1 Method 1: Violent enumeration method

public static boolean isPowerOf2(int num){
    
    
    int temp=1;
    while(temp<=num){
    
    
        if(temp==num){
    
    
            return true;
        }
        temp*=2;    //优化:把乘以2的操作改成向左移位
        //temp=temp<<1;
    }
    return false;
}

public static void main(String[] args){
    
    
    System.out.println(isPowerOf2(32));
    System.out.println(isPowerOf2(19));
}

Time complexity: O ( log ⁡ n ) O(\log{n})O(logn)

4.2 Method 2

If an integer is a power of 2, then when it is converted into binary, only the highest bit is 1 and the other bits are 0.
Once such a number is subtracted by 1, all its binary digits become 1.
n&(n-1)=0

return (num&num-1)==0;

5. Maximum adjacent difference after sorting an unordered array

Question: There is an unordered integer array, how to find the maximum difference between any two adjacent elements of the array after sorting?

5.1 Method 1: Solve after sorting

Using time complexity O ( n log ⁡ n ) O(n\log{n})O ( nlogn ) sorting algorithm (such as quick sort) to sort the original array, and then traverse to find the difference.
Time complexity:O ( n log ⁡ n ) O(n\log{n})O ( nlogn )
Without changing the original array, space complexity:O ( n ) O(n)O ( n )

5.2 Method 2: Using the idea of ​​counting and sorting

  1. Using the idea of ​​counting and sorting, first find the interval length k (k=max-min+1) of the maximum value max and the minimum value min of the original array, and the offset d=min.
  2. Creates a new Array of length k.
  3. The original array is traversed, and every time an element is traversed, the value corresponding to the subscript of the new array Array is +1. After the traversal, some of the element values ​​of the Array become 1 or higher, and some of the element values ​​are still 0.
  4. Traverse the new array Array, and count the maximum number of consecutive 0 values ​​in the Array + 1, which is the maximum difference between adjacent elements.

Problem: Array elements may have very different values

5.3 Method 3: Using the idea of ​​bucket sorting

  1. Using the idea of ​​bucket sorting, according to the length n of the original array, create n buckets, and each bucket represents an interval range. The first bucket starts from the minimum value min of the original array, and the interval span is (max-min)/(n-1).
  2. Traverse the original array, insert each element of the original array into the corresponding bucket, and record the maximum and minimum values ​​of each bucket.
  3. Traverse all the buckets, and count the difference between the maximum value of each bucket and the minimum value of the non-empty bucket on the right side of the bucket. The maximum value difference is the maximum adjacent difference after sorting the original array.

Time complexity: O ( n ) O(n)O ( n )

public static int getMaxSortedDistance(int[] array){
    
    
    //1. 得到数列的最大值和最小值
    int max=array[0];
    int min=array[0];
    for(int i=1;i<array.length;i++){
    
    
        if(array[i]>max){
    
    
            max=array[i];
        }
        if(array[i]<min){
    
    
            min=array[i];
        }
    }
    int d=max-min;
    //如果max和min相等,说明数组所有元素都相等,返回0
    if(d==0){
    
    
        return 0;
    }
    
    //2. 初始化桶
    int bucketNum=array.length;
    Bucket[] buckets=new Bucket[bucketNum];
    for(int i=0;i<bucketNum;i++){
    
    
        buckets[i]=new Bucket();
    }
    
    //3. 遍历原始数组,确定每个桶的最大最小值
    for(int i=0;i<array.length;i++){
    
    
        //确定数组元素所归属的桶下标
        int index=((array[i]-min)*(bucketNum-1)/d);
        if(buckets[index].min==null||buckets[index].min>array[i]){
    
    
            buckets[index].min=array[i];
        }
        if(buckets[index].max==null||buckets[index].max<array[i]){
    
    
            buckets[index].max=array[i];
        }
    }
    
    //4. 遍历桶,找到最大差值
    int leftMax=buckets[0].max;
    int maxDistance=0;
    for(int i=1;i<buckets.length;i++){
    
    
        if(buckets[i].min==null){
    
    
            continue;
        }
        if(buckets[i].min-leftMax>maxDistance){
    
    
            maxDistance=buckets[i].min-leftMax;
        }
        leftMax=buckets[i].max;
    }
    
    return maxDistance;
}

/**
    * 桶
    * */
private static class Bucket{
    
    
    Integer min;
    Integer max;
}

public static void main(String[] args){
    
    
    int[] array=new int[]{
    
    2,6,3,4,5,10,9};
    System.out.println(getMaxSortedDistance(array));
}

6. Implement a queue with a stack

Two stacks, back and forth

static class StackQueue{
    
    
    private Stack<Integer> stackA=new Stack<Integer>();
    private Stack<Integer> stackB=new Stack<Integer>();
    
    /**
        * 入队操作
        * @param element 入队的元素
        * */
    public void enQueue(int element){
    
    
        stackA.push(element);
    }
    
    /**
        * 出队操作
        * */
    public Integer deQueue(){
    
    
        if(stackB.isEmpty()){
    
    
            if(stackA.isEmpty()){
    
    
                return null;
            }
            transfer();
        }
        return stackB.pop();
    }
    
    /**
        * 栈A元素转移到栈B
        * */
    private void transfer(){
    
    
        while(!stackA.isEmpty()){
    
    
            stackB.push(stackA.pop());
        }
    }
}

public static void main(String[] args){
    
    
    StackQueue stackQueue=new StackQueue();
    stackQueue.enQueue(1);
    stackQueue.enQueue(2);
    stackQueue.enQueue(3);
    System.out.println(stackQueue.deQueue());
    System.out.println(stackQueue.deQueue());
    stackQueue.enQueue(4);
    System.out.println(stackQueue.deQueue());
    System.out.println(stackQueue.deQueue());
}

Time complexity of enqueue operation: O ( 1 ) O(1)O ( 1 )
dequeue operation, if element migration is involved, the time complexity isO ( n ) O(n)O ( n ) ; if no migration is used, the time complexity isO ( 1 ) O(1)O(1)

Amortized time complexity
There are only a few cases of dequeue operations that require element migration, and it is impossible to appear consecutively, and most subsequent dequeue operations do not require element migration.
Therefore, the time is evenly distributed to each dequeue operation, and its time complexity is O ( 1 ) O(1)O(1)

7. Find the next number in the full permutation

Question: Given a positive integer, find the next number in which all the digits of this positive integer are fully arranged.

method:

  1. Look at the reverse sequence area from the back to the front, and find the previous bit of the reverse sequence area, which is the boundary of digital replacement.
  2. Exchange the position of the previous digit in the reverse order area with the smallest number greater than it in the reverse order area.
  3. Convert the original reverse order area to sequential state.
//为方便数字的交换,入参和返回值的类型都采用了整型数组
public static int[] findNearestNumber(int[] numbers){
    
    
    //1. 从后向前查看逆序区域,找到逆序区域的前一位,也就是数字置换的边界
    int index=findTransferPoint(numbers);
    //如果数字置换边界是0,说明整个数组已经逆序,无法得到更大的相同数字组成的整数,返回null
    if(index==0){
    
    
        return null;
    }
    
    //2. 把逆序区域的前一位和逆序区域中刚刚大于它的数字交换位置
    //复制并入参,避免直接修改入参
    int[] numbersCopy=Arrays.copyOf(numbers, numbers.length);
    exchangeHead(numbersCopy,index);
    
    //3. 把原来的逆序区域转为顺序
    reverse(numbersCopy,index);
    return numbersCopy;
}

private static int findTransferPoint(int[] numbers){
    
    
    for(int i=numbers.length-1;i>0;i--){
    
    
        if(numbers[i]>numbers[i-1]){
    
    
            return i;
        }
    }
    return 0;
}

private static int[] exchangeHead(int[] numbers,int index){
    
    
    int head=numbers[index-1];
    for(int i=numbers.length-1;i>0;i--){
    
    
        if(head<numbers[i]){
    
    
            numbers[index-1]=numbers[i];
            numbers[i]=head;
            break;
        }
    }
    return numbers;
}

private static int[] reverse(int[] num,int index){
    
    
    for(int i=index,j=num.length-1;i<j;i++,j--){
    
    
        int temp=num[i];
        num[i]=num[j];
        num[j]=temp;
    }
    return num;
}

public static void main(String[] args){
    
    
    int[] numbers={
    
    1,2,3,4,5};
    //打印12345 之后的10个全排列整数
    for(int i=0;i<10;i++){
    
    
        numbers=findNearestNumber(numbers);
        outputNumbers(numbers);
    }
}

//输出数组
private static void outputNumbers(int[] numbers){
    
    
    for(int i:numbers){
    
    
        System.out.print(i);
    }
    System.out.println();
}

Solution name: lexicographical algorithm
Time complexity: O ( n ) O(n)O ( n )

8. The minimum value after deleting k numbers

Question: Given an integer, remove k numbers from the integer, and require the new integer formed by the remaining numbers to be as small as possible. (The size of the given integer can exceed the range of numbers of type long)

Each time the number larger than the number on the right (the last element of the first order) is deleted
to obtain the local optimal solution in turn , and finally the idea of ​​​​obtaining the global optimal solution is called the greedy algorithm.

8.1 Badly performing code

/**
    * 删除整数的k个数字,获得删除后的最小值
    * @param num 原整数
    * @param k 删除数量
    * */
public static String removeKDigits(String num,int k){
    
    
    String numNew=num;
    for(int i=0;i<k;i++){
    
    
        boolean hasCut=false;
        //从左向右遍历,找到比自己右侧数字大的数字并删除
        for(int j=0;j<numNew.length()-1;j++){
    
    
            if(numNew.charAt(j)>numNew.charAt(j+1)){
    
    
                numNew=numNew.substring(0,j)+numNew.substring(j+1,numNew.length());
                hasCut=true;
                break;
            }
        }
        //如果没有找到要删除的数字,则删除最后一个数字
        if(!hasCut){
    
    
            numNew=numNew.substring(0,numNew.length()-1);
        }
        //清除整数左侧的数字0
        numNew=removeZero(numNew);
    }
    //如果整数的所有数字都被删除了,直接返回0
    if(numNew.length()==0){
    
    
        return "0";
    }
    return numNew;
}

private static String removeZero(String num){
    
    
    for(int i=0;i<num.length()-1;i++){
    
    
        if(num.charAt(0)!='0'){
    
    
            break;
        }
        num=num.substring(1,num.length());
    }
    return num;
}

public static void main(String[] args){
    
    
    System.out.println(removeKDigits("1593212",3));
    System.out.println(removeKDigits("30200",1));
    System.out.println(removeKDigits("10",2));
    System.out.println(removeKDigits("541270936",3));
}

Time complexity: O ( kn ) O(kn)O ( kn )
disadvantages:

  1. Each inner loop needs to traverse all numbers from the beginning (should stay at the last deleted position to continue)
  2. The time complexity of the subString method is O ( n ) O(n)O ( n ) , not very performant

8.2 Optimized code

/**
    * 删除整数的k个数字,获得删除后的最小值
    * @param num 原整数
    * @param k 删除数量
    * */
public static String removeKDigits(String num,int k){
    
    
    //新整数的最终长度=原整数长度-k
    int newLength=num.length()-k;
    //创建一个栈,用于接收所有的数字
    char[] stack=new char[num.length()];
    int top=0;
    for(int i=0;i<num.length();++i){
    
    
        //遍历当前数字
        char c=num.charAt(i);
        //当栈顶数字大于遍历到的当前数字时,栈顶数字出栈(相当于删除数字)
        while(top>0&&stack[top-1]>c&&k>0){
    
    
            top-=1;
            k-=1;
        }
        //遍历到的当前数字入栈
        stack[top++]=c;
    }
    //找到栈中第1个非零数字的位置,以此构建新的整数字符串
    int offset=0;
    while(offset<newLength&&stack[offset]=='0'){
    
    
        offset++;
    }
    return offset==newLength?"0":new String(stack,offset,newLength-offset);
}

public static void main(String[] args){
    
    
    System.out.println(removeKDigits("1593212",3));
    System.out.println(removeKDigits("30200",1));
    System.out.println(removeKDigits("10",2));
    System.out.println(removeKDigits("541270936",3));
}

Time complexity: O ( n ) O(n)O ( n )
space complexity:O ( n ) O(n)O ( n )

9. How to implement the addition of large integers

Array storage, digital addition (similar to column vertical)

/**
    * 大整数求和
    * @param bigNumberA 大整数A
    * @param bigNumberB 大整数B
    * */
public static String bigNumberSum(String bigNumberA,String bigNumberB){
    
    
    //1. 把两个大整数用数组逆序存储,数组长度等于较大整数位数+1
    int maxLength=bigNumberA.length()>bigNumberB.length()?bigNumberA.length():bigNumberB.length();
    int[] arrayA=new int[maxLength+1];
    for(int i=0;i<bigNumberA.length();i++){
    
    
        arrayA[i]=bigNumberA.charAt(bigNumberA.length()-1-i)-'0';
    }
    int[] arrayB=new int[maxLength+1];
    for(int i=0;i<bigNumberB.length();i++){
    
    
        arrayB[i]=bigNumberB.charAt(bigNumberB.length()-1-i)-'0';
    }
    
    //2. 构建result数组,数组长度等于较大整数位数+1
    int[] result=new int[maxLength+1];
    
    //3. 遍历数组,按位相加
    for(int i=0;i<result.length;i++){
    
    
        int temp=result[i];
        temp+=arrayA[i];
        temp+=arrayB[i];
        //判断是否进位
        if(temp>=10){
    
    
            temp=temp-10;
            result[i+1]=1;
        }
        result[i]=temp;
    }
    
    //4. 把result数组再次逆序并转成String
    StringBuilder sb=new StringBuilder();
    //是否找到大整数的最高有效位
    boolean findFirst=false;
    for(int i=result.length-1;i>=0;i--){
    
    
        if(!findFirst){
    
    
            if(result[i]==0){
    
    
                continue;
            }
            findFirst=true;
        }
        sb.append(result[i]);
    }
    return sb.toString();
}

public static void main(String[] args){
    
    
    System.out.println(bigNumberSum("426709752318","95481253129"));
}

Time complexity: O ( n ) O(n)O ( n )

Optimization: It is not necessary to split the original integer so finely, it only needs to be split to the point where it can be directly calculated.
An int can have a maximum of 10 digits. In order to prevent overflow, every 9 digits of a large integer can be used as an element for addition.

10. How to solve the gold mine problem

Topic: There is a king who owns 5 gold mines. The gold reserves of each gold mine are different, and the number of workers who need to participate in mining is also different. For example, some gold mine reserves are 500kg of gold and need 5 workers to dig; some gold mine reserves are 200kg of gold and need 3 workers to dig...
If the total number of workers involved in mining is 10. Each gold mine must either be fully dug, or not dug, and half of the people cannot be sent to dig half of the gold mines. It is required to use a program to find out, if you want to get as much gold as possible, which gold mines should you choose to mine?

Tags: dynamic programming   knapsack problem

optimal substructure
boundary
state transition equation

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-ZbTIVYdJ-1595582632843) (comic algorithm 6.png)]

10.1 Code before optimization

/**
    * 获得金矿最优收益
    * @param w 工人数量
    * @param n 可选金矿数量
    * @param p 金矿开采所需的工人数量
    * @param g 金矿储量
    * */
public static int getBestGoldMining(int w,int n,int[] p,int[] g){
    
    
    if(w==0||n==0){
    
    
        return 0;
    }
    if(w<p[n-1]){
    
    
        return getBestGoldMining(w,n-1,p,g);
    }
    return Math.max(getBestGoldMining(w,n-1,p,g), getBestGoldMining(w-p[n-1],n-1,p,g)+g[n-1]);
}

public static void main(String[] args){
    
    
    int w=10;
    int[] p={
    
    5,5,3,4,3};
    int[] g={
    
    400,500,200,300,350};
    System.out.println("最优收益:"+getBestGoldMining(w,g.length,p,g));
}

Time complexity: O ( 2 n ) O(2^n)O(2n )
problem: recursion does a lot of double counting

10.2 Optimize step1

Bottom-up solution:
take the gold mine as the row and the number of workers as the column, and solve the table one by one

/**
    * 获得金矿最优收益
    * @param w 工人数量
    * @param p 金矿开采所需的工人数量
    * @param g 金矿储量
    * */
public static int getBestGoldMiningV2(int w,int[] p,int[] g){
    
    
    //创建表格
    int[][] resultTable=new int[g.length+1][w+1];
    //填充表格
    for(int i=1;i<=g.length;i++){
    
    
        for(int j=1;j<=w;j++){
    
    
            if(j<p[i-1]){
    
    
                resultTable[i][j]=resultTable[i-1][j];
            }else{
    
    
                resultTable[i][j]=Math.max(resultTable[i-1][j], resultTable[i-1][j-p[i-1]]+g[i-1]);
            }
        }
    }
    //返回最后一个格子的值
    return resultTable[g.length][w];
}

Time complexity: O ( nw ) O(nw)O ( n w )

10.3 Optimize step2

There is no need to save the entire table in the program. No matter how many gold mines there are, we only need to save the data of one row. When calculating the next line, count from right to left, and replace the old data one by one.

/**
    * 获得金矿最优收益
    * @param w 工人数量
    * @param p 金矿开采所需的工人数量
    * @param g 金矿储量
    * */
public static int getBestGoldMiningV3(int w,int[] p,int[] g){
    
    
    //创建当前结果
    int[] results=new int[w+1];
    //填充一维数组
    for(int i=1;i<=g.length;i++){
    
    
        for(int j=w;j>=1;j--){
    
    
            if(j>=p[i-1]){
    
    
                results[j]=Math.max(results[j], results[j-p[i-1]]+g[i-1]);
            }
        }
    }
    //返回最后1个格子的值
    return results[w];
}

Space Complexity: O ( n ) O(n)O ( n )

11. Find missing integers

Problem: There are 99 non-repeating positive integers in an unordered array, ranging from 1 to 100, but only one integer from 1 to 100 is missing. How to find out this missing integer?

11.1 Solution 1

Create a hash table with 1~100 as the key, traverse the array to delete the key corresponding to the element, and the last remaining key is the missing integer.
Time complexity: O ( n ) O(n)O ( n )
space complexity:O ( n ) O(n)O ( n )

11.2 Solution 2

First sort the array elements, and then traverse, if two adjacent elements are found to be discontinuous, it means that the integer between these two elements is missing
Time complexity: O ( n log ⁡ n ) O(n\log{n })O ( nlogn )
Space complexity:O ( 1 ) O(1)O(1)

11.3 Solution 3

First calculate the cumulative sum of 1~100, and then subtract all the elements in the array in turn, and the final difference is the missing integer Time
complexity: O ( n ) O(n)O ( n )
space complexity:O ( 1 ) O(1)O(1)

11.4 Problem expansion step1

Question: There are several positive integers in an unordered array, ranging from 1 to 100, 99 of which appear even times, and only 1 integer appears odd times, how to find the odd number of integers?

The XOR operation   is the same as 0 and 1.
Perform XOR operation on all the elements in the array in turn, and finally get the integer
Time complexity: O ( n ) O(n)O ( n )
space complexity:O ( 1 ) O(1)O(1)

11.5 Problem expansion step2

Question: ...if there are 2 integers in the array that appear odd times and other integers appear even times, how to find out these two integers?

Divide and conquer
If you divide the array into two parts, make sure that each part contains an integer that appears an odd number of times, which is the same as the previous question. First,
perform XOR operations on the elements of the array in turn, and there must be at least 1 in the final result. A binary bit is 1, use this position to divide the array into two parts, you can separate the two integers you are looking for

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-uOYZlpjp-1595582632846) (comic algorithm 7.png)]

Time complexity: O ( n ) O(n)O ( n )
space complexity:O ( 1 ) O(1)O(1)

public static int[] findLostNum(int[] array){
    
    
    //用于存储2个出现奇数次的整数
    int result[]=new int[2];
    //第1次进行整体异或运算
    int xorResult=0;
    for(int i=0;i<array.length;i++){
    
    
        xorResult^=array[i];
    }
    //如果进行异或运算的结果为0,则说明输入的数组不符合题目要求
    if(xorResult==0){
    
    
        return null;
    }
    //确定2个整数的不同位,以此来做分组
    int separator=1;
    while(0==(xorResult&separator)){
    
    
        separator<<=1;
    }
    //第2次分组进行异或运算
    for(int i=0;i<array.length;i++){
    
    
        if(0==(array[i]&separator)){
    
    
            result[0]^=array[i];
        }else{
    
    
            result[1]^=array[i];
        }
    }
    
    return result;
}

public static void main(String[] args){
    
    
    int[] array={
    
    4,1,2,2,5,1,4,3};
    int[] result=findLostNum(array);
    System.out.println(result[0]+", "+result[1]);
}

Chapter6: Business Application of Algorithms

1. Bitmap algorithm / bitmap algorithm

User label → relational database
Bitmap (a piece of memory space with a length of 10bit) Algorithm: used for deduplication and query operations on a large number of integers
Use labels to correspond to multiple users, and convert them to Bitmap for intersection/union
Algorithm Advantages: High High-performance bit operations
Reverse matching: full Bitmap (exclusive or)

static class MyBitmap{
    
    
    //每一个word是一个long类型元素,对应一个64位二进制数据
    private long[] words;
    //Bitmap的位数大小
    private int size;
    
    public MyBitmap(int size){
    
    
        this.size=size;
        this.words=new long[(getWordIndex(size-1)+1)];
    }
    
    /**
        * 判断Bitmap某一位的状态
        * @param bitIndex 位图的第bitIndex位
        * */
    public boolean getBit(int bitIndex){
    
    
        if(bitIndex<0||bitIndex>size-1){
    
    
            throw new IndexOutOfBoundsException("超过Bitmap有效范围");
        }
        int wordIndex=getWordIndex(bitIndex);
        return (words[wordIndex]&(1L<<bitIndex))!=0;
    }
    
    /**
        * 把Bitmap某一位设置为true
        * @param bitIndex 位图的第bitIndex位
        * */
    public void setBit(int bitIndex){
    
    
        if(bitIndex<0||bitIndex>size-1){
    
    
            throw new IndexOutOfBoundsException("超过Bitmap有效范围");
        }
        int wordIndex=getWordIndex(bitIndex);
        words[wordIndex]|=(1L<<bitIndex);
    }
    
    /**
        * 定位Bitmap某一位所对应的word
        * @param bitIndex 位图的第bitIndex位
        * */
    private int getWordIndex(int bitIndex){
    
    
        //右移6位,相当于除以64
        return bitIndex>>6;
    }
}

public static void main(String[] args){
    
    
    MyBitmap bitMap=new MyBitmap(128);
    bitMap.setBit(126);
    bitMap.setBit(75);
    System.out.println(bitMap.getBit(126));
    System.out.println(bitMap.getBit(78));
}

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-uP2R2Xxj-1595582632849) (comic algorithm 8.png)]

2. LRU algorithm

Use a hash table as a cache, the hash table is too large

Least Recently Used
memory management algorithm
Hypothesis: Data that has not been used for a long time is less likely to be used in the future. Therefore, when the memory occupied by the data reaches a certain threshold, we need to remove the least recently used data.
Data structure - hash list

Relying on the orderliness of the hash list, the Key-Value can be sorted according to the last usage time.
Roughly speaking, it is to add/move the most recently visited users to the end

The wheel encapsulated in Java: LinkedHashMap

static class LRUCache{
    
    
    private Node head;
    private Node end;
    //缓存存储上限
    private int limit;
    
    private HashMap<String,Node> hashMap;
    
    public LRUCache(int limit){
    
    
        this.limit=limit;
        hashMap=new HashMap<String,Node>();
    }
    
    public String get(String key){
    
    
        Node node=hashMap.get(key);
        if(node==null){
    
    
            return null;
        }
        refreshNode(node);
        return node.value;
    }
    
    public void put(String key,String value){
    
    
        Node node=hashMap.get(key);
        if(node==null){
    
    
            //如果Key不存在,则插入Key-Value
            if(hashMap.size()>=limit){
    
    
                String oldKey=removeNode(head);
                hashMap.remove(oldKey);
            }
            node=new Node(key,value);
            addNode(node);
            hashMap.put(key, node);
        }else{
    
    
            //如果Key存在,则刷新Key-Value
            node.value=value;
            refreshNode(node);
        }
    }
    
    public void remove(String key){
    
    
        Node node=hashMap.get(key);
        removeNode(node);
        hashMap.remove(key);
    }
    
    /**
        * 刷新被访问的节点位置
        * @param node 被访问的节点
        * */
    private void refreshNode(Node node){
    
    
        //如果访问的是尾节点,则无需移动节点
        if(node==end){
    
    
            return;
        }
        //移除节点
        removeNode(node);
        //重新插入节点
        addNode(node);
    }
    
    /**
        * 删除节点
        * @param node 要删除的节点
        * */
    private String removeNode(Node node){
    
    
        if(node==head&&node==end){
    
    
            //移除唯一的节点
            head=null;
            end=null;
        }else if(node==end){
    
    
            //移除尾节点
            end=end.pre;
            end.next=null;
        }else if(node==head){
    
    
            //移除头节点
            head=head.next;
            head.pre=null;
        }else{
    
    
            //移除中间节点
            node.pre.next=node.next;
            node.next.pre=node.pre;
        }
        return node.key;
    }
    
    /**
        * 尾部插入节点
        * @param node 要插入的节点
        * */
    private void addNode(Node node){
    
    
        if(end!=null){
    
    
            end.next=node;
            node.pre=end;
            node.next=null;
        }
        end=node;
        if(head==null){
    
    
            head=node;
        }
    }
    
    class Node{
    
    
        Node(String key,String value){
    
    
            this.key=key;
            this.value=value;
        }
        public Node pre;
        public Node next;
        public String key;
        public String value;
    }
}

public static void main(String[] args){
    
    
    LRUCache lruCache=new LRUCache(5);
    lruCache.put("001", "用户1信息");
    lruCache.put("002", "用户2信息");
    lruCache.put("003", "用户3信息");
    lruCache.put("004", "用户4信息");
    lruCache.put("005", "用户5信息");
    lruCache.get("002");
    lruCache.put("004", "用户4信息更新");
    lruCache.put("006", "用户6信息");
    System.out.println(lruCache.get("001"));
    System.out.println(lruCache.get("006"));
}

NOTE: This code is not thread safe

The requirements of the user system can also be realized by using the cache database Redis, and the underlying layer of Redis also implements a recycling algorithm similar to LRU.

3. A star pathfinding algorithm

A*search algorithm

Walk around obstacles from the start to the end

OpenList: The grid that can be reached
CloseList: The grid that has been reached
F=G+H
G: The cost from the starting point to the current grid, that is, how many steps have been spent.
H: Without considering obstacles, the distance from the current grid to the target grid, that is, how far away from the target.
F: The comprehensive evaluation of G and H, that is, the total number of steps from the starting point to the current grid, and then from the current grid to the target grid.

Put the starting point into OpenList →
(find the value with the smallest F value in OpenList as the current grid, move the current grid into CloseList → check the grids that can be reached from the top, bottom, left, and right sides of the current grid to see if they are in OpenList or CloseList; if not, put They join the OpenList, calculate the corresponding G, H, F values, and use the current grid as their "parent node".)
After the loop reaches the end point, it traces back along the parent node to find an optimal path

heuristic search

//迷宫地图
public static final int[][] MAZE={
    
    
    {
    
    0,0,0,0,0,0,0},
    {
    
    0,0,0,1,0,0,0},
    {
    
    0,0,0,1,0,0,0},
    {
    
    0,0,0,1,0,0,0},
    {
    
    0,0,0,0,0,0,0}
};

/**
    * A*寻路主逻辑
    * @param start 迷宫起点
    * @param end 迷宫终点
    * */
public static Grid aStarSearch(Grid start,Grid end){
    
    
    ArrayList<Grid> openList=new ArrayList<Grid>();
    ArrayList<Grid> closeList=new ArrayList<Grid>();
    //把起点加入openList
    openList.add(start);
    //主循环,每一轮检查1个当前方格节点
    while(openList.size()>0){
    
    
        //在openList中查找F值最小的节点,将其作为当前方格节点
        Grid currentGrid=findMinGird(openList);
        //将当前方格节点从openList中移除
        openList.remove(currentGrid);
        //当前方格节点进入closeList
        closeList.add(currentGrid);
        //找到所有邻近节点
        List<Grid> neighbors=findNeighbors(currentGrid,openList,closeList);
        for(Grid grid:neighbors){
    
    
            if(!openList.contains(grid)){
    
    
                //邻近节点不在openList中,标记“父节点”、G、H、F,并放入openList
                grid.initGird(currentGrid,end);
                openList.add(grid);
            }
        }
        //如果终点在openList中,直接返回终点格子
        for(Grid grid:openList){
    
    
            if((grid.x==end.x)&&(grid.y==end.y)){
    
    
                return grid;
            }
        }
    }
    //openList用尽,仍然找不到终点,说明终点不可到达,返回空
    return null;
}

private static Grid findMinGird(ArrayList<Grid> openList){
    
    
    Grid tempGrid=openList.get(0);
    for(Grid grid:openList){
    
    
        if(grid.f<tempGrid.f){
    
    
            tempGrid=grid;
        }
    }
    return tempGrid;
}

private static ArrayList<Grid> findNeighbors(Grid grid,List<Grid> openList,List<Grid> closeList){
    
    
    ArrayList<Grid> gridList=new ArrayList<Grid>();
    if(isValidGrid(grid.x,grid.y-1,openList,closeList)){
    
    
        gridList.add(new Grid(grid.x,grid.y-1));
    }
    if(isValidGrid(grid.x,grid.y+1,openList,closeList)){
    
    
        gridList.add(new Grid(grid.x,grid.y+1));
    }
    if(isValidGrid(grid.x-1,grid.y,openList,closeList)){
    
    
        gridList.add(new Grid(grid.x-1,grid.y));
    }
    if(isValidGrid(grid.x+1,grid.y,openList,closeList)){
    
    
        gridList.add(new Grid(grid.x+1,grid.y));
    }
    return gridList;
}

private static boolean isValidGrid(int x,int y,List<Grid> openList,List<Grid> closeList){
    
    
    //是否超过边界
    if(x<0||x>=MAZE.length||y<0||y>=MAZE[0].length){
    
    
        return false;
    }
    //是否有障碍物
    if(MAZE[x][y]==1){
    
    
        return false;
    }
    //是否已经在openList中
    if(containGrid(openList,x,y)){
    
    
        return false;
    }
    //是否已经在closeList中
    if(containGrid(closeList,x,y)){
    
    
        return false;
    }
    return true;
}

private static boolean containGrid(List<Grid> grids,int x,int y){
    
    
    for(Grid n:grids){
    
    
        if((n.x==x)&&(n.y==y)){
    
    
            return true;
        }
    }
    return false;
}

static class Grid{
    
    
    public int x;
    public int y;
    public int f;
    public int g;
    public int h;
    public Grid parent;
    
    public Grid(int x,int y){
    
    
        this.x=x;
        this.y=y;
    }
    
    public void initGird(Grid parent,Grid end){
    
    
        this.parent=parent;
        if(parent!=null){
    
    
            this.g=parent.g+1;
        }else{
    
    
            this.g=1;
        }
        this.h=Math.abs(this.x-end.x)+Math.abs(this.y-end.y);
        this.f=this.g+this.h;
    }
}

public static void main(String[] args){
    
    
    //设置起点和终点
    Grid startGrid=new Grid(2,1);
    Grid endGrid=new Grid(2,5);
    //搜索迷宫终点
    Grid resultGrid=aStarSearch(startGrid,endGrid);		
    //回溯迷宫路径
    ArrayList<Grid> path=new ArrayList<Grid>();
    while(resultGrid!=null){
    
    
        path.add(new Grid(resultGrid.x,resultGrid.y));
        resultGrid=resultGrid.parent;
    }
    //输出迷宫和路径,路径用*表示
    for(int i=0;i<MAZE.length;i++){
    
    
        for(int j=0;j<MAZE[0].length;j++){
    
    
            if(containGrid(path,i,j)){
    
    
                System.out.print("*,");
            }else{
    
    
                System.out.print(MAZE[i][j]+",");
            }
        }
        System.out.println();
    }
}

4. Red Envelope Algorithm

  1. The amount of red envelopes should be distributed as evenly as possible
  2. In order to avoid some problems caused by high concurrency: first calculate the amount of removal of each red envelope, and put them in a queue, and the user who receives the red envelope must find his own share in the queue.
  3. fair

Method 1: Double mean method

Limit the upper limit of each random amount to twice the remaining average amount

Assume that the remaining amount of red envelopes is m yuan, and the remaining number of people is n:
the amount grabbed each time = random interval [0.01, m/n×2-0.01] yuan

It is guaranteed that the average value of each random amount is equal

/**
    * 拆分红包
    * @param totalAmount 总金额(以分为单位)
    * @param totalPeopleNum 总人数
    * */
public static List<Integer> divideRedPackage(Integer totalAmount,Integer totalPeopleNum){
    
    
    List<Integer> amountList=new ArrayList<Integer>();
    Integer restAmount=totalAmount;
    Integer restPeopleNum=totalPeopleNum;
    Random random=new Random();
    for(int i=0;i<totalPeopleNum-1;i++){
    
    
        //随机范围:[1,剩余人均金额的2倍-1]分
        int amount=random.nextInt(restAmount/restPeopleNum*2-1)+1;
        restAmount-=amount;
        restPeopleNum--;
        amountList.add(amount);
    }
    amountList.add(restAmount);
    return amountList;
}

public static void main(String[] args){
    
    
    List<Integer> amountList=divideRedPackage(1000,10);
    for(Integer amount:amountList){
    
    
        System.out.println("抢到金额:"+new BigDecimal(amount).divide(new BigDecimal(100)));
    }
}

Method 2: Line Segment Cutting Method

rid(2,1);
Grid endGrid=new Grid(2,5);
//Search the end of the maze
Grid resultGrid=aStarSearch(startGrid,endGrid);
//Trace back the maze path
ArrayList path=new ArrayList();
while(resultGrid! =null){ path.add(new Grid(resultGrid.x,resultGrid.y)); resultGrid=resultGrid.parent; } //output the maze and path, the path is represented by for (int i=0;i<MAZE.length ;i++){ for(int j=0;j<MAZE[0].length;j++){ if(containGrid(path,i,j)){ System.out.print(" ,"); }else{ System .out.print(MAZE[i][j]+”,”); } } System.out.println(); } }















## 4. 红包算法
1. 红包金额尽可能分布均衡
2. 为了避免高并发引起的一些问题:先计算好每个红包拆除的金额,并把它们放到一个队列里,领取红包的用户要在队列中找到属于自己的那一份。
3. 公平

### 方法1:二倍均值法
把每次随机金额的上限定为剩余人均金额的两倍

假设剩余红包金额为m元,剩余人数为n:
每次抢到的金额=随机区间[0.01, m/n×2-0.01]元

保证了每次随机金额的均值是相等的

```java
/**
    * 拆分红包
    * @param totalAmount 总金额(以分为单位)
    * @param totalPeopleNum 总人数
    * */
public static List<Integer> divideRedPackage(Integer totalAmount,Integer totalPeopleNum){
    List<Integer> amountList=new ArrayList<Integer>();
    Integer restAmount=totalAmount;
    Integer restPeopleNum=totalPeopleNum;
    Random random=new Random();
    for(int i=0;i<totalPeopleNum-1;i++){
        //随机范围:[1,剩余人均金额的2倍-1]分
        int amount=random.nextInt(restAmount/restPeopleNum*2-1)+1;
        restAmount-=amount;
        restPeopleNum--;
        amountList.add(amount);
    }
    amountList.add(restAmount);
    return amountList;
}

public static void main(String[] args){
    List<Integer> amountList=divideRedPackage(1000,10);
    for(Integer amount:amountList){
        System.out.println("抢到金额:"+new BigDecimal(amount).divide(new BigDecimal(100)));
    }
}

Method 2: Line Segment Cutting Method

Imagine the total amount of red envelopes as a growth line, and the amount that each person grabs is a sub-line segment that is split. The length of each segment is determined by the "cut point".

Guess you like

Origin blog.csdn.net/PolarisRisingWar/article/details/107565861