├── .gitignore ├── README.md ├── array ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ └── SuffixArray.java ├── binary ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ └── BinaryTest.java ├── binarySearch ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ └── BinarySearch.java ├── cyclefinding ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ └── CycleFinding.java ├── hashTable ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ ├── DoubleHashingProbingHashMap.java │ ├── LinearProbingHashMap.java │ ├── QuadraticProbingHashMap.java │ └── SeperateChainHashMap.java ├── heap ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ ├── BinaryHeap.java │ └── HeapSort.java ├── leecode ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ ├── easy │ ├── NO110isBalanced.java │ ├── No100isSameTree.java │ ├── No101isSymmetricTree.java │ ├── No104maxDepth.java │ ├── No107levelOrderBottom.java │ ├── No108sortedArrayToBST.java │ ├── No111minDepth.java │ ├── No112hasPathSum.java │ ├── No118yanghui.java │ ├── No119yanghui2.java │ ├── No121maxProfit.java │ ├── No122maxProfit.java │ ├── No125isPalindrome.java │ ├── No136singleNumber.java │ ├── No13RomanToInt.java │ ├── No141hasCycle.java │ ├── No14LongestCommonPrefix.java │ ├── No155MinStack.java │ ├── No155MinStack2.java │ ├── No1TwoSum.java │ ├── No1TwoSum2.java │ ├── No20IsValidBracket.java │ ├── No21mergeTwoLists.java │ ├── No26removeDuplicates.java │ ├── No27removeElement.java │ ├── No28StrStr.java │ ├── No35searchInsert.java │ ├── No38countAndSay.java │ ├── No53maxSubArray.java │ ├── No58lengthOfLastWord.java │ ├── No66plusOne.java │ ├── No67addBinary.java │ ├── No69mySqrt.java │ ├── No70climbStairs.java │ ├── No7ReverseInt.java │ ├── No83deleteDuplicates.java │ ├── No88mergeTwoArray.java │ └── No9IsPalindrome.java │ └── middle │ ├── No102levelOrderForTree.java │ └── No144preorderTraversal.java ├── list ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ ├── DoublyLinkedList.java │ └── LinkedList.java ├── pom.xml ├── queue ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ ├── ArrayDeQueue.java │ ├── ArrayQueue.java │ ├── DyncArrayDeQueue.java │ ├── DyncArrayQueue.java │ ├── LinkedListDeQueue.java │ ├── LinkedListQueue.java │ └── PriorityQueueTest.java ├── recursion ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ ├── CoinChange.java │ ├── FactorialNumber.java │ ├── Fibonacci.java │ ├── GCD.java │ ├── Knapsack.java │ ├── LongestSubSeq.java │ ├── NChooseK.java │ └── TravelingSalesmen.java ├── sorting ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ ├── BubbleSort.java │ ├── BubbleSort1.java │ ├── BubbleSort2.java │ ├── CountingSort.java │ ├── CountingSort1.java │ ├── InsertionSort.java │ ├── MergeSort.java │ ├── QuickSort.java │ ├── RadixSort.java │ ├── RandomQuickSort.java │ ├── SelectionSort.java │ └── SelectionSort1.java ├── stack ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ ├── ArrayStack.java │ ├── DyncArrayStack.java │ └── LinkedListStack.java ├── stringMatch ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── flydean │ │ └── stringmatch │ │ ├── KMP.java │ │ └── KMPTest.java │ └── resources │ └── application.properties ├── tree ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── flydean │ ├── AVLTree.java │ ├── BinarySearchTree.java │ ├── FenwickTree.java │ ├── MaxSegmentTree.java │ ├── MinSegmentTree.java │ ├── RecurTree.java │ ├── SuffixTree.java │ ├── SuffixTrieNode.java │ └── TernaryTree.java └── trie ├── pom.xml └── src └── main └── java └── com └── flydean └── Trie.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | 33 | .DS_Store 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # learn-algorithm 2 | 跟我一起学算法 3 | 4 | 算法是一切的基础,我会用图文+动画+代码的方式来详细讲解常用的算法。 5 | 6 | 希望能够带领大家进入算法的精彩世界。 7 | 8 | 如果喜欢的话,请大家 star ![Github stars](https://img.shields.io/github/stars/ddean2009/learn-algorithm.svg)一下,谢谢 9 | 10 | # 目前已完成的部分: 11 | 12 | * [看动画学算法之:排序-冒泡排序](http://www.flydean.com/algorithm-bubble-sort/) 13 | * [看动画学算法之:排序-插入排序](http://www.flydean.com/algorithm-insertion-sort/) 14 | * [看动画学算法之:排序-选择排序](http://www.flydean.com/algorithm-selection-sort/) 15 | * [看动画学算法之:排序-归并排序](http://www.flydean.com/algorithm-merge-sort/) 16 | * [看动画学算法之:排序-快速排序](http://www.flydean.com/algorithm-quick-sort/) 17 | * [看动画学算法之:排序-count排序](http://www.flydean.com/algorithm-count-sort/) 18 | * [看动画学算法之:排序-基数排序](http://www.flydean.com/algorithm-radix-sort/) 19 | -------------------------------------------------------------------------------- /array/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | array 13 | 14 | 15 | -------------------------------------------------------------------------------- /binary/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /binary/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.example 8 | learn-algorithm 9 | 1.0-SNAPSHOT 10 | 11 | 12 | com.flydean 13 | binary 14 | 15 | 16 | 17 17 | 17 18 | UTF-8 19 | 20 | 21 | -------------------------------------------------------------------------------- /binary/src/main/java/com/flydean/BinaryTest.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | 4 | public class BinaryTest { 5 | 6 | public static void main(String[] args) { 7 | int a = 100; 8 | int b =212; 9 | System.out.println(a^b); 10 | Integer.reverse(122); 11 | Integer.bitCount(112); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /binarySearch/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /binarySearch/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.example 8 | learn-algorithm 9 | 1.0-SNAPSHOT 10 | 11 | 12 | com.flydean 13 | binarySearch 14 | 15 | 16 | 17 17 | 17 18 | UTF-8 19 | 20 | 21 | -------------------------------------------------------------------------------- /binarySearch/src/main/java/com/flydean/BinarySearch.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | public class BinarySearch { 4 | 5 | public static void main(String[] args) { 6 | int[] nums = new int[]{1,3,3,3,5}; 7 | BinarySearch bs = new BinarySearch(); 8 | System.out.println(bs.searchInsertLeftMost(nums, 2)); 9 | System.out.println(bs.searchInsertRightMost(nums, 3)); 10 | } 11 | 12 | public int searchInsertLeftMost(int[] nums, int target) { 13 | int left=0; 14 | int right = nums.length-1; 15 | while(left <=right){ 16 | int mid=(left+right)/2; 17 | if(nums[mid] 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | cyclefinding 13 | 14 | 15 | -------------------------------------------------------------------------------- /cyclefinding/src/main/java/com/flydean/CycleFinding.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version CycleFinding, 2020/8/18 6 | */ 7 | public class CycleFinding { 8 | 9 | public static int entryStep; 10 | public static int entryPoint; 11 | public static int cycleLongth; 12 | 13 | //环路生成函数 14 | //以这样的形式来生成数据:{x0, x1 = f(x0), x2 = f(x1), ..., xi = f(xi-1), ...} 15 | public static int f(int x){ 16 | return (3*x*x+7*x+5)%97; 17 | } 18 | 19 | //弗洛伊德兔子乌龟-环检测算法 20 | public static void floydCycleFinding(int x){ 21 | //第一步:让乌龟和兔子相遇 22 | // 我们定义两个值,一个是乌龟=f(x),一个是兔子,因为兔子的速度两倍于乌龟,所以我们可以用f(f(x))来表示兔子的值。 23 | int tortoise = f(x), hare = f(f(x)); 24 | //然后乌龟和兔子一直向前走,直到他们的值相等,表明两者相遇了 25 | //注意,相遇点并不是环的入口点 26 | while (tortoise != hare) { 27 | tortoise = f(tortoise); 28 | hare = f(f(hare)); 29 | } 30 | //第二步:找到环的入口点 31 | //让兔子重新从起点开始起跑,步长和乌龟一致,而乌龟继续原来的位置向后走,当两者相遇的点,就是环的入口点 32 | entryStep = 0; hare = x; 33 | while (tortoise != hare) { 34 | tortoise = f(tortoise); 35 | hare = f(hare); entryStep++; 36 | } 37 | entryPoint= tortoise; 38 | 39 | //第三步:找到环的步长 40 | //让兔子继续向前走,乌龟不动,当两者再次相遇的时候,就找到了步长 41 | cycleLongth = 1; hare = f(tortoise); 42 | while (tortoise != hare) { 43 | hare = f(hare); cycleLongth++; 44 | } 45 | } 46 | 47 | public static void main(String[] args) { 48 | floydCycleFinding(62); 49 | System.out.printf("entryPoint: %d,cycleLongth %d\n", entryPoint, cycleLongth); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /hashTable/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | hashTable 13 | 14 | 15 | -------------------------------------------------------------------------------- /hashTable/src/main/java/com/flydean/DoubleHashingProbingHashMap.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * 双倍散列 i =(base + step * h2(v))%M 5 | * @author wayne 6 | * @version DoubleHashingProbingHashMap 7 | */ 8 | public class DoubleHashingProbingHashMap { 9 | 10 | //存储数据的节点 11 | static class HashNode 12 | { 13 | public int value; 14 | public int key; 15 | 16 | HashNode(int key, int value) 17 | { 18 | this.value = value; 19 | this.key = key; 20 | } 21 | } 22 | 23 | //hashNode数组,用来存储hashMap的数据 24 | HashNode[] hashNodes; 25 | int capacity; 26 | int size; 27 | final int PRIME =7; 28 | //代表空节点 29 | HashNode dummy; 30 | 31 | public DoubleHashingProbingHashMap(){ 32 | this.capacity=20; 33 | this.size=0; 34 | hashNodes= new HashNode[capacity]; 35 | //创建一个dummy节点,供删除使用 36 | dummy= new HashNode(-1,-1); 37 | } 38 | 39 | // 计算第一次hash 40 | int hash1(int key) 41 | { 42 | return (key % capacity); 43 | } 44 | 45 | // 计算第二次hash 46 | int hash2(int key) 47 | { 48 | return (PRIME - (key % capacity)); 49 | } 50 | 51 | //插入节点 52 | void insertNode(int key, int value) 53 | { 54 | HashNode temp = new HashNode(key, value); 55 | 56 | //获取key的hashcode 57 | int hashIndex = hash1(key); 58 | 59 | //find next free space 60 | int i=1; 61 | while(hashNodes[hashIndex] != null && hashNodes[hashIndex].key != key 62 | && hashNodes[hashIndex].key != -1) 63 | { 64 | hashIndex=hashIndex+i*hash2(key); 65 | hashIndex %= capacity; 66 | i++; 67 | } 68 | 69 | //插入新节点,size+1 70 | if(hashNodes[hashIndex] == null || hashNodes[hashIndex].key == -1) { 71 | size++; 72 | } 73 | //将新节点插入数组 74 | hashNodes[hashIndex] = temp; 75 | } 76 | 77 | //删除节点 78 | int deleteNode(int key) 79 | { 80 | // 获取key的hashcode 81 | int hashIndex = hash1(key); 82 | 83 | //finding the node with given key 84 | int i=1; 85 | while(hashNodes[hashIndex] != null) 86 | { 87 | //找到了要删除的节点 88 | if(hashNodes[hashIndex].key == key) 89 | { 90 | HashNode temp = hashNodes[hashIndex]; 91 | 92 | //插入dummy节点,表示已经被删除 93 | hashNodes[hashIndex] = dummy; 94 | 95 | // size -1 96 | size--; 97 | return temp.value; 98 | } 99 | hashIndex=hashIndex+i*hash2(key); 100 | hashIndex %= capacity; 101 | i++; 102 | } 103 | //如果没有找到,返回-1 104 | return -1; 105 | } 106 | 107 | //获取节点 108 | int get(int key) 109 | { 110 | int hashIndex = hash1(key); 111 | int counter=0; 112 | //找到我们的节点 113 | int i=0; 114 | while(hashNodes[hashIndex] != null) 115 | { 116 | if(counter++>capacity) //设置遍历的边界条件,防止无限循环 117 | return -1; 118 | //找到了 119 | if(hashNodes[hashIndex].key == key){ 120 | return hashNodes[hashIndex].value; 121 | } 122 | hashIndex=hashIndex+i*hash2(key); 123 | hashIndex %= capacity; 124 | i++; 125 | } 126 | //没找到,返回-1 127 | return -1; 128 | } 129 | 130 | //Return current size 131 | int sizeofMap() 132 | { 133 | return size; 134 | } 135 | 136 | //Return true if size is 0 137 | boolean isEmpty() 138 | { 139 | return size == 0; 140 | } 141 | 142 | void display() 143 | { 144 | for(int i=0 ; i 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | heap 13 | 14 | 15 | -------------------------------------------------------------------------------- /heap/src/main/java/com/flydean/BinaryHeap.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version BinaryHeap, 2020/7/22 6 | */ 7 | public class BinaryHeap { 8 | 9 | //底层存储数组用来存储数据 10 | private int[] intArray; 11 | //heap大小 12 | private int heapSize; 13 | //数组容量 14 | private int capacity; 15 | 16 | //获取某个index的父节点 17 | private int parent(int i) { return (i-1)>>1; } // (i-1)/2 18 | //获取某个index的左子节点 19 | private int leftChild(int i) { return i<<1+1; } // i*2+1 20 | //获取某个index的右子节点 21 | private int rightChild(int i) { return (i<<1)+2; } // i*2+2 22 | 23 | public BinaryHeap(int capacity) { 24 | this.capacity= capacity; 25 | intArray = new int[capacity]; 26 | intArray[0]=-1; 27 | heapSize = 0; 28 | } 29 | 30 | //交换数组中的两个index 31 | private void swap(int[] array, int i, int j) { 32 | int temp = array[i]; 33 | array[i]=array[j]; 34 | array[j]=temp; 35 | } 36 | 37 | //递归shift某个节点,将大值移动到最上面 38 | private void shiftUp(int i) { 39 | if (i == 0) return; // 如果是root,则不作操作 40 | if (intArray[i] > intArray[parent(i)]) { // 子节点大于父节点,则需要进行swap操作 41 | swap(intArray, i, parent(i)); // 将子节点和父节点进行互换 42 | shiftUp(parent(i)); // 递归shift父节点 43 | } 44 | } 45 | 46 | //递归shift某个节点,将小值移动到最下面 47 | //比较过程是先比较左子节点,再比较右子节点 48 | private void shiftDown(int i) { 49 | if (i >= heapSize) return; // 超出了数组存储范围,直接返回 50 | int swapId = i; //要互换的id 51 | //如果存在左子节点,并且当前节点小于左子节点,则将要互换的id设置为左子节点 52 | if (leftChild(i) <= heapSize-1 && intArray[i] < intArray[leftChild(i)]){ 53 | swapId = leftChild(i); 54 | } 55 | //如果存在右子节点,并且互换的id小于右子节点,则将互换的id设置为右子节点 56 | if (rightChild(i) <= heapSize-1 && intArray[swapId] < intArray[rightChild(i)]){ 57 | swapId = rightChild(i); 58 | } 59 | if (swapId != i) { // 需要互换 60 | swap(intArray, i, swapId); // 进行互换 61 | shiftDown(swapId); // 递归要互换的节点 62 | } 63 | } 64 | 65 | public boolean isFull(){ 66 | return heapSize == intArray.length; 67 | } 68 | 69 | //扩展heap,这里我们采用倍增的方式 70 | private void expandHeap(){ 71 | int[] expandedArray = new int[capacity* 2]; 72 | System.arraycopy(intArray,0, expandedArray,0, capacity); 73 | capacity= capacity*2; 74 | intArray= expandedArray; 75 | } 76 | 77 | //heap插入,插入到最后的位置,然后进行shift up操作 78 | public void insert(int x) { 79 | if(isFull()){ 80 | expandHeap(); 81 | } 82 | heapSize++; 83 | intArray[heapSize-1]= x; // 插入最后位置 84 | shiftUp(heapSize-1); // shift up 85 | 86 | } 87 | 88 | //获取最大的数据, 89 | public int extractMax() { 90 | int taken = intArray[0]; //root 就是最大的值 91 | swap(intArray, 0, heapSize-1); // 将root和最后一个值进行互换 92 | heapSize--; 93 | shiftDown(0); // 递归执行shift Down操作 94 | return taken; // 返回root的值 95 | } 96 | 97 | public int getMax() { 98 | return intArray[0]; 99 | } 100 | 101 | public Boolean isEmpty() { 102 | return heapSize == 0; 103 | } 104 | 105 | public void printHeap(){ 106 | for(int totalIndex=0;totalIndex= 0; i--) 15 | shiftDown(arr, n, i); 16 | 17 | // 一个一个取出最大的元素,并放置在数组的末尾 18 | for (int i=n-1; i>0; i--) 19 | { 20 | //将最大的元素放在数组末尾 21 | int temp = arr[0]; 22 | arr[0] = arr[i]; 23 | arr[i] = temp; 24 | 25 | // 重新构建新的二叉堆 26 | shiftDown(arr, i, 0); 27 | } 28 | } 29 | 30 | // shift down arr, 需要传入arraySize和开始的nodeIndex 31 | void shiftDown(int[] arr, int arraySize, int nodeIndex) 32 | { 33 | int largest = nodeIndex; // 最大值的index 34 | int left = 2*nodeIndex + 1; // left = 2*i + 1 35 | int right = 2*nodeIndex + 2; // right = 2*i + 2 36 | 37 | // 如果left子节点大于父节点 38 | if (left < arraySize && arr[left] > arr[largest]) 39 | largest = left; 40 | 41 | // 如果right子节点大于父节点 42 | if (right < arraySize && arr[right] > arr[largest]) 43 | largest = right; 44 | 45 | // 需要交换 46 | if (largest != nodeIndex) 47 | { 48 | int swap = arr[nodeIndex]; 49 | arr[nodeIndex] = arr[largest]; 50 | arr[largest] = swap; 51 | 52 | // 递归交换子节点 53 | shiftDown(arr, arraySize, largest); 54 | } 55 | } 56 | 57 | //输出数组 58 | static void printArray(int[] arr) 59 | { 60 | int n = arr.length; 61 | for (int i=0; i 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | leecode 13 | 14 | 15 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/NO110isBalanced.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import com.flydean.middle.No102levelOrderForTree; 4 | 5 | /** 6 | * @author wayne 7 | * @version NO110isBalanced, 2020/8/26 8 | * 9 | * 平衡二叉树 10 | * 给定一个二叉树,判断它是否是高度平衡的二叉树。 11 | * 12 | * 本题中,一棵高度平衡二叉树定义为: 13 | * 14 | * 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 15 | * 16 | * 示例 1: 17 | * 18 | * 给定二叉树 [3,9,20,null,null,15,7] 19 | * 20 | * 3 21 | * / \ 22 | * 9 20 23 | * / \ 24 | * 15 7 25 | * 返回 true 。 26 | * 27 | * 示例 2: 28 | * 29 | * 给定二叉树 [1,2,2,3,3,null,null,4,4] 30 | * 31 | * 1 32 | * / \ 33 | * 2 2 34 | * / \ 35 | * 3 3 36 | * / \ 37 | * 4 4 38 | * 返回 false 。 39 | */ 40 | public class NO110isBalanced { 41 | 42 | /** 43 | * 方法一:自顶向下的递归 44 | * 二叉树的每个节点的左右子树的高度差的绝对值不超过 11,则二叉树是平衡二叉树。 45 | * 根据定义,一棵二叉树是平衡二叉树,当且仅当其所有子树也都是平衡二叉树,因此可以使用递归的方式判断二叉树是不是平衡二叉树, 46 | * 递归的顺序可以是自顶向下或者自底向上。 47 | * 时间复杂度:O(n ^ 2),其中 n 是二叉树中的节点个数。 48 | * 空间复杂度:O(n),其中 n 是二叉树中的节点个数。空间复杂度主要取决于递归调用的层数,递归调用的层数不会超过 n。 49 | */ 50 | 51 | public boolean isBalanced1(TreeNode root) { 52 | if (root == null) { 53 | return true; 54 | } else { 55 | return Math.abs(height1(root.left) - height1(root.right)) <= 1 && isBalanced1(root.left) && isBalanced1(root.right); 56 | } 57 | } 58 | 59 | public int height1(TreeNode root) { 60 | if (root == null) { 61 | return 0; 62 | } else { 63 | return Math.max(height1(root.left), height1(root.right)) + 1; 64 | } 65 | } 66 | 67 | /** 68 | * 方法二:自底向上的递归 69 | * 方法一由于是自顶向下递归,因此对于同一个节点,函数 height 会被重复调用,导致时间复杂度较高。 70 | * 如果使用自底向上的做法,则对于每个节点,函数 height 只会被调用一次。 71 | * 72 | * 自底向上递归的做法类似于后序遍历,对于当前遍历到的节点,先递归地判断其左右子树是否平衡,再判断以当前节点为根的子树是否平衡。 73 | * 如果一棵子树是平衡的,则返回其高度(高度一定是非负整数),否则返回 −1。如果存在一棵子树不平衡,则整个二叉树一定不平衡。 74 | * 75 | * 时间复杂度:O(n),其中 n 是二叉树中的节点个数。使用自底向上的递归, 76 | * 每个节点的计算高度和判断是否平衡都只需要处理一次,最坏情况下需要遍历二叉树中的所有节点,因此时间复杂度是 O(n)。 77 | * 78 | * 空间复杂度:O(n),其中 n 是二叉树中的节点个数。空间复杂度主要取决于递归调用的层数,递归调用的层数不会超过 n。 79 | * 80 | */ 81 | 82 | public boolean isBalanced2(TreeNode root) { 83 | return height2(root) >= 0; 84 | } 85 | 86 | public int height2(TreeNode root) { 87 | if (root == null) { 88 | return 0; 89 | } 90 | int leftHeight = height2(root.left); 91 | int rightHeight = height2(root.right); 92 | if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1) { 93 | return -1; 94 | } else { 95 | return Math.max(leftHeight, rightHeight) + 1; 96 | } 97 | } 98 | 99 | 100 | public static class TreeNode { 101 | int val; 102 | TreeNode left; 103 | TreeNode right; 104 | TreeNode(int x) { val = x; } 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No100isSameTree.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import javax.swing.tree.TreeNode; 4 | import java.util.LinkedList; 5 | import java.util.Queue; 6 | 7 | /** 8 | * @author wayne 9 | * @version No100isSameTree, 2020/8/26 10 | * 相同的树 11 | * 给定两个二叉树,编写一个函数来检验它们是否相同。 12 | * 13 | * 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 14 | * 15 | * 示例 1: 16 | * 17 | * 输入: 1 1 18 | * / \ / \ 19 | * 2 3 2 3 20 | * 21 | * [1,2,3], [1,2,3] 22 | * 23 | * 输出: true 24 | * 示例 2: 25 | * 26 | * 输入: 1 1 27 | * / \ 28 | * 2 2 29 | * 30 | * [1,2], [1,null,2] 31 | * 32 | * 输出: false 33 | * 示例 3: 34 | * 35 | * 输入: 1 1 36 | * / \ / \ 37 | * 2 1 1 2 38 | * 39 | * [1,2,1], [1,1,2] 40 | * 41 | * 输出: false 42 | */ 43 | public class No100isSameTree { 44 | 45 | /** 46 | * 方法一:深度优先搜索 47 | * 如果两个二叉树都为空,则两个二叉树相同。如果两个二叉树中有且只有一个为空,则两个二叉树一定不相同。 48 | * 49 | * 如果两个二叉树都不为空,那么首先判断它们的根节点的值是否相同,若不相同则两个二叉树一定不同,若相同,再分别判断两个二叉树的左子树是否相同以及右子树是否相同。这是一个递归的过程,因此可以使用深度优先搜索,递归地判断两个二叉树是否相同。 50 | */ 51 | 52 | public boolean isSameTree(TreeNode p, TreeNode q) { 53 | if (p == null && q == null) { 54 | return true; 55 | } else if (p == null || q == null) { 56 | return false; 57 | } else if (p.val != q.val) { 58 | return false; 59 | } else { 60 | return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); 61 | } 62 | } 63 | 64 | /** 65 | * 方法二:广度优先搜索 66 | * 也可以通过广度优先搜索判断两个二叉树是否相同。同样首先判断两个二叉树是否为空,如果两个二叉树都不为空,则从两个二叉树的根节点开始广度优先搜索。 67 | * 68 | * 使用两个队列分别存储两个二叉树的节点。初始时将两个二叉树的根节点分别加入两个队列。每次从两个队列各取出一个节点,进行如下比较操作。 69 | * 70 | * 比较两个节点的值,如果两个节点的值不相同则两个二叉树一定不同; 71 | * 72 | * 如果两个节点的值相同,则判断两个节点的子节点是否为空,如果只有一个节点的左子节点为空,或者只有一个节点的右子节点为空,则两个二叉树的结构不同,因此两个二叉树一定不同; 73 | * 74 | * 如果两个节点的子节点的结构相同,则将两个节点的非空子节点分别加入两个队列,子节点加入队列时需要注意顺序,如果左右子节点都不为空,则先加入左子节点,后加入右子节点。 75 | * 76 | * 如果搜索结束时两个队列同时为空,则两个二叉树相同。如果只有一个队列为空,则两个二叉树的结构不同,因此两个二叉树不同。 77 | */ 78 | 79 | public boolean isSameTree2(TreeNode p, TreeNode q) { 80 | if (p == null && q == null) { 81 | return true; 82 | } else if (p == null || q == null) { 83 | return false; 84 | } 85 | Queue queue1 = new LinkedList<>(); 86 | Queue queue2 = new LinkedList<>(); 87 | queue1.offer(p); 88 | queue2.offer(q); 89 | while (!queue1.isEmpty() && !queue2.isEmpty()) { 90 | TreeNode node1 = queue1.poll(); 91 | TreeNode node2 = queue2.poll(); 92 | if (node1.val != node2.val) { 93 | return false; 94 | } 95 | TreeNode left1 = node1.left, right1 = node1.right, left2 = node2.left, right2 = node2.right; 96 | if (left1 == null ^ left2 == null) { 97 | return false; 98 | } 99 | if (right1 == null ^ right2 == null) { 100 | return false; 101 | } 102 | if (left1 != null) { 103 | queue1.offer(left1); 104 | } 105 | if (right1 != null) { 106 | queue1.offer(right1); 107 | } 108 | if (left2 != null) { 109 | queue2.offer(left2); 110 | } 111 | if (right2 != null) { 112 | queue2.offer(right2); 113 | } 114 | } 115 | return queue1.isEmpty() && queue2.isEmpty(); 116 | } 117 | 118 | 119 | public static class TreeNode { 120 | int val; 121 | TreeNode left; 122 | TreeNode right; 123 | 124 | TreeNode() { 125 | } 126 | 127 | TreeNode(int val) { 128 | this.val = val; 129 | } 130 | 131 | TreeNode(int val, TreeNode left, TreeNode right) { 132 | this.val = val; 133 | this.left = left; 134 | this.right = right; 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No101isSymmetricTree.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * @author wayne 8 | * @version No101isSymmetricTree, 2020/8/26 9 | * 对称二叉树 10 | * 11 | * 给定一个二叉树,检查它是否是镜像对称的。 12 | * 13 | * 14 | * 15 | * 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 16 | * 17 | * 1 18 | * / \ 19 | * 2 2 20 | * / \ / \ 21 | * 3 4 4 3 22 | * 23 | * 24 | * 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 25 | * 26 | * 1 27 | * / \ 28 | * 2 2 29 | * \ \ 30 | * 3 3 31 | * 32 | * 33 | * 进阶: 34 | * 35 | * 你可以运用递归和迭代两种方法解决这个问题吗? 36 | * 37 | */ 38 | public class No101isSymmetricTree { 39 | 40 | /** 41 | * 方法一:递归 42 | * 如果一个树的左子树与右子树镜像对称,那么这个树是对称的。 43 | * 因此,该问题可以转化为:两个树在什么情况下互为镜像? 44 | * 45 | * 如果同时满足下面的条件,两个树互为镜像: 46 | * 47 | * 它们的两个根结点具有相同的值 48 | * 每个树的右子树都与另一个树的左子树镜像对称 49 | *我们可以实现这样一个递归函数,通过「同步移动」两个指针的方法来遍历这棵树, 50 | * p 指针和 q 指针一开始都指向这棵树的根,随后 p 右移时,q 左移,p 左移时,q 右移。 51 | * 每次检查当前 p 和 q 节点的值是否相等,如果相等再判断左右子树是否对称。 52 | * 53 | */ 54 | 55 | public boolean isSymmetric1(TreeNode root) { 56 | return check1(root, root); 57 | } 58 | 59 | public boolean check1(TreeNode p, TreeNode q) { 60 | if (p == null && q == null) { 61 | return true; 62 | } 63 | if (p == null || q == null) { 64 | return false; 65 | } 66 | return p.val == q.val && check1(p.left, q.right) && check1(p.right, q.left); 67 | } 68 | 69 | /** 70 | * 方法二:迭代 71 | * 思路和算法 72 | * 73 | * 「方法一」中我们用递归的方法实现了对称性的判断,那么如何用迭代的方法实现呢? 74 | * 首先我们引入一个队列,这是把递归程序改写成迭代程序的常用方法。初始化时我们把根节点入队两次。 75 | * 每次提取两个结点并比较它们的值(队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像), 76 | * 然后将两个结点的左右子结点按相反的顺序插入队列中。 77 | * 当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。 78 | * 79 | */ 80 | public boolean isSymmetric(TreeNode root) { 81 | return check(root, root); 82 | } 83 | 84 | public boolean check(TreeNode u, TreeNode v) { 85 | Queue q = new LinkedList<>(); 86 | q.offer(u); 87 | q.offer(v); 88 | while (!q.isEmpty()) { 89 | u = q.poll(); 90 | v = q.poll(); 91 | if (u == null && v == null) { 92 | continue; 93 | } 94 | if ((u == null || v == null) || (u.val != v.val)) { 95 | return false; 96 | } 97 | 98 | q.offer(u.left); 99 | q.offer(v.right); 100 | 101 | q.offer(u.right); 102 | q.offer(v.left); 103 | } 104 | return true; 105 | } 106 | 107 | 108 | 109 | public static class TreeNode { 110 | int val; 111 | TreeNode left; 112 | TreeNode right; 113 | TreeNode(int x) { val = x; } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No104maxDepth.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * @author wayne 8 | * @version No104maxDepth, 2020/8/26 9 | * 10 | * 二叉树的最大深度 11 | * 给定一个二叉树,找出其最大深度。 12 | * 13 | * 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 14 | * 15 | * 说明: 叶子节点是指没有子节点的节点。 16 | * 17 | * 示例: 18 | * 给定二叉树 [3,9,20,null,null,15,7], 19 | * 20 | * 3 21 | * / \ 22 | * 9 20 23 | * / \ 24 | * 15 7 25 | * 返回它的最大深度 3 。 26 | */ 27 | public class No104maxDepth { 28 | 29 | /** 30 | * 方法一:递归 31 | * 思路与算法 32 | * 33 | * 如果我们知道了左子树和右子树的最大深度 ll 和 rr,那么该二叉树的最大深度即为 34 | * max(l,r)+1 35 | * 36 | * 而左子树和右子树的最大深度又可以以同样的方式进行计算。 37 | * 因此我们在计算当前二叉树的最大深度时,可以先递归计算出其左子树和右子树的最大深度, 38 | * 然后在 O(1) 时间内计算出当前二叉树的最大深度。递归在访问到空节点时退出。 39 | */ 40 | 41 | public int maxDepth(TreeNode root) { 42 | if (root == null) { 43 | return 0; 44 | } else { 45 | int leftHeight = maxDepth(root.left); 46 | int rightHeight = maxDepth(root.right); 47 | return Math.max(leftHeight, rightHeight) + 1; 48 | } 49 | } 50 | 51 | 52 | /** 53 | * 方法二:广度优先搜索 54 | * 思路与算法 55 | * 56 | * 我们也可以用「广度优先搜索」的方法来解决这道题目,但我们需要对其进行一些修改, 57 | * 此时我们广度优先搜索的队列里存放的是「当前层的所有节点」。 58 | * 每次拓展下一层的时候,不同于广度优先搜索的每次只从队列里拿出一个节点,我们需要将队列里的所有节点都拿出来进行拓展, 59 | * 这样能保证每次拓展完的时候队列里存放的是当前层的所有节点, 60 | * 即我们是一层一层地进行拓展,最后我们用一个变量 ans 来维护拓展的次数,该二叉树的最大深度即为 ans。 61 | * 62 | */ 63 | 64 | public int maxDepth2(TreeNode root) { 65 | if (root == null) { 66 | return 0; 67 | } 68 | Queue queue = new LinkedList<>(); 69 | queue.offer(root); 70 | int ans = 0; 71 | while (!queue.isEmpty()) { 72 | int size = queue.size(); 73 | while (size > 0) { 74 | TreeNode node = queue.poll(); 75 | if (node.left != null) { 76 | queue.offer(node.left); 77 | } 78 | if (node.right != null) { 79 | queue.offer(node.right); 80 | } 81 | size--; 82 | } 83 | ans++; 84 | } 85 | return ans; 86 | } 87 | 88 | 89 | public static class TreeNode { 90 | int val; 91 | TreeNode left; 92 | TreeNode right; 93 | TreeNode(int x) { val = x; } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No107levelOrderBottom.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.ArrayList; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | 7 | /** 8 | * @author wayne 9 | * @version No107levelOrderBottom, 2020/8/26 10 | * 11 | * 二叉树的层次遍历 II 12 | * 13 | * 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 14 | * 15 | * 例如: 16 | * 给定二叉树 [3,9,20,null,null,15,7], 17 | * 18 | * 3 19 | * / \ 20 | * 9 20 21 | * / \ 22 | * 15 7 23 | * 返回其自底向上的层次遍历为: 24 | * 25 | * [ 26 | * [15,7], 27 | * [9,20], 28 | * [3] 29 | * ] 30 | */ 31 | public class No107levelOrderBottom { 32 | 33 | /** 34 | * 和 No102,很类似, 这里我们使用 lists.add(0,list);添加到list中的第一个中去,从而保证了反向顺序。 35 | * @param root 36 | * @return 37 | */ 38 | public List> levelOrderBottom(TreeNode root) { 39 | List> lists = new LinkedList<>(); 40 | if (root == null) { 41 | return lists; 42 | } 43 | List nodes = new LinkedList<>(); 44 | nodes.add(root); 45 | while (!nodes.isEmpty()) { 46 | int size = nodes.size(); 47 | List list = new ArrayList<>(); 48 | for (int i = 0; i < size; i++) { 49 | TreeNode remove = nodes.remove(0); 50 | list.add(remove.val); 51 | if (remove.left != null) { 52 | nodes.add(remove.left); 53 | } 54 | if (remove.right != null) { 55 | nodes.add(remove.right); 56 | } 57 | } 58 | lists.add(0,list); 59 | } 60 | return lists; 61 | } 62 | 63 | public static class TreeNode { 64 | int val; 65 | TreeNode left; 66 | TreeNode right; 67 | TreeNode(int x) { val = x; } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No108sortedArrayToBST.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No108sortedArrayToBST, 2020/8/26 6 | * 7 | * 将有序数组转换为二叉搜索树 8 | * 9 | * 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 10 | * 11 | * 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 12 | * 13 | * 示例: 14 | * 15 | * 给定有序数组: [-10,-3,0,5,9], 16 | * 17 | * 一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: 18 | * 19 | * 0 20 | * / \ 21 | * -3 9 22 | * / / 23 | * -10 5 24 | */ 25 | public class No108sortedArrayToBST { 26 | 27 | /** 28 | * 我们可以选择中间数字作为二叉搜索树的根节点,这样分给左右子树的数字个数相同或只相差 1, 29 | * 可以使得树保持平衡。如果数组长度是奇数,则根节点的选择是唯一的, 30 | * 如果数组长度是偶数,则可以选择中间位置左边的数字作为根节点或者选择中间位置右边的数字作为根节点, 31 | * 选择不同的数字作为根节点则创建的平衡二叉搜索树也是不同的。 32 | * 确定平衡二叉搜索树的根节点之后,其余的数字分别位于平衡二叉搜索树的左子树和右子树中, 33 | * 左子树和右子树分别也是平衡二叉搜索树,因此可以通过递归的方式创建平衡二叉搜索树。 34 | * 35 | * 递归的基准情形是平衡二叉搜索树不包含任何数字,此时平衡二叉搜索树为空。 36 | * 方法一:中序遍历,总是选择中间位置左边的数字作为根节点 37 | * 选择中间位置左边的数字作为根节点,则根节点的下标为mid=(left+right)/2,此处的除法为整数除法。 38 | * 39 | * @param nums 40 | * @return 41 | */ 42 | public TreeNode sortedArrayToBST(int[] nums) { 43 | return helper(nums, 0, nums.length - 1); 44 | } 45 | 46 | public TreeNode helper(int[] nums, int left, int right) { 47 | if (left > right) { 48 | return null; 49 | } 50 | 51 | // 总是选择中间位置左边的数字作为根节点 52 | int mid = (left + right) / 2; 53 | 54 | TreeNode root = new TreeNode(nums[mid]); 55 | root.left = helper(nums, left, mid - 1); 56 | root.right = helper(nums, mid + 1, right); 57 | return root; 58 | } 59 | 60 | 61 | public static class TreeNode { 62 | int val; 63 | TreeNode left; 64 | TreeNode right; 65 | TreeNode(int x) { val = x; } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No111minDepth.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * @author wayne 8 | * @version No111minDepth, 2020/8/26 9 | * 二叉树的最小深度 10 | * 11 | * 给定一个二叉树,找出其最小深度。 12 | * 13 | * 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 14 | * 15 | * 说明: 叶子节点是指没有子节点的节点。 16 | * 17 | * 示例: 18 | * 19 | * 给定二叉树 [3,9,20,null,null,15,7], 20 | * 21 | * 3 22 | * / \ 23 | * 9 20 24 | * / \ 25 | * 15 7 26 | * 返回它的最小深度 2. 27 | */ 28 | public class No111minDepth { 29 | 30 | 31 | /** 32 | * 方法一:深度优先搜索 33 | * 思路及解法 34 | * 35 | * 首先可以想到使用深度优先搜索的方法,遍历整棵树,记录最小深度。 36 | * 37 | * 对于每一个非叶子节点,我们只需要分别计算其左右子树的最小叶子节点深度。这样就将一个大问题转化为了小问题,可以递归地解决该问题。 38 | * 时间复杂度:O(N),其中 N 是树的节点数。对每个节点访问一次。 39 | * 40 | * 空间复杂度:O(H),其中 H 是树的高度。空间复杂度主要取决于递归时栈空间的开销,最坏情况下,树呈现链状,空间复杂度为 O(N)。 41 | * 平均情况下树的高度与节点数的对数正相关,空间复杂度为 O(logN)。 42 | * 43 | */ 44 | 45 | public int minDepth(TreeNode root) { 46 | if (root == null) { 47 | return 0; 48 | } 49 | 50 | if (root.left == null && root.right == null) { 51 | return 1; 52 | } 53 | 54 | int min_depth = Integer.MAX_VALUE; 55 | if (root.left != null) { 56 | min_depth = Math.min(minDepth(root.left), min_depth); 57 | } 58 | if (root.right != null) { 59 | min_depth = Math.min(minDepth(root.right), min_depth); 60 | } 61 | 62 | return min_depth + 1; 63 | } 64 | 65 | /** 66 | * 方法二:广度优先搜索 67 | * 思路及解法 68 | * 69 | * 同样,我们可以想到使用广度优先搜索的方法,遍历整棵树。 70 | * 71 | * 当我们找到一个叶子节点时,直接返回这个叶子节点的深度。广度优先搜索的性质保证了最先搜索到的叶子节点的深度一定最小。 72 | * 73 | */ 74 | 75 | class QueueNode { 76 | TreeNode node; 77 | int depth; 78 | 79 | public QueueNode(TreeNode node, int depth) { 80 | this.node = node; 81 | this.depth = depth; 82 | } 83 | } 84 | 85 | public int minDepth2(TreeNode root) { 86 | if (root == null) { 87 | return 0; 88 | } 89 | 90 | Queue queue = new LinkedList<>(); 91 | queue.offer(new QueueNode(root, 1)); 92 | while (!queue.isEmpty()) { 93 | QueueNode nodeDepth = queue.poll(); 94 | TreeNode node = nodeDepth.node; 95 | int depth = nodeDepth.depth; 96 | if (node.left == null && node.right == null) { 97 | return depth; 98 | } 99 | if (node.left != null) { 100 | queue.offer(new QueueNode(node.left, depth + 1)); 101 | } 102 | if (node.right != null) { 103 | queue.offer(new QueueNode(node.right, depth + 1)); 104 | } 105 | } 106 | 107 | return 0; 108 | } 109 | 110 | public static class TreeNode { 111 | int val; 112 | TreeNode left; 113 | TreeNode right; 114 | TreeNode(int x) { val = x; } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No112hasPathSum.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * @author wayne 8 | * @version No112hasPathSum, 2020/8/26 9 | * 10 | * 路径总和 11 | * 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。 12 | * 13 | * 说明: 叶子节点是指没有子节点的节点。 14 | * 15 | * 示例: 16 | * 给定如下二叉树,以及目标和 sum = 22, 17 | * 18 | * 5 19 | * / \ 20 | * 4 8 21 | * / / \ 22 | * 11 13 4 23 | * / \ \ 24 | * 7 2 1 25 | * 返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。 26 | */ 27 | public class No112hasPathSum { 28 | 29 | /** 30 | * 方法一:广度优先搜索 31 | * 思路及算法 32 | * 33 | * 首先我们可以想到使用广度优先搜索的方式,记录从根节点到当前节点的路径和,以防止重复计算。 34 | * 35 | * 这样我们使用两个队列,分别存储将要遍历的节点,以及根节点到这些节点的路径和即可。 36 | * 时间复杂度:O(N),其中 N 是树的节点数。对每个节点访问一次。 37 | * 38 | * 空间复杂度:O(N),其中 N 是树的节点数。空间复杂度主要取决于队列的开销,队列中的元素个数不会超过树的节点数。 39 | * 40 | */ 41 | 42 | public boolean hasPathSum1(TreeNode root, int sum) { 43 | if (root == null) { 44 | return false; 45 | } 46 | Queue queNode = new LinkedList<>(); 47 | Queue queVal = new LinkedList<>(); 48 | queNode.offer(root); 49 | queVal.offer(root.val); 50 | while (!queNode.isEmpty()) { 51 | TreeNode now = queNode.poll(); 52 | int temp = queVal.poll(); 53 | if (now.left == null && now.right == null) { 54 | if (temp == sum) { 55 | return true; 56 | } 57 | continue; 58 | } 59 | if (now.left != null) { 60 | queNode.offer(now.left); 61 | queVal.offer(now.left.val + temp); 62 | } 63 | if (now.right != null) { 64 | queNode.offer(now.right); 65 | queVal.offer(now.right.val + temp); 66 | } 67 | } 68 | return false; 69 | } 70 | 71 | /** 72 | * 方法二:递归 73 | * 思路及算法 74 | * 75 | * 观察要求我们完成的函数,我们可以归纳出它的功能:询问是否存在从当前节点 root 到叶子节点的路径,满足其路径和为 sum。 76 | * 77 | * 假定从根节点到当前节点的值之和为 val,我们可以将这个大问题转化为一个小问题:是否存在从当前节点的子节点到叶子的路径,满足其路径和为 sum - val。 78 | * 79 | * 不难发现这满足递归的性质,若当前节点就是叶子节点,那么我们直接判断 sum 是否等于 val 即可(因为路径和已经确定,就是当前节点的值,我们只需要判断该路径和是否满足条件)。若当前节点不是叶子节点,我们只需要递归地询问它的子节点是否能满足条件即可。 80 | * 81 | */ 82 | 83 | public boolean hasPathSum2(TreeNode root, int sum) { 84 | if (root == null) { 85 | return false; 86 | } 87 | if (root.left == null && root.right == null) { 88 | return sum == root.val; 89 | } 90 | return hasPathSum2(root.left, sum - root.val) || hasPathSum2(root.right, sum - root.val); 91 | } 92 | 93 | 94 | public static class TreeNode { 95 | int val; 96 | TreeNode left; 97 | TreeNode right; 98 | TreeNode(int x) { val = x; } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No118yanghui.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * @author wayne 8 | * @version No118yanghui, 2020/8/26 9 | *杨辉三角 10 | * 给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。 11 | * 在杨辉三角中,每个数是它左上方和右上方的数的和。 12 | * 13 | * 示例: 14 | * 15 | * 输入: 5 16 | * 输出: 17 | * [ 18 | * [1], 19 | * [1,1], 20 | * [1,2,1], 21 | * [1,3,3,1], 22 | * [1,4,6,4,1] 23 | * ] 24 | */ 25 | public class No118yanghui { 26 | /** 27 | * 方法:动态规划 28 | * 思路 29 | * 30 | * 如果能够知道一行杨辉三角,我们就可以根据每对相邻的值轻松地计算出它的下一行。 31 | * @param numRows 32 | * @return 33 | */ 34 | public List> generate(int numRows) { 35 | List> triangle = new ArrayList>(); 36 | 37 | // First base case; if user requests zero rows, they get zero rows. 38 | if (numRows == 0) { 39 | return triangle; 40 | } 41 | 42 | // Second base case; first row is always [1]. 43 | triangle.add(new ArrayList<>()); 44 | triangle.get(0).add(1); 45 | 46 | for (int rowNum = 1; rowNum < numRows; rowNum++) { 47 | List row = new ArrayList<>(); 48 | List prevRow = triangle.get(rowNum-1); 49 | 50 | // The first row element is always 1. 51 | row.add(1); 52 | 53 | // Each triangle element (other than the first and last of each row) 54 | // is equal to the sum of the elements above-and-to-the-left and 55 | // above-and-to-the-right. 56 | for (int j = 1; j < rowNum; j++) { 57 | row.add(prevRow.get(j-1) + prevRow.get(j)); 58 | } 59 | 60 | // The last row element is always 1. 61 | row.add(1); 62 | 63 | triangle.add(row); 64 | } 65 | 66 | return triangle; 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No119yanghui2.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * @author wayne 8 | * @version No119yanghui2, 2020/8/26 9 | * 10 | * 杨辉三角 II 11 | * 12 | * 给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。 13 | * 14 | * 15 | * 16 | * 在杨辉三角中,每个数是它左上方和右上方的数的和。 17 | * 18 | * 示例: 19 | * 20 | * 输入: 3 21 | * 输出: [1,3,3,1] 22 | * 进阶: 23 | * 24 | * 你可以优化你的算法到 O(k) 空间复杂度吗? 25 | */ 26 | public class No119yanghui2 { 27 | 28 | /** 29 | * 公式法 30 | * 如果输入的行数是n,那么该行内容依次是:1、(n-1)/1、(n-1)(n-2)/2、(n-1)(n-2)(n-3)/3... 31 | */ 32 | 33 | public List getRow(int rowIndex) { 34 | rowIndex++; 35 | List list = new ArrayList<>(); 36 | for (int i = 0; i < rowIndex; i++) { 37 | if (i == 0) { 38 | list.add(1); 39 | } else { 40 | long num = (long)list.get(i - 1) * (long)(rowIndex - i) / i; 41 | list.add((int)num); 42 | } 43 | } 44 | return list; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No121maxProfit.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No121maxProfit, 2020/8/26 6 | * 7 | * 买卖股票的最佳时机 8 | * 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 9 | * 10 | * 如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。 11 | * 12 | * 注意:你不能在买入股票前卖出股票。 13 | * 14 | * 示例 1: 15 | * 16 | * 输入: [7,1,5,3,6,4] 17 | * 输出: 5 18 | * 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 19 | * 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 20 | * 示例 2: 21 | * 22 | * 输入: [7,6,4,3,1] 23 | * 输出: 0 24 | * 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 25 | */ 26 | public class No121maxProfit { 27 | 28 | /** 29 | * 我们来假设自己来购买股票。随着时间的推移,每天我们都可以选择出售股票与否。那么,假设在第 i 天,如果我们要在今天卖股票,那么我们能赚多少钱呢? 30 | * 31 | * 显然,如果我们真的在买卖股票,我们肯定会想:如果我是在历史最低点买的股票就好了!太好了,在题目中,我们只要用一个变量记录一个历史最低价格 minprice,我们就可以假设自己的股票是在那天买的。那么我们在第 i 天卖出股票能得到的利润就是 prices[i] - minprice。 32 | * 33 | * 因此,我们只需要遍历价格数组一遍,记录历史最低点,然后在每一天考虑这么一个问题:如果我是在历史最低点买进的,那么我今天卖出能赚多少钱?当考虑完所有天数之时,我们就得到了最好的答案。 34 | * @param prices 35 | * @return 36 | */ 37 | public int maxProfit(int[] prices) { 38 | int minprice = Integer.MAX_VALUE; 39 | int maxprofit = 0; 40 | for (int i = 0; i < prices.length; i++) { 41 | if (prices[i] < minprice) 42 | minprice = prices[i]; 43 | else if (prices[i] - minprice > maxprofit) 44 | maxprofit = prices[i] - minprice; 45 | } 46 | return maxprofit; 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No122maxProfit.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No122maxProfit, 2020/8/26 6 | * 7 | * 买卖股票的最佳时机 II 8 | * 9 | * 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 10 | * 11 | * 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 12 | * 13 | * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 14 | * 15 | * 16 | * 17 | * 示例 1: 18 | * 19 | * 输入: [7,1,5,3,6,4] 20 | * 输出: 7 21 | * 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 22 | * 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 23 | * 示例 2: 24 | * 25 | * 输入: [1,2,3,4,5] 26 | * 输出: 4 27 | * 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 28 | * 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 29 | * 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 30 | * 示例 3: 31 | * 32 | * 输入: [7,6,4,3,1] 33 | * 输出: 0 34 | * 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 35 | * 36 | * 37 | * 提示: 38 | * 39 | * 1 <= prices.length <= 3 * 10 ^ 4 40 | * 0 <= prices[i] <= 10 ^ 4 41 | */ 42 | public class No122maxProfit { 43 | 44 | /** 45 | * 方法1: 找到valley和peak 46 | * 47 | */ 48 | 49 | public int maxProfit1(int[] prices) { 50 | int i = 0; 51 | int valley = prices[0]; 52 | int peak = prices[0]; 53 | int maxprofit = 0; 54 | while (i < prices.length - 1) { 55 | while (i < prices.length - 1 && prices[i] >= prices[i + 1]) 56 | i++; 57 | valley = prices[i]; 58 | while (i < prices.length - 1 && prices[i] <= prices[i + 1]) 59 | i++; 60 | peak = prices[i]; 61 | maxprofit += peak - valley; 62 | } 63 | return maxprofit; 64 | } 65 | 66 | /** 67 | * 该解决方案遵循 方法一 的本身使用的逻辑,但有一些轻微的变化。 68 | * 在这种情况下,我们可以简单地继续在斜坡上爬升并持续增加从连续交易中获得的利润,而不是在谷之后寻找每个峰值。 69 | * 最后,我们将有效地使用峰值和谷值,但我们不需要跟踪峰值和谷值对应的成本以及最大利润, 70 | * 但我们可以直接继续增加加数组的连续数字之间的差值, 71 | * 如果第二个数字大于第一个数字,我们获得的总和将是最大利润。这种方法将简化解决方案。 72 | * 73 | * @param prices 74 | * @return 75 | */ 76 | public int maxProfit2(int[] prices) { 77 | int maxprofit = 0; 78 | for (int i = 1; i < prices.length; i++) { 79 | if (prices[i] > prices[i - 1]) 80 | maxprofit += prices[i] - prices[i - 1]; 81 | } 82 | return maxprofit; 83 | } 84 | 85 | 86 | 87 | } 88 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No125isPalindrome.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No125isPalindrome, 2020/8/26 6 | * 7 | * 验证回文串 8 | * 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 9 | * 10 | * 说明:本题中,我们将空字符串定义为有效的回文串。 11 | * 12 | * 示例 1: 13 | * 14 | * 输入: "A man, a plan, a canal: Panama" 15 | * 输出: true 16 | * 示例 2: 17 | * 18 | * 输入: "race a car" 19 | * 输出: false 20 | */ 21 | public class No125isPalindrome { 22 | 23 | /** 24 | * 方法一:筛选 + 判断 25 | * 最简单的方法是对字符串 ss 进行一次遍历,并将其中的字母和数字字符进行保留,放在另一个字符串 sgood 中。 26 | * 这样我们只需要判断 sgood 是否是一个普通的回文串即可。 27 | * 28 | * 判断的方法有两种。第一种是使用语言中的字符串翻转 API 得到 sgood 的逆序字符串 sgood_rev, 29 | * 只要这两个字符串相同,那么 sgood 就是回文串。 30 | * 31 | */ 32 | 33 | public boolean isPalindrome(String s) { 34 | StringBuffer sgood = new StringBuffer(); 35 | int length = s.length(); 36 | for (int i = 0; i < length; i++) { 37 | char ch = s.charAt(i); 38 | if (Character.isLetterOrDigit(ch)) { 39 | sgood.append(Character.toLowerCase(ch)); 40 | } 41 | } 42 | StringBuffer sgood_rev = new StringBuffer(sgood).reverse(); 43 | return sgood.toString().equals(sgood_rev.toString()); 44 | } 45 | 46 | /** 47 | * 第二种是使用双指针。初始时,左右指针分别指向 sgood 的两侧,随后我们不断地将这两个指针相向移动, 48 | * 每次移动一步,并判断这两个指针指向的字符是否相同。当这两个指针相遇时,就说明 sgood 时回文串。 49 | * 50 | */ 51 | 52 | public boolean isPalindrome2(String s) { 53 | StringBuffer sgood = new StringBuffer(); 54 | int length = s.length(); 55 | for (int i = 0; i < length; i++) { 56 | char ch = s.charAt(i); 57 | if (Character.isLetterOrDigit(ch)) { 58 | sgood.append(Character.toLowerCase(ch)); 59 | } 60 | } 61 | int n = sgood.length(); 62 | int left = 0, right = n - 1; 63 | while (left < right) { 64 | if (Character.toLowerCase(sgood.charAt(left)) != Character.toLowerCase(sgood.charAt(right))) { 65 | return false; 66 | } 67 | ++left; 68 | --right; 69 | } 70 | return true; 71 | } 72 | 73 | /** 74 | * 方法二:在原字符串上直接判断 75 | * 我们可以对方法一中第二种判断回文串的方法进行优化,就可以得到只使用 O(1)O(1) 空间的算法。 76 | * 77 | * 我们直接在原字符串 ss 上使用双指针。在移动任意一个指针时,需要不断地向另一指针的方向移动, 78 | * 直到遇到一个字母或数字字符,或者两指针重合为止。也就是说,我们每次将指针移到下一个字母字符或数字字符, 79 | * 再判断这两个指针指向的字符是否相同。 80 | * 81 | */ 82 | 83 | public boolean isPalindrome3(String s) { 84 | int n = s.length(); 85 | int left = 0, right = n - 1; 86 | while (left < right) { 87 | while (left < right && !Character.isLetterOrDigit(s.charAt(left))) { 88 | ++left; 89 | } 90 | while (left < right && !Character.isLetterOrDigit(s.charAt(right))) { 91 | --right; 92 | } 93 | if (left < right) { 94 | if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) { 95 | return false; 96 | } 97 | ++left; 98 | --right; 99 | } 100 | } 101 | return true; 102 | } 103 | 104 | 105 | 106 | } 107 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No136singleNumber.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No136singleNumber, 2020/8/26 6 | * 7 | * 只出现一次的数字 8 | * 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 9 | * 10 | * 说明: 11 | * 12 | * 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 13 | * 14 | * 示例 1: 15 | * 16 | * 输入: [2,2,1] 17 | * 输出: 1 18 | * 示例 2: 19 | * 20 | * 输入: [4,1,2,1,2] 21 | * 输出: 4 22 | */ 23 | public class No136singleNumber { 24 | 25 | /** 26 | * 位运算 27 | * 如果没有时间复杂度和空间复杂度的限制,这道题有很多种解法,可能的解法有如下几种。 28 | * 29 | * 使用集合存储数字。遍历数组中的每个数字,如果集合中没有该数字,则将该数字加入集合,如果集合中已经有该数字,则将该数字从集合中删除,最后剩下的数字就是只出现一次的数字。 30 | * 31 | * 使用哈希表存储每个数字和该数字出现的次数。遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。 32 | * 33 | * 使用集合存储数组中出现的所有数字,并计算数组中的元素之和。由于集合保证元素无重复,因此计算集合中的所有元素之和的两倍,即为每个元素出现两次的情况下的元素之和。由于数组中只有一个元素出现一次,其余元素都出现两次,因此用集合中的元素之和的两倍减去数组中的元素之和,剩下的数就是数组中只出现一次的数字。 34 | * 35 | * 上述三种解法都需要额外使用 O(n) 的空间,其中 n 是数组长度。如果要求使用线性时间复杂度和常数空间复杂度,上述三种解法显然都不满足要求。那么,如何才能做到线性时间复杂度和常数空间复杂度呢? 36 | * 37 | * 答案是使用位运算。对于这道题,可使用异或运算 ⊕。异或运算有以下三个性质。 38 | * 39 | * 因此,数组中的全部元素的异或运算结果即为数组中只出现一次的数字。 40 | */ 41 | 42 | public int singleNumber(int[] nums) { 43 | int single = 0; 44 | for (int num : nums) { 45 | single ^= num; 46 | } 47 | return single; 48 | } 49 | 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No13RomanToInt.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * 罗马数字转整数 5 | * 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 6 | * 7 | * 字符 数值 8 | * I 1 9 | * V 5 10 | * X 10 11 | * L 50 12 | * C 100 13 | * D 500 14 | * M 1000 15 | * 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。 16 | * 17 | * 通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况: 18 | * 19 | * I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。 20 | * X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 21 | * C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。 22 | * 给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。 23 | * 24 | * 示例 1: 25 | * 26 | * 输入: "III" 27 | * 输出: 3 28 | * 示例 2: 29 | * 30 | * 输入: "IV" 31 | * 输出: 4 32 | * 示例 3: 33 | * 34 | * 输入: "IX" 35 | * 输出: 9 36 | * 示例 4: 37 | * 38 | * 输入: "LVIII" 39 | * 输出: 58 40 | * 解释: L = 50, V= 5, III = 3. 41 | * 示例 5: 42 | * 43 | * 输入: "MCMXCIV" 44 | * 输出: 1994 45 | * 解释: M = 1000, CM = 900, XC = 90, IV = 4. 46 | * @author wayne 47 | * @version No13Solution, 2020/8/25 48 | */ 49 | 50 | import java.util.HashMap; 51 | import java.util.Map; 52 | 53 | /** 54 | * 首先将所有的组合可能性列出并添加到哈希表中 55 | * 然后对字符串进行遍历,由于组合只有两种,一种是 1 个字符,一种是 2 个字符,其中 2 个字符优先于 1 个字符 56 | * 先判断两个字符的组合在哈希表中是否存在,存在则将值取出加到结果 ans 中,并向后移2个字符。不存在则将判断当前 1 个字符是否存在,存在则将值取出加到结果 ans 中,并向后移 1 个字符 57 | * 遍历结束返回结果 ans 58 | */ 59 | public class No13RomanToInt { 60 | 61 | public int romanToInt(String s) { 62 | Map map = new HashMap<>(); 63 | map.put("I", 1); 64 | map.put("IV", 4); 65 | map.put("V", 5); 66 | map.put("IX", 9); 67 | map.put("X", 10); 68 | map.put("XL", 40); 69 | map.put("L", 50); 70 | map.put("XC", 90); 71 | map.put("C", 100); 72 | map.put("CD", 400); 73 | map.put("D", 500); 74 | map.put("CM", 900); 75 | map.put("M", 1000); 76 | 77 | int ans = 0; 78 | for(int i = 0;i < s.length();) { 79 | if(i + 1 < s.length() && map.containsKey(s.substring(i, i+2))) { 80 | ans += map.get(s.substring(i, i+2)); 81 | i += 2; 82 | } else { 83 | ans += map.get(s.substring(i, i+1)); 84 | i ++; 85 | } 86 | } 87 | return ans; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No141hasCycle.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No141hasCycle, 2020/8/26 6 | * 7 | * 环形链表 8 | * 给定一个链表,判断链表中是否有环。 9 | * 10 | * 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。 11 | * 12 | * 13 | * 14 | * 示例 1: 15 | * 16 | * 输入:head = [3,2,0,-4], pos = 1 17 | * 输出:true 18 | * 解释:链表中有一个环,其尾部连接到第二个节点。 19 | * 20 | * 21 | * 示例 2: 22 | * 23 | * 输入:head = [1,2], pos = 0 24 | * 输出:true 25 | * 解释:链表中有一个环,其尾部连接到第一个节点。 26 | * 27 | * 28 | * 示例 3: 29 | * 30 | * 输入:head = [1], pos = -1 31 | * 输出:false 32 | * 解释:链表中没有环。 33 | * 34 | * 35 | * 36 | * 37 | * 进阶: 38 | * 39 | * 你能用 O(1)(即,常量)内存解决此问题吗? 40 | */ 41 | public class No141hasCycle { 42 | 43 | /** 44 | * 双指针 45 | * 思路 46 | * 47 | * 想象一下,两名运动员以不同的速度在环形赛道上跑步会发生什么? 48 | * 49 | * 算法 50 | * 51 | * 通过使用具有 不同速度 的快、慢两个指针遍历链表,空间复杂度可以被降低至 O(1)O(1)。慢指针每次移动一步,而快指针每次移动两步。 52 | * 53 | * 如果列表中不存在环,最终快指针将会最先到达尾部,此时我们可以返回 false。 54 | * 55 | * 现在考虑一个环形链表,把慢指针和快指针想象成两个在环形赛道上跑步的运动员(分别称之为慢跑者与快跑者)。而快跑者最终一定会追上慢跑者。这是为什么呢?考虑下面这种情况(记作情况 A)- 假如快跑者只落后慢跑者一步,在下一次迭代中,它们就会分别跑了一步或两步并相遇。 56 | * 57 | * 其他情况又会怎样呢?例如,我们没有考虑快跑者在慢跑者之后两步或三步的情况。但其实不难想到,因为在下一次或者下下次迭代后,又会变成上面提到的情况 A 58 | */ 59 | 60 | public boolean hasCycle(ListNode head) { 61 | if (head == null || head.next == null) { 62 | return false; 63 | } 64 | ListNode slow = head; 65 | ListNode fast = head.next; 66 | while (slow != fast) { 67 | if (fast == null || fast.next == null) { 68 | return false; 69 | } 70 | slow = slow.next; 71 | fast = fast.next.next; 72 | } 73 | return true; 74 | } 75 | 76 | public static class ListNode { 77 | int val; 78 | ListNode next; 79 | ListNode(int x) { val = x; } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No14LongestCommonPrefix.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * 最长公共前缀 5 | * 编写一个函数来查找字符串数组中的最长公共前缀。 6 | * 7 | * 如果不存在公共前缀,返回空字符串 ""。 8 | * 9 | * 示例 1: 10 | * 11 | * 输入: ["flower","flow","flight"] 12 | * 输出: "fl" 13 | * 示例 2: 14 | * 15 | * 输入: ["dog","racecar","car"] 16 | * 输出: "" 17 | * 解释: 输入不存在公共前缀。 18 | * 说明: 19 | * 20 | * 所有输入只包含小写字母 a-z 。 21 | * @author wayne 22 | * @version No14Solution, 2020/8/25 23 | */ 24 | public class No14LongestCommonPrefix { 25 | 26 | /** 27 | * 方法一:横向扫描 28 | * 依次遍历字符串数组中的每个字符串,对于每个遍历到的字符串,更新最长公共前缀,当遍历完所有的字符串以后,即可得到字符串数组中的最长公共前缀。 29 | * 如果在尚未遍历完所有的字符串时,最长公共前缀已经是空串,则最长公共前缀一定是空串,因此不需要继续遍历剩下的字符串,直接返回空串即可。 30 | * 31 | * 复杂度分析 32 | * 33 | * 时间复杂度:O(mn),其中 mm 是字符串数组中的字符串的平均长度,nn 是字符串的数量。最坏情况下,字符串数组中的每个字符串的每个字符都会被比较一次。 34 | * 35 | * 空间复杂度:O(1)。使用的额外空间复杂度为常数。 36 | * @param strs 37 | * @return 38 | */ 39 | public String longestCommonPrefix1(String[] strs) { 40 | if (strs == null || strs.length == 0) { 41 | return ""; 42 | } 43 | String prefix = strs[0]; 44 | int count = strs.length; 45 | for (int i = 1; i < count; i++) { 46 | prefix = longestCommonPrefix1(prefix, strs[i]); 47 | if (prefix.length() == 0) { 48 | break; 49 | } 50 | } 51 | return prefix; 52 | } 53 | 54 | public String longestCommonPrefix1(String str1, String str2) { 55 | int length = Math.min(str1.length(), str2.length()); 56 | int index = 0; 57 | while (index < length && str1.charAt(index) == str2.charAt(index)) { 58 | index++; 59 | } 60 | return str1.substring(0, index); 61 | } 62 | 63 | /** 64 | * 方法二:纵向扫描 65 | * 方法一是横向扫描,依次遍历每个字符串,更新最长公共前缀。另一种方法是纵向扫描。 66 | * 纵向扫描时,从前往后遍历所有字符串的每一列,比较相同列上的字符是否相同,如果相同则继续对下一列进行比较, 67 | * 如果不相同则当前列不再属于公共前缀,当前列之前的部分为最长公共前缀。 68 | * 69 | * 时间复杂度:O(mn),其中 mm 是字符串数组中的字符串的平均长度,nn 是字符串的数量。最坏情况下,字符串数组中的每个字符串的每个字符都会被比较一次。 70 | * 空间复杂度:O(1)。使用的额外空间复杂度为常数。 71 | */ 72 | 73 | public String longestCommonPrefix2(String[] strs) { 74 | if (strs == null || strs.length == 0) { 75 | return ""; 76 | } 77 | int length = strs[0].length(); 78 | int count = strs.length; 79 | for (int i = 0; i < length; i++) { 80 | char c = strs[0].charAt(i); 81 | for (int j = 1; j < count; j++) { 82 | if (i == strs[j].length() || strs[j].charAt(i) != c) { 83 | return strs[0].substring(0, i); 84 | } 85 | } 86 | } 87 | return strs[0]; 88 | } 89 | 90 | /** 91 | * 方法三:分治 92 | * 注意到 LCP的计算满足结合律,有以下结论: 93 | * 基于上述结论,可以使用分治法得到字符串数组中的最长公共前缀。对于问题 94 | * LCP(S1,...Sn),可以分解成两个子问题: LCP(S1,...Smid) LCP(Smid+1,...Sn) 95 | * 对两个子问题分别求解,然后对两个子问题的解计算最长公共前缀,即为原问题的解。 96 | * 时间复杂度:O(mn),其中 mm 是字符串数组中的字符串的平均长度,nn 是字符串的数量。 97 | * 98 | * 空间复杂度:O(mlogn),其中 m 是字符串数组中的字符串的平均长度,n 是字符串的数量。空间复杂度主要取决于递归调用的层数. 99 | */ 100 | public String longestCommonPrefix3(String[] strs) { 101 | if (strs == null || strs.length == 0) { 102 | return ""; 103 | } else { 104 | return longestCommonPrefix3(strs, 0, strs.length - 1); 105 | } 106 | } 107 | 108 | public String longestCommonPrefix3(String[] strs, int start, int end) { 109 | if (start == end) { 110 | return strs[start]; 111 | } else { 112 | int mid = (end - start) / 2 + start; 113 | String lcpLeft = longestCommonPrefix3(strs, start, mid); 114 | String lcpRight = longestCommonPrefix3(strs, mid + 1, end); 115 | return commonPrefix3(lcpLeft, lcpRight); 116 | } 117 | } 118 | 119 | public String commonPrefix3(String lcpLeft, String lcpRight) { 120 | int minLength = Math.min(lcpLeft.length(), lcpRight.length()); 121 | for (int i = 0; i < minLength; i++) { 122 | if (lcpLeft.charAt(i) != lcpRight.charAt(i)) { 123 | return lcpLeft.substring(0, i); 124 | } 125 | } 126 | return lcpLeft.substring(0, minLength); 127 | } 128 | 129 | 130 | /** 131 | * 方法四:二分查找 132 | * 显然,最长公共前缀的长度不会超过字符串数组中的最短字符串的长度。 133 | * 用 minLength 表示字符串数组中的最短字符串的长度,则可以在 [0,minLength] 的范围内通过二分查找得到最长公共前缀的长度。 134 | * 每次取查找范围的中间值 mid,判断每个字符串的长度为 mid 的前缀是否相同, 135 | * 如果相同则最长公共前缀的长度一定大于或等于 mid,如果不相同则最长公共前缀的长度一定小于 mid, 136 | * 通过上述方式将查找范围缩小一半,直到得到最长公共前缀的长度。 137 | *复杂度分析 138 | * 139 | * 时间复杂度:O(mnlogm),其中 mm 是字符串数组中的字符串的最小长度,n 是字符串的数量。二分查找的迭代执行次数是O(logm), 140 | * 每次迭代最多需要比较 mn 个字符,因此总时间复杂度是 O(mnlogm)。 141 | * 142 | * 空间复杂度:O(1)。使用的额外空间复杂度为常数。 143 | */ 144 | 145 | public String longestCommonPrefix4(String[] strs) { 146 | if (strs == null || strs.length == 0) { 147 | return ""; 148 | } 149 | int minLength = Integer.MAX_VALUE; 150 | for (String str : strs) { 151 | minLength = Math.min(minLength, str.length()); 152 | } 153 | int low = 0, high = minLength; 154 | while (low < high) { 155 | int mid = (high - low + 1) / 2 + low; 156 | if (isCommonPrefix4(strs, mid)) { 157 | low = mid; 158 | } else { 159 | high = mid - 1; 160 | } 161 | } 162 | return strs[0].substring(0, low); 163 | } 164 | 165 | public boolean isCommonPrefix4(String[] strs, int length) { 166 | String str0 = strs[0].substring(0, length); 167 | int count = strs.length; 168 | for (int i = 1; i < count; i++) { 169 | String str = strs[i]; 170 | for (int j = 0; j < length; j++) { 171 | if (str0.charAt(j) != str.charAt(j)) { 172 | return false; 173 | } 174 | } 175 | } 176 | return true; 177 | } 178 | 179 | 180 | } 181 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No155MinStack.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * @author wayne 7 | * @version No155MinStack, 2020/8/26 8 | * 9 | * 最小栈 10 | * 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 11 | * 12 | * push(x) —— 将元素 x 推入栈中。 13 | * pop() —— 删除栈顶的元素。 14 | * top() —— 获取栈顶元素。 15 | * getMin() —— 检索栈中的最小元素。 16 | * 17 | * 18 | * 示例: 19 | * 20 | * 输入: 21 | * ["MinStack","push","push","push","getMin","pop","top","getMin"] 22 | * [[],[-2],[0],[-3],[],[],[],[]] 23 | * 24 | * 输出: 25 | * [null,null,null,null,-3,null,0,-2] 26 | * 27 | * 解释: 28 | * MinStack minStack = new MinStack(); 29 | * minStack.push(-2); 30 | * minStack.push(0); 31 | * minStack.push(-3); 32 | * minStack.getMin(); --> 返回 -3. 33 | * minStack.pop(); 34 | * minStack.top(); --> 返回 0. 35 | * minStack.getMin(); --> 返回 -2. 36 | * 37 | * 38 | * 提示: 39 | * 40 | * pop、top 和 getMin 操作总是在 非空栈 上调用。 41 | */ 42 | public class No155MinStack { 43 | 44 | /** 45 | * 按照上面的思路,我们只需要设计一个数据结构,使得每个元素 a 与其相应的最小值 m 时刻保持一一对应。因此我们可以使用一个辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。 46 | * 47 | * 当一个元素要入栈时,我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中; 48 | * 49 | * 当一个元素要出栈时,我们把辅助栈的栈顶元素也一并弹出; 50 | * 51 | * 在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。 52 | * 53 | */ 54 | 55 | /** 56 | * 方法一:辅助栈和数据栈同步 57 | */ 58 | 59 | // 数据栈 60 | private Stack data; 61 | // 辅助栈 62 | private Stack helper; 63 | 64 | /** 65 | * initialize your data structure here. 66 | */ 67 | public No155MinStack() { 68 | data = new Stack<>(); 69 | helper = new Stack<>(); 70 | } 71 | 72 | // 思路 1:数据栈和辅助栈在任何时候都同步 73 | 74 | public void push(int x) { 75 | // 数据栈和辅助栈一定会增加元素 76 | data.add(x); 77 | if (helper.isEmpty() || helper.peek() >= x) { 78 | helper.add(x); 79 | } else { 80 | helper.add(helper.peek()); 81 | } 82 | } 83 | 84 | public void pop() { 85 | // 两个栈都得 pop 86 | if (!data.isEmpty()) { 87 | helper.pop(); 88 | data.pop(); 89 | } 90 | } 91 | 92 | public int top() { 93 | if(!data.isEmpty()){ 94 | return data.peek(); 95 | } 96 | throw new RuntimeException("栈中元素为空,此操作非法"); 97 | } 98 | 99 | public int getMin() { 100 | if(!helper.isEmpty()){ 101 | return helper.peek(); 102 | } 103 | throw new RuntimeException("栈中元素为空,此操作非法"); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No155MinStack2.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * @author wayne 7 | * @version No155MinStack2, 2020/8/26 8 | */ 9 | public class No155MinStack2 { 10 | 11 | // 数据栈 12 | private Stack data; 13 | // 辅助栈 14 | private Stack helper; 15 | 16 | /** 17 | * initialize your data structure here. 18 | */ 19 | public No155MinStack2() { 20 | data = new Stack<>(); 21 | helper = new Stack<>(); 22 | } 23 | 24 | // 思路 2:辅助栈和数据栈不同步 25 | // 关键 1:辅助栈的元素空的时候,必须放入新进来的数 26 | // 关键 2:新来的数小于或者等于辅助栈栈顶元素的时候,才放入(特别注意这里等于要考虑进去) 27 | // 关键 3:出栈的时候,辅助栈的栈顶元素等于数据栈的栈顶元素,才出栈,即"出栈保持同步"就可以了 28 | 29 | public void push(int x) { 30 | // 辅助栈在必要的时候才增加 31 | data.add(x); 32 | // 关键 1 和 关键 2 33 | if (helper.isEmpty() || helper.peek() >= x) { 34 | helper.add(x); 35 | } 36 | } 37 | 38 | public void pop() { 39 | // 关键 3:data 一定得 pop() 40 | if (!data.isEmpty()) { 41 | // 注意:声明成 int 类型,这里完成了自动拆箱,从 Integer 转成了 int,因此下面的比较可以使用 "==" 运算符 42 | // 如果把 top 变量声明成 Integer 类型,下面的比较就得使用 equals 方法 43 | int top = data.pop(); 44 | if(top == helper.peek()){ 45 | helper.pop(); 46 | } 47 | } 48 | } 49 | 50 | public int top() { 51 | if(!data.isEmpty()){ 52 | return data.peek(); 53 | } 54 | throw new RuntimeException("栈中元素为空,此操作非法"); 55 | } 56 | 57 | public int getMin() { 58 | if(!helper.isEmpty()){ 59 | return helper.peek(); 60 | } 61 | throw new RuntimeException("栈中元素为空,此操作非法"); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No1TwoSum.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * @author wayne 7 | * @version No1Solution, 2020/8/19 8 | * 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 9 | * 10 | * 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 11 | * 示例: 12 | * 13 | * 给定 nums = [2, 7, 11, 15], target = 9 14 | * 15 | * 因为 nums[0] + nums[1] = 2 + 7 = 9 16 | * 所以返回 [0, 1] 17 | * 18 | * 通过以空间换取速度的方式,我们可以将查找时间从 O(n) 降低到 O(1)。 19 | * 哈希表正是为此目的而构建的,它支持以 近似 恒定的时间进行快速查找。我用“近似”来描述,是因为一旦出现冲突,查找用时可能会退化到 O(n)。 20 | * 但只要你仔细地挑选哈希函数,在哈希表中进行查找的用时应当被摊销为 O(1)。 21 | * 一个简单的实现使用了两次迭代。在第一次迭代中,我们将每个元素的值和它的索引添加到表中。 22 | * 然后,在第二次迭代中,我们将检查每个元素所对应的目标元素(target - nums[i])是否存在于表中。 23 | * 注意,该目标元素不能是 nums[i]本身! 24 | * 25 | * 复杂度分析: 26 | * 27 | * 时间复杂度:O(n), 28 | * 我们把包含有 n 个元素的列表遍历两次。由于哈希表将查找时间缩短到 O(1) ,所以时间复杂度为 O(n)。 29 | * 30 | * 空间复杂度:O(n), 31 | * 所需的额外空间取决于哈希表中存储的元素数量,该表中存储了 n 个元素。 32 | * 33 | */ 34 | public class No1TwoSum { 35 | public int[] twoSum(int[] nums, int target) { 36 | int[] result=new int[2]; 37 | HashMap map= new HashMap(); 38 | for(int i=0; i< nums.length; i++){ 39 | map.put(nums[i],i); 40 | } 41 | for(int i=0; i< nums.length; i++){ 42 | int number= target-nums[i]; 43 | if(map.containsKey(number) && map.get(number) != i){ 44 | result[1]=i; 45 | result[0]=map.get(number); 46 | } 47 | } 48 | return result; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No1TwoSum2.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * @author wayne 7 | * @version No1Solution2, 2020/8/19 8 | * 9 | * 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 10 | * 11 | * 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 12 | * 13 | * 示例: 14 | * 15 | * 给定 nums = [2, 7, 11, 15], target = 9 16 | * 17 | * 因为 nums[0] + nums[1] = 2 + 7 = 9 18 | * 所以返回 [0, 1] 19 | * 20 | * 一遍哈希表 21 | * 在进行迭代并将元素插入到表中的同时,我们还会回过头来检查表中是否已经存在当前元素所对应的目标元素。 22 | * 如果它存在,那我们已经找到了对应解,并立即将其返回。 23 | */ 24 | public class No1TwoSum2 { 25 | public int[] twoSum(int[] nums, int target) { 26 | int[] result=new int[2]; 27 | HashMap map= new HashMap(); 28 | for(int i=0; i< nums.length; i++){ 29 | int number= target-nums[i]; 30 | if(map.containsKey(number)){ 31 | result[1]=i; 32 | result[0]=map.get(number); 33 | } 34 | map.put(nums[i],i); 35 | } 36 | return result; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No20IsValidBracket.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.Deque; 4 | import java.util.HashMap; 5 | import java.util.LinkedList; 6 | import java.util.Map; 7 | 8 | /** 9 | * @author wayne 10 | * @version No20IsValidBracket, 2020/8/25 11 | * 12 | * 有效的括号 13 | * 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 14 | * 15 | * 有效字符串需满足: 16 | * 17 | * 左括号必须用相同类型的右括号闭合。 18 | * 左括号必须以正确的顺序闭合。 19 | * 注意空字符串可被认为是有效字符串。 20 | * 21 | * 示例 1: 22 | * 23 | * 输入: "()" 24 | * 输出: true 25 | * 示例 2: 26 | * 27 | * 输入: "()[]{}" 28 | * 输出: true 29 | * 示例 3: 30 | * 31 | * 输入: "(]" 32 | * 输出: false 33 | * 示例 4: 34 | * 35 | * 输入: "([)]" 36 | * 输出: false 37 | * 示例 5: 38 | * 39 | * 输入: "{[]}" 40 | * 输出: true 41 | */ 42 | public class No20IsValidBracket { 43 | 44 | /** 45 | * 方法一:栈 46 | * 判断括号的有效性可以使用「栈」这一数据结构来解决。 47 | * 48 | * 我们对给定的字符串 ss 进行遍历,当我们遇到一个左括号时,我们会期望在后续的遍历中,有一个相同类型的右括号将其闭合。由于后遇到的左括号要先闭合,因此我们可以将这个左括号放入栈顶。 49 | * 50 | * 当我们遇到一个右括号时,我们需要将一个相同类型的左括号闭合。此时,我们可以取出栈顶的左括号并判断它们是否是相同类型的括号。 51 | * 52 | * 如果不是相同的类型,或者栈中并没有左括号,那么字符串 ss 无效,返回 False。为了快速判断括号的类型,我们可以使用哈希映射(HashMap)存储每一种括号。 53 | * 54 | * 哈希映射的键为右括号,值为相同类型的左括号。 55 | * 56 | * 在遍历结束后,如果栈中没有左括号,说明我们将字符串 ss 中的所有左括号闭合,返回 True,否则返回 False。 57 | * 58 | * 注意到有效字符串的长度一定为偶数,因此如果字符串的长度为奇数,我们可以直接返回 False,省去后续的遍历判断过程。 59 | */ 60 | 61 | public boolean isValid(String s) { 62 | int n = s.length(); 63 | if (n % 2 == 1) { 64 | return false; 65 | } 66 | 67 | Map pairs = new HashMap<>(){{ 68 | put(')', '('); 69 | put(']', '['); 70 | put('}', '{'); 71 | }}; 72 | Deque stack = new LinkedList<>(); 73 | for (int i = 0; i < n; i++) { 74 | char ch = s.charAt(i); 75 | if (pairs.containsKey(ch)) { 76 | if (stack.isEmpty() || stack.peek() != pairs.get(ch)) { 77 | return false; 78 | } 79 | stack.pop(); 80 | } else { 81 | stack.push(ch); 82 | } 83 | } 84 | return stack.isEmpty(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No21mergeTwoLists.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No21mergeTwoLists, 2020/8/25 6 | * 7 | * 合并两个有序链表 8 | * 9 | * 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 10 | * 11 | * 示例: 12 | * 13 | * 输入:1->2->4, 1->3->4 14 | * 输出:1->1->2->3->4->4 15 | */ 16 | public class No21mergeTwoLists { 17 | 18 | /** 19 | * 方法一:递归 20 | * 如果 l1 或者 l2 一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。 21 | * 否则,我们要判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。 22 | * 如果两个链表有一个为空,递归结束。 23 | * 24 | * 时间复杂度: O(n+m),其中 nn 和 mm 分别为两个链表的长度。 25 | * 空间复杂度: O(n+m),其中 nn 和 mm 分别为两个链表的长度。 26 | */ 27 | 28 | public ListNode mergeTwoLists1(ListNode l1, ListNode l2) { 29 | if (l1 == null) { 30 | return l2; 31 | } 32 | else if (l2 == null) { 33 | return l1; 34 | } 35 | else if (l1.val < l2.val) { 36 | l1.next = mergeTwoLists1(l1.next, l2); 37 | return l1; 38 | } 39 | else { 40 | l2.next = mergeTwoLists1(l1, l2.next); 41 | return l2; 42 | } 43 | 44 | } 45 | 46 | /** 47 | * 方法二:迭代 48 | * 思路 49 | * 50 | * 我们可以用迭代的方法来实现上述算法。当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里, 51 | * 当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。 52 | * 53 | * 算法 54 | * 55 | * 首先,我们设定一个哨兵节点 prehead ,这可以在最后让我们比较容易地返回合并后的链表。 56 | * 我们维护一个 prev 指针,我们需要做的是调整它的 next 指针。 57 | * 然后,我们重复以下过程,直到 l1 或者 l2 指向了 null : 58 | * 如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 prev 节点的后面同时将 l1 指针往后移一位。 59 | * 否则,我们对 l2 做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把 prev 向后移一位。 60 | * 61 | * 在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的, 62 | * 它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面, 63 | * 并返回合并链表即可。 64 | */ 65 | 66 | public ListNode mergeTwoLists2(ListNode l1, ListNode l2) { 67 | ListNode prehead = new ListNode(-1); 68 | 69 | ListNode prev = prehead; 70 | while (l1 != null && l2 != null) { 71 | if (l1.val <= l2.val) { 72 | prev.next = l1; 73 | l1 = l1.next; 74 | } else { 75 | prev.next = l2; 76 | l2 = l2.next; 77 | } 78 | prev = prev.next; 79 | } 80 | 81 | // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可 82 | prev.next = l1 == null ? l2 : l1; 83 | 84 | return prehead.next; 85 | } 86 | 87 | 88 | 89 | static class ListNode { 90 | int val; 91 | ListNode next; 92 | ListNode() {} 93 | ListNode(int val) { this.val = val; } 94 | ListNode(int val, ListNode next) { this.val = val; this.next = next; }} 95 | } 96 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No26removeDuplicates.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No26removeDuplicates, 2020/8/25 6 | * 7 | * 删除排序数组中的重复项 8 | * 9 | * 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 10 | * 11 | * 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 12 | * 13 | * 14 | * 15 | * 示例 1: 16 | * 17 | * 给定数组 nums = [1,1,2], 18 | * 19 | * 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 20 | * 21 | * 你不需要考虑数组中超出新长度后面的元素。 22 | * 示例 2: 23 | * 24 | * 给定 nums = [0,0,1,1,1,2,2,3,3,4], 25 | * 26 | * 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 27 | * 28 | * 你不需要考虑数组中超出新长度后面的元素。 29 | * 30 | * 31 | * 说明: 32 | * 33 | * 为什么返回数值是整数,但输出的答案是数组呢? 34 | * 35 | * 请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 36 | * 37 | * 你可以想象内部操作如下: 38 | * 39 | * // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 40 | * int len = removeDuplicates(nums); 41 | * 42 | * // 在函数里修改输入数组对于调用者是可见的。 43 | * // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 44 | * for (int i = 0; i < len; i++) { 45 | * print(nums[i]); 46 | * } 47 | * 48 | */ 49 | public class No26removeDuplicates { 50 | 51 | /** 52 | * 方法:双指针法 53 | * 算法 54 | * 55 | * 数组完成排序后,我们可以放置两个指针 i 和 j,其中 i 是慢指针,而 j 是快指针。只要 nums[i] = nums[j],我们就增加 j 以跳过重复项。 56 | * 57 | * 当我们遇到 nums[j] != nums[i] 时,跳过重复项的运行已经结束,因此我们必须把它(nums[j])的值复制到 nums[i + 1]。 58 | * 59 | * 然后递增 i,接着我们将再次重复相同的过程,直到 j 到达数组的末尾为止。 60 | * 61 | * 时间复杂度:O(n),假设数组的长度是 n,那么 i 和 j 分别最多遍历 n 步。 62 | * 63 | * 空间复杂度:O(1)。 64 | */ 65 | 66 | public int removeDuplicates(int[] nums) { 67 | if (nums.length == 0) return 0; 68 | int i = 0; 69 | for (int j = 1; j < nums.length; j++) { 70 | if (nums[j] != nums[i]) { 71 | i++; 72 | nums[i] = nums[j]; 73 | } 74 | } 75 | return i + 1; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No27removeElement.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No27removeElement, 2020/8/25 6 | * 7 | * 移除元素 8 | * 9 | * 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 10 | * 11 | * 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 12 | * 13 | * 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 14 | * 15 | * 16 | * 17 | * 示例 1: 18 | * 19 | * 给定 nums = [3,2,2,3], val = 3, 20 | * 21 | * 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 22 | * 23 | * 你不需要考虑数组中超出新长度后面的元素。 24 | * 示例 2: 25 | * 26 | * 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 27 | * 28 | * 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。 29 | * 30 | * 注意这五个元素可为任意顺序。 31 | * 32 | * 你不需要考虑数组中超出新长度后面的元素。 33 | * 34 | * 35 | * 说明: 36 | * 37 | * 为什么返回数值是整数,但输出的答案是数组呢? 38 | * 39 | * 请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 40 | * 41 | * 你可以想象内部操作如下: 42 | * 43 | * // nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝 44 | * int len = removeElement(nums, val); 45 | * 46 | * // 在函数里修改输入数组对于调用者是可见的。 47 | * // 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。 48 | * for (int i = 0; i < len; i++) { 49 | * print(nums[i]); 50 | * } 51 | */ 52 | public class No27removeElement { 53 | 54 | /** 55 | * 方法一:双指针 56 | * 思路 57 | * 58 | * 既然问题要求我们就地删除给定值的所有元素,我们就必须用 O(1)O(1) 的额外空间来处理它。 59 | * 如何解决?我们可以保留两个指针 ii 和 jj,其中 ii 是慢指针,jj 是快指针。 60 | * 算法 61 | * 62 | * 当 nums[j] 与给定的值相等时,递增 jj 以跳过该元素。只要 nums[j] !=val, 63 | * 我们就复制 nums[j] 到 nums[i]并同时递增两个索引。重复这一过程,直到 jj 到达数组的末尾,该数组的新长度为 ii。 64 | * 65 | * 时间复杂度:O(n), 66 | * 假设数组总共有 n 个元素,i 和 j 至少遍历 2n 步。 67 | * 68 | * 空间复杂度:O(1)。 69 | */ 70 | 71 | public int removeElement1(int[] nums, int val) { 72 | int i = 0; 73 | for (int j = 0; j < nums.length; j++) { 74 | if (nums[j] != val) { 75 | nums[i] = nums[j]; 76 | i++; 77 | } 78 | } 79 | return i; 80 | } 81 | 82 | /** 83 | * 双指针 —— 当要删除的元素很少时 84 | * 思路 85 | * 86 | * 现在考虑数组包含很少的要删除的元素的情况。例如,num=[1,2,3,5,4],Val=4。之前的算法会对前四个元素做不必要的复制操作。 87 | * 另一个例子是 num=[4,1,2,3,5],Val=4。似乎没有必要将 [1,2,3,5] 这几个元素左移一步,因为问题描述中提到元素的顺序可以更改。 88 | * 89 | * 算法 90 | * 91 | * 当我们遇到 nums[i]=val 时,我们可以将当前元素与最后一个元素进行交换,并释放最后一个元素。这实际上使数组的大小减少了 1。 92 | * 93 | * 请注意,被交换的最后一个元素可能是您想要移除的值。但是不要担心,在下一次迭代中,我们仍然会检查这个元素。 94 | * 95 | * 时间复杂度:O(n),ii 和 nn 最多遍历 nn 步。在这个方法中,赋值操作的次数等于要删除的元素的数量。因此,如果要移除的元素很少,效率会更高。 96 | * 97 | * 空间复杂度:O(1)。 98 | */ 99 | 100 | public int removeElement2(int[] nums, int val) { 101 | int i = 0; 102 | int n = nums.length; 103 | while (i < n) { 104 | if (nums[i] == val) { 105 | nums[i] = nums[n - 1]; 106 | // reduce array size by one 107 | n--; 108 | } else { 109 | i++; 110 | } 111 | } 112 | return n; 113 | } 114 | 115 | 116 | 117 | 118 | 119 | } 120 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No28StrStr.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * 实现 strStr() 5 | * 实现 strStr() 函数。 6 | * 7 | * 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。 8 | * 9 | * 示例 1: 10 | * 11 | * 输入: haystack = "hello", needle = "ll" 12 | * 输出: 2 13 | * 示例 2: 14 | * 15 | * 输入: haystack = "aaaaa", needle = "bba" 16 | * 输出: -1 17 | * 说明: 18 | * 19 | * 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 20 | * 21 | * 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。 22 | * @author wayne 23 | * @version No28StrStr, 2020/8/25 24 | */ 25 | public class No28StrStr { 26 | 27 | /** 28 | * 方法一:子串逐一比较 - 线性时间复杂度 29 | * 最直接的方法 - 沿着字符换逐步移动滑动窗口,将窗口内的子串与 needle 字符串比较。 30 | * 31 | * 时间复杂度:O((N−L)L),其中 N 为 haystack 字符串的长度,L 为 needle 字符串的长度。内循环中比较字符串的复杂度为 L,总共需要比较 (N - L) 次。 32 | * 33 | * 空间复杂度:O(1)。 34 | */ 35 | 36 | public int strStr1(String haystack, String needle) { 37 | int L = needle.length(), n = haystack.length(); 38 | 39 | for (int start = 0; start < n - L + 1; ++start) { 40 | if (haystack.substring(start, start + L).equals(needle)) { 41 | return start; 42 | } 43 | } 44 | return -1; 45 | } 46 | 47 | /** 48 | * 方法二:双指针 - 线性时间复杂度 49 | * 上一个方法的缺陷是会将 haystack 所有长度为 L 的子串都与 needle 字符串比较,实际上是不需要这么做的。 50 | * 51 | * 首先,只有子串的第一个字符跟 needle 字符串第一个字符相同的时候才需要比较。 52 | * 53 | * 算法 54 | * 55 | * 移动 pn 指针,直到 pn 所指向位置的字符与 needle 字符串第一个字符相等。 56 | * 57 | * 通过 pn,pL,curr_len 计算匹配长度。 58 | * 59 | * 如果完全匹配(即 curr_len == L),返回匹配子串的起始坐标(即 pn - L)。 60 | * 61 | * 如果不完全匹配,回溯。使 pn = pn - curr_len + 1, pL = 0, curr_len = 0。 62 | * 63 | *时间复杂度:最坏时间复杂度为 O((N - L)L),最优时间复杂度为 O(N)。 64 | * 65 | * 空间复杂度:O(1)。 66 | */ 67 | 68 | public int strStr2(String haystack, String needle) { 69 | int L = needle.length(), n = haystack.length(); 70 | if (L == 0) return 0; 71 | 72 | int pn = 0; 73 | while (pn < n - L + 1) { 74 | // find the position of the first needle character 75 | // in the haystack string 76 | while (pn < n - L + 1 && haystack.charAt(pn) != needle.charAt(0)) ++pn; 77 | 78 | // compute the max match string 79 | int currLen = 0, pL = 0; 80 | while (pL < L && pn < n && haystack.charAt(pn) == needle.charAt(pL)) { 81 | ++pn; 82 | ++pL; 83 | ++currLen; 84 | } 85 | 86 | // if the whole needle string is found, 87 | // return its start position 88 | if (currLen == L) return pn - L; 89 | 90 | // otherwise, backtrack 91 | pn = pn - currLen + 1; 92 | } 93 | return -1; 94 | } 95 | 96 | 97 | /** 98 | * 方法三: Rabin Karp - 常数复杂度 99 | * 有一种最坏时间复杂度也为 O(N) 的算法。思路是这样的,先生成窗口内子串的哈希码,然后再跟 needle 字符串的哈希码做比较。 100 | * 101 | * 这个思路有一个问题需要解决,如何在常数时间生成子串的哈希码? 102 | * 103 | * 滚动哈希:常数时间生成哈希码 104 | * 105 | * 生成一个长度为 L 数组的哈希码,需要 O(L) 时间。 106 | * 107 | * 如何在常数时间生成滑动窗口数组的哈希码?利用滑动窗口的特性,每次滑动都有一个元素进,一个出。 108 | * 109 | * 由于只会出现小写的英文字母,因此可以将字符串转化成值为 0 到 25 的整数数组: arr[i] = (int)S.charAt(i) - (int)'a'。按照这种规则,abcd 整数数组形式就是 [0, 1, 2, 3],转换公式如下所示。 110 | * 111 | * 下面来考虑窗口从 abcd 滑动到 bcde 的情况。这时候整数形式数组从 [0, 1, 2, 3] 变成了 [1, 2, 3, 4],数组最左边的 0 被移除,同时最右边新添了 4。 112 | * 113 | * 滑动后数组的哈希值可以根据滑动前数组的哈希值来计算,计算公式如下所示。 114 | * 115 | * 时间复杂度:O(N),计算 needle 字符串的哈希值需要 O(L) 时间,之后需要执行 (N - L) 次循环,每次循环的计算复杂度为常数。 116 | * 117 | * 空间复杂度:O(1)。 118 | * 119 | * @param idx 120 | * @param s 121 | * @return 122 | */ 123 | 124 | // function to convert character to integer 125 | public int charToInt(int idx, String s) { 126 | return (int)s.charAt(idx) - (int)'a'; 127 | } 128 | 129 | public int strStr3(String haystack, String needle) { 130 | int L = needle.length(), n = haystack.length(); 131 | if (L > n) return -1; 132 | 133 | // base value for the rolling hash function 134 | int a = 26; 135 | // modulus value for the rolling hash function to avoid overflow 136 | long modulus = (long)Math.pow(2, 31); 137 | 138 | // compute the hash of strings haystack[:L], needle[:L] 139 | long h = 0, ref_h = 0; 140 | for (int i = 0; i < L; ++i) { 141 | h = (h * a + charToInt(i, haystack)) % modulus; 142 | ref_h = (ref_h * a + charToInt(i, needle)) % modulus; 143 | } 144 | if (h == ref_h) return 0; 145 | 146 | // const value to be used often : a**L % modulus 147 | long aL = 1; 148 | for (int i = 1; i <= L; ++i) aL = (aL * a) % modulus; 149 | 150 | for (int start = 1; start < n - L + 1; ++start) { 151 | // compute rolling hash in O(1) time 152 | h = (h * a - charToInt(start - 1, haystack) * aL 153 | + charToInt(start + L - 1, haystack)) % modulus; 154 | if (h == ref_h) return start; 155 | } 156 | return -1; 157 | } 158 | 159 | 160 | 161 | } 162 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No35searchInsert.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | *给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 5 | * 6 | * 你可以假设数组中无重复元素。 7 | * 8 | * 示例 1: 9 | * 10 | * 输入: [1,3,5,6], 5 11 | * 输出: 2 12 | * 示例 2: 13 | * 14 | * 输入: [1,3,5,6], 2 15 | * 输出: 1 16 | * 示例 3: 17 | * 18 | * 输入: [1,3,5,6], 7 19 | * 输出: 4 20 | * 示例 4: 21 | * 22 | * 输入: [1,3,5,6], 0 23 | * 输出: 0 24 | * @author wayne 25 | * @version No35searchInsert, 2020/8/25 26 | */ 27 | public class No35searchInsert { 28 | 29 | /** 30 | * 方法一:二分查找 31 | * 思路与算法 32 | * 33 | * 假设题意是叫你在排序数组中寻找是否存在一个目标值,那么训练有素的读者肯定立马就能想到利用二分法在 O(logn) 的时间内找到是否存在目标值。 34 | * 但这题还多了个额外的条件,即如果不存在数组中的时候需要返回按顺序插入的位置,那我们还能用二分法么?答案是可以的,我们只需要稍作修改即可。 35 | * 36 | * 考虑这个插入的位置 pos,它成立的条件为: 37 | * nums[pos−1]> 1) + left; 60 | if (target <= nums[mid]) { 61 | ans = mid; 62 | right = mid - 1; 63 | } else { 64 | left = mid + 1; 65 | } 66 | } 67 | return ans; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No38countAndSay.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No38countAndSay, 2020/8/25 6 | * 7 | * 外观数列 8 | * 给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。 9 | * 10 | * 注意:整数序列中的每一项将表示为一个字符串。 11 | * 12 | * 「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下: 13 | * 14 | * 1. 1 15 | * 2. 11 16 | * 3. 21 17 | * 4. 1211 18 | * 5. 111221 19 | * 第一项是数字 1 20 | * 21 | * 描述前一项,这个数是 1 即 “一个 1 ”,记作 11 22 | * 23 | * 描述前一项,这个数是 11 即 “两个 1 ” ,记作 21 24 | * 25 | * 描述前一项,这个数是 21 即 “一个 2 一个 1 ” ,记作 1211 26 | * 27 | * 描述前一项,这个数是 1211 即 “一个 1 一个 2 两个 1 ” ,记作 111221 28 | * 29 | * 30 | * 31 | * 示例 1: 32 | * 33 | * 输入: 1 34 | * 输出: "1" 35 | * 解释:这是一个基本样例。 36 | * 示例 2: 37 | * 38 | * 输入: 4 39 | * 输出: "1211" 40 | * 解释:当 n = 3 时,序列是 "21",其中我们有 "2" 和 "1" 两组,"2" 可以读作 "12",也就是出现频次 = 1 而 值 = 2;类似 "1" 可以读作 "11"。所以答案是 "12" 和 "11" 组合在一起,也就是 "1211"。 41 | * 42 | */ 43 | public class No38countAndSay { 44 | 45 | /** 46 | * 比如 n=6时,那么用递归得到上一层的字符串str=“111221” 47 | * 我们将start指向下标0,我们从下标1开始遍历,遍历到“2”下标3的时候,sb拼上(3-0)个1即sb.append(3).append(1),将start指针指向下标3,接着重复以上操作,直到到达str的最后一位,sb直接拼上即可。 48 | * @param n 49 | * @return 50 | */ 51 | 52 | public String countAndSay(int n) { 53 | // 递归终止条件 54 | if (n == 1) { 55 | return "1"; 56 | } 57 | StringBuilder res = new StringBuilder(); 58 | // 拿到上一层的字符串 59 | String str = countAndSay(n - 1); 60 | int length = str.length(); 61 | // 开始指针为0 62 | int start = 0; 63 | // 注意这从起始条件要和下面长度统一 64 | for (int i = 1; i < length + 1; i++) { 65 | // 字符串最后一位直接拼接,最后的字符串只能为1 66 | if (i == length) { 67 | // res.append(i - start).append(str.charAt(start)); 68 | res.append(i - start).append(1); 69 | // 直到start位的字符串和i位的字符串不同,拼接并更新start位 70 | } else if (str.charAt(i) != str.charAt(start) ) { 71 | res.append(i - start).append(str.charAt(start)); 72 | start = i; 73 | } 74 | } 75 | return res.toString(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No53maxSubArray.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No53maxSubArray, 2020/8/25 6 | * 7 | * 最大子序和 8 | * 9 | * 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 10 | * 11 | * 示例: 12 | * 13 | * 输入: [-2,1,-3,4,-1,2,1,-5,4] 14 | * 输出: 6 15 | * 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 16 | * 进阶: 17 | * 18 | * 如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。 19 | */ 20 | public class No53maxSubArray { 21 | 22 | /** 23 | * 动态规划 24 | * 思路和算法 25 | * 26 | * 假设 nums 数组的长度是 n,下标从 0 到 n−1。 27 | * 28 | * 我们用 a[i] 29 | * 代表 nums[i],用 f(i) 代表以第 i 个数结尾的「连续子数组的最大和」,那么很显然我们要求的答案就是: 30 | * max{f(i)} 31 | * 32 | * 因此我们只需要求出每个位置的 f(i),然后返回 f 数组中的最大值即可。那么我们如何求 f(i) 呢?我们可以考虑 a[i] 33 | * 单独成为一段还是加入 f(i−1) 对应的那一段,这取决于 a[i] 和 f(i - 1) + a[i] 34 | * 的大小,我们希望获得一个比较大的,于是可以写出这样的动态规划转移方程: 35 | * f(i)=max{f(i−1)+a } 36 | * 37 | */ 38 | 39 | public int maxSubArray(int[] nums) { 40 | int[] dp = new int[nums.length]; 41 | dp[0] = nums[0]; 42 | int max = nums[0]; 43 | for (int i = 1; i < nums.length; i++) { 44 | dp[i] = Math.max(dp[i- 1] + nums[i], nums[i]); 45 | if (max < dp[i]) { 46 | max = dp[i]; 47 | } 48 | } 49 | return max; 50 | } 51 | 52 | /** 53 | * 方法2 54 | * 动态规划的是首先对数组进行遍历,当前最大连续子序列和为 sum,结果为 ans 55 | * 如果 sum > 0,则说明 sum 对结果有增益效果,则 sum 保留并加上当前遍历数字 56 | * 如果 sum <= 0,则说明 sum 对结果无增益效果,需要舍弃,则 sum 直接更新为当前遍历数字 57 | * 每次比较 sum 和 ans的大小,将最大值置为ans,遍历结束返回结果 58 | */ 59 | 60 | public int maxSubArray2(int[] nums) { 61 | int ans = nums[0]; 62 | int sum = 0; 63 | for(int num: nums) { 64 | if(sum > 0) { 65 | sum += num; 66 | } else { 67 | sum = num; 68 | } 69 | ans = Math.max(ans, sum); 70 | } 71 | return ans; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No58lengthOfLastWord.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No58lengthOfLastWord, 2020/8/25 6 | * 7 | * 最后一个单词的长度 8 | * 9 | * 给定一个仅包含大小写字母和空格 ' ' 的字符串 s,返回其最后一个单词的长度。如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。 10 | * 11 | * 如果不存在最后一个单词,请返回 0 。 12 | * 13 | * 说明:一个单词是指仅由字母组成、不包含任何空格字符的 最大子字符串。 14 | * 15 | * 示例: 16 | * 17 | * 输入: "Hello World" 18 | * 输出: 5 19 | */ 20 | public class No58lengthOfLastWord { 21 | /** 22 | * 从字符串末尾开始向前遍历,其中主要有两种情况 23 | * 第一种情况,以字符串"Hello World"为例,从后向前遍历直到遍历到头或者遇到空格为止,即为最后一个单词"World"的长度5 24 | * 第二种情况,以字符串"Hello World "为例,需要先将末尾的空格过滤掉,再进行第一种情况的操作,即认为最后一个单词为"World",长度为5 25 | * 所以完整过程为先从后过滤掉空格找到单词尾部,再从尾部向前遍历,找到单词头部,最后两者相减,即为单词的长度 26 | * 时间复杂度:O(n),n为结尾空格和结尾单词总体长度 27 | */ 28 | 29 | public int lengthOfLastWord(String s) { 30 | int end = s.length() - 1; 31 | while(end >= 0 && s.charAt(end) == ' ') end--; 32 | if(end < 0) return 0; 33 | int start = end; 34 | while(start >= 0 && s.charAt(start) != ' ') start--; 35 | return end - start; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No66plusOne.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No66plusOne, 2020/8/25 6 | * 7 | * 加一 8 | * 9 | * 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。 10 | * 11 | * 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 12 | * 13 | * 你可以假设除了整数 0 之外,这个整数不会以零开头。 14 | * 15 | * 示例1: 16 | * 17 | * 输入: [1,2,3] 18 | * 输出: [1,2,4] 19 | * 解释: 输入数组表示数字 123。 20 | * 示例 2: 21 | * 22 | * 输入: [4,3,2,1] 23 | * 输出: [4,3,2,2] 24 | * 解释: 输入数组表示数字 4321。 25 | */ 26 | public class No66plusOne { 27 | 28 | /** 29 | * 这道题需要整理出来有哪几种情况,在进行处理会更舒服 30 | * 末位无进位,则末位加一即可,因为末位无进位,前面也不可能产生进位,比如 45 => 46 31 | * 末位有进位,在中间位置进位停止,则需要找到进位的典型标志,即为当前位 %10后为 0,则前一位加 1,直到不为 0 为止,比如 499 => 500 32 | * 末位有进位,并且一直进位到最前方导致结果多出一位,对于这种情况,需要在第 2 种情况遍历结束的基础上,进行单独处理,比如 999 => 1000 33 | * 时间复杂度:O(n) 34 | * @param digits 35 | * @return 36 | */ 37 | public int[] plusOne(int[] digits) { 38 | int len = digits.length; 39 | for(int i = len - 1; i >= 0; i--) { 40 | digits[i]++; 41 | digits[i] %= 10; 42 | if(digits[i]!=0) 43 | return digits; 44 | } 45 | digits = new int[len + 1]; 46 | digits[0] = 1; 47 | return digits; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No67addBinary.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No67addBinary, 2020/8/25 6 | * 7 | * 二进制求和 8 | 给你两个二进制字符串,返回它们的和(用二进制表示)。 9 | 10 | 输入为 非空 字符串且只包含数字 1 和 0。 11 | 12 | 示例 1: 13 | 14 | 输入: a = "11", b = "1" 15 | 输出: "100" 16 | 示例 2: 17 | 18 | 输入: a = "1010", b = "1011" 19 | 输出: "10101" 20 | 21 | 22 | 提示: 23 | 24 | 每个字符串仅由字符 '0' 或 '1' 组成。 25 | 1 <= a.length, b.length <= 10^4 26 | 字符串如果不是 "0" ,就都不含前导零。 27 | * 28 | */ 29 | public class No67addBinary { 30 | 31 | /** 32 | * 整体思路是将两个字符串较短的用 00 补齐,使得两个字符串长度一致,然后从末尾进行遍历计算,得到最终结果。 33 | * 34 | * 本题解中大致思路与上述一致,但由于字符串操作原因,不确定最后的结果是否会多出一位进位,所以会有 2 种处理方式: 35 | * 36 | * 第一种,在进行计算时直接拼接字符串,会得到一个反向字符,需要最后再进行翻转 37 | * 第二种,按照位置给结果字符赋值,最后如果有进位,则在前方进行字符串拼接添加进位 38 | * 时间复杂度:O(n)O(n) 39 | * 40 | * @param a 41 | * @param b 42 | * @return 43 | */ 44 | public String addBinary(String a, String b) { 45 | StringBuilder ans = new StringBuilder(); 46 | int ca = 0; 47 | for(int i = a.length() - 1, j = b.length() - 1;i >= 0 || j >= 0; i--, j--) { 48 | int sum = ca; 49 | sum += i >= 0 ? a.charAt(i) - '0' : 0; 50 | sum += j >= 0 ? b.charAt(j) - '0' : 0; 51 | ans.append(sum % 2); 52 | ca = sum / 2; 53 | } 54 | ans.append(ca == 1 ? ca : ""); 55 | return ans.reverse().toString(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No69mySqrt.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No69mySqrt, 2020/8/25 6 | * 7 | * x 的平方根 8 | * 实现 int sqrt(int x) 函数。 9 | * 10 | * 计算并返回 x 的平方根,其中 x 是非负整数。 11 | * 12 | * 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 13 | * 14 | * 示例 1: 15 | * 16 | * 输入: 4 17 | * 输出: 2 18 | * 示例 2: 19 | * 20 | * 输入: 8 21 | * 输出: 2 22 | * 说明: 8 的平方根是 2.82842..., 23 | * 由于返回类型是整数,小数部分将被舍去。 24 | */ 25 | public class No69mySqrt { 26 | 27 | /** 28 | * 袖珍计算器算法 29 | * 「袖珍计算器算法」是一种用指数函数 \expexp 和对数函数 \lnln 代替平方根函数的方法。我们通过有限的可以使用的数学函数,得到我们想要计算的结果。 30 | * 31 | * 我们将 sqrt{x} 32 | * 写成幂的形式 x^{1/2} 33 | * ,再使用自然对数 ee 进行换底,即可得到 34 | * 35 | * 在得到结果的整数部分 \textit{ans}ans 后,我们应当找出 \textit{ans}ans 与 \textit{ans} + 1ans+1 中哪一个是真正的答案。 36 | */ 37 | 38 | public int mySqrt1(int x) { 39 | if (x == 0) { 40 | return 0; 41 | } 42 | int ans = (int)Math.exp(0.5 * Math.log(x)); 43 | return (long)(ans + 1) * (ans + 1) <= x ? ans + 1 : ans; 44 | } 45 | 46 | /** 47 | * 二分查找 48 | * 由于 xx 平方根的整数部分 ans 是满足 k^2 <=2 49 | * 的最大 k 值,因此我们可以对 k 进行二分查找,从而得到答案。 50 | * 51 | * 二分查找的下界为 0,上界可以粗略地设定为 x。在二分查找的每一步中, 52 | * 我们只需要比较中间元素 mid 的平方与 x 的大小关系,并通过比较的结果调整上下界的范围。 53 | * 由于我们所有的运算都是整数运算,不会存在误差,因此在得到最终的答案 ans 后,也就不需要再去尝试 ans+1 了。 54 | * 55 | */ 56 | 57 | public int mySqrt2(int x) { 58 | int l = 0, r = x, ans = -1; 59 | while (l <= r) { 60 | int mid = l + (r - l) / 2; 61 | if ((long)mid * mid <= x) { 62 | ans = mid; 63 | l = mid + 1; 64 | } 65 | else { 66 | r = mid - 1; 67 | } 68 | } 69 | return ans; 70 | } 71 | 72 | 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No70climbStairs.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No70climbStairs, 2020/8/25 6 | * 7 | * 爬楼梯 8 | * 9 | * 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 10 | * 11 | * 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 12 | * 13 | * 注意:给定 n 是一个正整数。 14 | * 15 | * 示例 1: 16 | * 17 | * 输入: 2 18 | * 输出: 2 19 | * 解释: 有两种方法可以爬到楼顶。 20 | * 1. 1 阶 + 1 阶 21 | * 2. 2 阶 22 | * 示例 2: 23 | * 24 | * 输入: 3 25 | * 输出: 3 26 | * 解释: 有三种方法可以爬到楼顶。 27 | * 1. 1 阶 + 1 阶 + 1 阶 28 | * 2. 1 阶 + 2 阶 29 | * 3. 2 阶 + 1 阶 30 | * 31 | */ 32 | public class No70climbStairs { 33 | 34 | /** 35 | * 36 | * 方法1 37 | * 动态规划 38 | * 本问题其实常规解法可以分成多个子问题,爬第n阶楼梯的方法数量,等于 2 部分之和 39 | * 40 | * 爬上 n−1 阶楼梯的方法数量。因为再爬1阶就能到第n阶 41 | * 爬上 n−2 阶楼梯的方法数量,因为再爬2阶就能到第n阶 42 | * 所以我们得到公式 dp[n]=dp[n−1]+dp[n−2] 43 | * 同时需要初始化 dp[0]=1 和 dp[1]=1 44 | * 时间复杂度:O(n) 45 | */ 46 | 47 | public int climbStairs1(int n) { 48 | int[] dp = new int[n + 1]; 49 | dp[0] = 1; 50 | dp[1] = 1; 51 | for(int i = 2; i <= n; i++) { 52 | dp[i] = dp[i - 1] + dp[i - 2]; 53 | } 54 | return dp[n]; 55 | } 56 | 57 | /** 58 | * 我们不难通过转移方程和边界条件给出一个时间复杂度和空间复杂度都是 O(n) 的实现, 59 | * 但是由于这里的 f(x) 只和 f(x−1) 与 f(x−2) 有关,所以我们可以用「滚动数组思想」把空间复杂度优化成 O(1)。 60 | * 下面的代码中给出的就是这种实现。 61 | * 62 | */ 63 | 64 | public int climbStairs2(int n) { 65 | int p = 0, q = 0, r = 1; 66 | for (int i = 1; i <= n; ++i) { 67 | p = q; 68 | q = r; 69 | r = p + q; 70 | } 71 | return r; 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No7ReverseInt.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * 整数反转 5 | * 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。 6 | * 示例 1: 7 | * 8 | * 输入: 123 9 | * 输出: 321 10 | * 示例 2: 11 | * 12 | * 输入: -123 13 | * 输出: -321 14 | * 示例 3: 15 | * 16 | * 输入: 120 17 | * 输出: 21 18 | * 注意: 19 | * 20 | * 假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。 21 | * @author wayne 22 | * @version No7Solution, 2020/8/25 23 | */ 24 | 25 | public class No7ReverseInt { 26 | 27 | /** 28 | *通过循环将数字x的每一位拆开,在计算新值时每一步都判断是否溢出。 29 | * 溢出条件有两个,一个是大于整数最大值MAX_VALUE,另一个是小于整数最小值MIN_VALUE,设当前计算结果为ans,下一位为pop。 30 | * 从ans * 10 + pop > MAX_VALUE这个溢出条件来看 31 | * 当出现 ans > MAX_VALUE / 10 且 还有pop需要添加 时,则一定溢出 32 | * 当出现 ans == MAX_VALUE / 10 且 pop > 7 时,则一定溢出,7是2^31 - 1的个位数 33 | * 从ans * 10 + pop < MIN_VALUE这个溢出条件来看 34 | * 当出现 ans < MIN_VALUE / 10 且 还有pop需要添加 时,则一定溢出 35 | * 当出现 ans == MIN_VALUE / 10 且 pop < -8 时,则一定溢出,8是-2^31的个位数 36 | * @param x 37 | * @return 38 | */ 39 | public int reverse(int x) { 40 | int ans = 0; 41 | while (x != 0) { 42 | int pop = x % 10; 43 | if (ans > Integer.MAX_VALUE / 10 || (ans == Integer.MAX_VALUE / 10 && pop > 7)) 44 | return 0; 45 | if (ans < Integer.MIN_VALUE / 10 || (ans == Integer.MIN_VALUE / 10 && pop < -8)) 46 | return 0; 47 | ans = ans * 10 + pop; 48 | x /= 10; 49 | } 50 | return ans; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No83deleteDuplicates.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * @author wayne 5 | * @version No83删除排序链表中的重复元素, 2020/8/25 6 | * 7 | * 删除排序链表中的重复元素 8 | * 9 | * 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 10 | * 11 | * 示例 1: 12 | * 13 | * 输入: 1->1->2 14 | * 输出: 1->2 15 | * 示例 2: 16 | * 17 | * 输入: 1->1->2->3->3 18 | * 输出: 1->2->3 19 | */ 20 | public class No83deleteDuplicates { 21 | 22 | /** 23 | * 这是一个简单的问题,仅测试你操作列表的结点指针的能力。 24 | * 由于输入的列表已排序,因此我们可以通过将结点的值与它之后的结点进行比较来确定它是否为重复结点。 25 | * 如果它是重复的,我们更改当前结点的 next 指针,以便它跳过下一个结点并直接指向下一个结点之后的结点。 26 | * 27 | */ 28 | 29 | public ListNode deleteDuplicates(ListNode head) { 30 | ListNode current = head; 31 | while (current != null && current.next != null) { 32 | if (current.next.val == current.val) { 33 | current.next = current.next.next; 34 | } else { 35 | current = current.next; 36 | } 37 | } 38 | return head; 39 | } 40 | 41 | public static class ListNode { 42 | int val; 43 | ListNode next; 44 | ListNode(int x) { val = x; } 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No88mergeTwoArray.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * @author wayne 7 | * @version No88mergeTwoArray, 2020/8/26 8 | * 9 | * 合并两个有序数组 10 | * 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。 11 | * 12 | * 13 | * 14 | * 说明: 15 | * 16 | * 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 17 | * 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 18 | * 19 | * 20 | * 示例: 21 | * 22 | * 输入: 23 | * nums1 = [1,2,3,0,0,0], m = 3 24 | * nums2 = [2,5,6], n = 3 25 | * 26 | * 输出: [1,2,2,3,5,6] 27 | */ 28 | public class No88mergeTwoArray { 29 | 30 | /** 31 | * 方法1: 合并后排序 32 | * 33 | */ 34 | 35 | public void merge1(int[] nums1, int m, int[] nums2, int n) { 36 | System.arraycopy(nums2, 0, nums1, m, n); 37 | Arrays.sort(nums1); 38 | } 39 | 40 | /** 41 | *方法2: 双指针 / 从前往后 42 | * 最直接的算法实现是将指针p1 置为 nums1的开头, p2为 nums2的开头,在每一步将最小值放入输出数组中。 43 | * 44 | * 由于 nums1 是用于输出的数组,需要将nums1中的前m个元素放在其他地方,也就需要 O(m) 的空间复杂度。 45 | */ 46 | 47 | public void merge2(int[] nums1, int m, int[] nums2, int n) { 48 | // Make a copy of nums1. 49 | int [] nums1_copy = new int[m]; 50 | System.arraycopy(nums1, 0, nums1_copy, 0, m); 51 | 52 | // Two get pointers for nums1_copy and nums2. 53 | int p1 = 0; 54 | int p2 = 0; 55 | 56 | // Set pointer for nums1 57 | int p = 0; 58 | 59 | // Compare elements from nums1_copy and nums2 60 | // and add the smallest one into nums1. 61 | while ((p1 < m) && (p2 < n)) 62 | nums1[p++] = (nums1_copy[p1] < nums2[p2]) ? nums1_copy[p1++] : nums2[p2++]; 63 | 64 | // if there are still elements to add 65 | if (p1 < m) 66 | System.arraycopy(nums1_copy, p1, nums1, p1 + p2, m + n - p1 - p2); 67 | if (p2 < n) 68 | System.arraycopy(nums2, p2, nums1, p1 + p2, m + n - p1 - p2); 69 | } 70 | 71 | /** 72 | * 方法3: 双指针 / 从后往前 73 | * 74 | * 方法二已经取得了最优的时间复杂度O(n+m),但需要使用额外空间。这是由于在从头改变nums1的值时,需要把nums1中的元素存放在其他位置。 75 | * 76 | * 如果我们从结尾开始改写 nums1 的值又会如何呢?这里没有信息,因此不需要额外空间。 77 | * 78 | * 这里的指针 p 用于追踪添加元素的位置。 79 | */ 80 | 81 | public void merge3(int[] nums1, int m, int[] nums2, int n) { 82 | // two get pointers for nums1 and nums2 83 | int p1 = m - 1; 84 | int p2 = n - 1; 85 | // set pointer for nums1 86 | int p = m + n - 1; 87 | 88 | // while there are still elements to compare 89 | while ((p1 >= 0) && (p2 >= 0)) 90 | // compare two elements from nums1 and nums2 91 | // and add the largest one in nums1 92 | nums1[p--] = (nums1[p1] < nums2[p2]) ? nums2[p2--] : nums1[p1--]; 93 | 94 | // add missing elements from nums2 95 | System.arraycopy(nums2, 0, nums1, 0, p2 + 1); 96 | } 97 | 98 | 99 | } 100 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/easy/No9IsPalindrome.java: -------------------------------------------------------------------------------- 1 | package com.flydean.easy; 2 | 3 | /** 4 | * 回文数 5 | * 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 6 | * 7 | * 示例 1: 8 | * 9 | * 输入: 121 10 | * 输出: true 11 | * 示例 2: 12 | * 13 | * 输入: -121 14 | * 输出: false 15 | * 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 16 | * 示例 3: 17 | * 18 | * 输入: 10 19 | * 输出: false 20 | * 解释: 从右向左读, 为 01 。因此它不是一个回文数。 21 | * 进阶: 22 | * 23 | * 你能不将整数转为字符串来解决这个问题吗? 24 | * @author wayne 25 | * @version No9Solution, 2020/8/25 26 | */ 27 | 28 | /** 29 | * 方法一:反转一半数字 30 | * 思路 31 | * 32 | * 映入脑海的第一个想法是将数字转换为字符串,并检查字符串是否为回文。但是,这需要额外的非常量空间来创建问题描述中所不允许的字符串。 33 | * 34 | * 第二个想法是将数字本身反转,然后将反转后的数字与原始数字进行比较,如果它们是相同的,那么这个数字就是回文。 35 | * 但是,如果反转后的数字大于 \text{int.MAX}int.MAX,我们将遇到整数溢出问题。 36 | * 37 | * 按照第二个想法,为了避免数字反转可能导致的溢出问题,为什么不考虑只反转 \text{int}int 数字的一半?毕竟,如果该数字是回文,其后半部分反转后应该与原始数字的前半部分相同。 38 | * 39 | * 例如,输入 1221,我们可以将数字 “1221” 的后半部分从 “21” 反转为 “12”,并将其与前半部分 “12” 进行比较,因为二者相同,我们得知数字 1221 是回文。 40 | * 41 | * 算法 42 | * 43 | * 首先,我们应该处理一些临界情况。所有负数都不可能是回文,例如:-123 不是回文,因为 - 不等于 3。所以我们可以对所有负数返回 false。除了 0 以外,所有个位是 0 的数字不可能是回文,因为最高位不等于 0。所以我们可以对所有大于 0 且个位是 0 的数字返回 false。 44 | * 45 | * 现在,让我们来考虑如何反转后半部分的数字。 46 | * 47 | * 对于数字 1221,如果执行 1221 % 10,我们将得到最后一位数字 1,要得到倒数第二位数字,我们可以先通过除以 10 把最后一位数字从 1221 中移除,1221 / 10 = 122,再求出上一步结果除以 10 的余数,122 % 10 = 2,就可以得到倒数第二位数字。如果我们把最后一位数字乘以 10,再加上倒数第二位数字,1 * 10 + 2 = 12,就得到了我们想要的反转后的数字。如果继续这个过程,我们将得到更多位数的反转数字。 48 | * 49 | * 现在的问题是,我们如何知道反转数字的位数已经达到原始数字位数的一半? 50 | * 51 | * 由于整个过程我们不断将原始数字除以 10,然后给反转后的数字乘上 10,所以,当原始数字小于或等于反转后的数字时,就意味着我们已经处理了一半位数的数字了。 52 | * 53 | */ 54 | public class No9IsPalindrome { 55 | 56 | public boolean isPalindrome(int x) { 57 | // 特殊情况: 58 | // 如上所述,当 x < 0 时,x 不是回文数。 59 | // 同样地,如果数字的最后一位是 0,为了使该数字为回文, 60 | // 则其第一位数字也应该是 0 61 | // 只有 0 满足这一属性 62 | if (x < 0 || (x % 10 == 0 && x != 0)) { 63 | return false; 64 | } 65 | 66 | int revertedNumber = 0; 67 | while (x > revertedNumber) { 68 | revertedNumber = revertedNumber * 10 + x % 10; 69 | x /= 10; 70 | } 71 | 72 | // 当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。 73 | // 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,revertedNumber = 123, 74 | // 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。 75 | return x == revertedNumber || x == revertedNumber / 10; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/middle/No102levelOrderForTree.java: -------------------------------------------------------------------------------- 1 | package com.flydean.middle; 2 | 3 | import java.util.ArrayList; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | 7 | /** 8 | * @author wayne 9 | * @version No102levelOrderForTree, 2020/8/26 10 | * 11 | * 二叉树的层序遍历 12 | * 13 | * 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 14 | * 15 | * 示例: 16 | * 二叉树:[3,9,20,null,null,15,7], 17 | * 18 | * 3 19 | * / \ 20 | * 9 20 21 | * / \ 22 | * 15 7 23 | * 返回其层次遍历结果: 24 | * 25 | * [ 26 | * [3], 27 | * [9,20], 28 | * [15,7] 29 | * ] 30 | */ 31 | public class No102levelOrderForTree { 32 | 33 | /** 34 | * 宽度优先搜索 35 | * 思路和算法 36 | * 37 | * 我们可以用宽度优先搜索解决这个问题。 38 | * 39 | * 我们可以想到最朴素的方法是用一个二元组 (node, level) 来表示状态, 40 | * 它表示某个节点和它所在的层数,每个新进队列的节点的 level 值都是父亲节点的 level 值加一。 41 | * 最后根据每个点的 level 对点进行分类,分类的时候我们可以利用哈希表, 42 | * 维护一个以 level 为键,对应节点值组成的数组为值,宽度优先搜索结束以后按键 level 从小到大取出所有值,组成答案返回即可。 43 | * 44 | * 考虑如何优化空间开销:如何不用哈希映射,并且只用一个变量 node 表示状态,实现这个功能呢? 45 | * 46 | * 我们可以用一种巧妙的方法修改 BFS: 47 | * 48 | * 首先根元素入队 49 | * 当队列不为空的时候 50 | * 求当前队列的长度 si 51 | * 依次从队列中取 si 个元素进行拓展,然后进入下一次迭代 52 | * 它和 BFS 的区别在于 BFS 每次只取一个元素拓展,而这里每次取 si个元素。 53 | * 在上述过程中的第 i 次迭代就得到了二叉树的第 i 层的 si个元素。 54 | 55 | * @param root 56 | * @return 57 | */ 58 | public List> levelOrder(TreeNode root) { 59 | List> lists = new LinkedList<>(); 60 | if (root == null) { 61 | return lists; 62 | } 63 | //2. 64 | List nodes = new LinkedList<>(); 65 | nodes.add(root); 66 | while (!nodes.isEmpty()) { 67 | int size = nodes.size(); 68 | List list = new ArrayList<>(); 69 | for (int i = 0; i < size; i++) { 70 | TreeNode remove = nodes.remove(0); 71 | list.add(remove.val); 72 | if (remove.left != null) { 73 | nodes.add(remove.left); 74 | } 75 | if (remove.right != null) { 76 | nodes.add(remove.right); 77 | } 78 | } 79 | lists.add(list); 80 | } 81 | return lists; 82 | } 83 | 84 | //双队列 85 | public List> levelOrder2(TreeNode root) { 86 | List> res=new ArrayList<>(); 87 | if (root==null) 88 | return res; 89 | LinkedList queue1=new LinkedList<>(),queue2=new LinkedList<>(); 90 | queue1.offer(root); 91 | while (!queue1.isEmpty()){ 92 | List item=new ArrayList<>(); 93 | while (!queue1.isEmpty()){ 94 | TreeNode node=queue1.remove(); 95 | item.add(node.val); 96 | if (node.left!=null) 97 | queue2.offer(node.left); 98 | if (node.right!=null) 99 | queue2.offer(node.right); 100 | } 101 | res.add(item); 102 | LinkedList tmp=queue1; 103 | queue1=queue2; 104 | queue2=tmp; 105 | } 106 | return res; 107 | } 108 | 109 | public static class TreeNode { 110 | int val; 111 | TreeNode left; 112 | TreeNode right; 113 | TreeNode(int x) { val = x; } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /leecode/src/main/java/com/flydean/middle/No144preorderTraversal.java: -------------------------------------------------------------------------------- 1 | package com.flydean.middle; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Stack; 6 | 7 | /** 8 | * @author wayne 9 | * @version No144preorderTraversal, 2020/8/26 10 | * 11 | * 二叉树的前序遍历 12 | * 给定一个二叉树,返回它的 前序 遍历。 13 | * 14 | * 示例: 15 | * 16 | * 输入: [1,null,2,3] 17 | * 1 18 | * \ 19 | * 2 20 | * / 21 | * 3 22 | * 23 | * 输出: [1,2,3] 24 | * 进阶: 递归算法很简单,你可以通过迭代算法完成吗? 25 | */ 26 | public class No144preorderTraversal { 27 | 28 | 29 | /** 30 | * 递归解法 31 | * @param head 32 | */ 33 | public static void preOrderRecur(TreeNode head) { 34 | if (head == null) { 35 | return; 36 | } 37 | System.out.print(head.val + " "); 38 | preOrderRecur(head.left); 39 | preOrderRecur(head.right); 40 | } 41 | 42 | /** 43 | * 迭代算法 44 | */ 45 | 46 | public List preorderTraversal(TreeNode root) { 47 | List resultList= new ArrayList<>(); 48 | if (root == null) { 49 | return resultList; 50 | } 51 | Stack stack = new Stack<>(); 52 | stack.push(root); 53 | while (!stack.isEmpty()) { 54 | TreeNode node = stack.pop(); 55 | resultList.add(node.val); 56 | if (node.right != null) { 57 | stack.push(node.right); 58 | } 59 | if (node.left != null) { 60 | stack.push(node.left); 61 | } 62 | } 63 | return resultList; 64 | } 65 | 66 | public static class TreeNode { 67 | int val; 68 | TreeNode left; 69 | TreeNode right; 70 | TreeNode(int x) { val = x; } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /list/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | list 13 | 14 | 15 | -------------------------------------------------------------------------------- /list/src/main/java/com/flydean/DoublyLinkedList.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version DoublyLinkedList 6 | */ 7 | public class DoublyLinkedList { 8 | 9 | Node head; // head 节点 10 | 11 | //Node表示的是Linked list中的节点,包含一个data数据,上一个节点和下一个节点的引用 12 | class Node { 13 | int data; 14 | Node next; 15 | Node prev; 16 | //Node的构造函数 17 | Node(int d) { 18 | data = d; 19 | } 20 | } 21 | 22 | //插入到linkedList的头部 23 | public void push(int newData) { 24 | //构建要插入的节点 25 | Node newNode = new Node(newData); 26 | //新节点的next指向现在的head节点 27 | //新节点的prev指向null 28 | newNode.next = head; 29 | newNode.prev = null; 30 | 31 | if (head != null) 32 | head.prev = newNode; 33 | 34 | //现有的head节点指向新的节点 35 | head = newNode; 36 | } 37 | 38 | //插入在第几个元素之后 39 | public void insertAfter(int index, int newData) { 40 | Node prevNode = head; 41 | for (int i = 1; i < index; i++) { 42 | if (prevNode == null) { 43 | System.out.println("输入的index有误,请重新输入"); 44 | return; 45 | } 46 | prevNode = prevNode.next; 47 | } 48 | //创建新的节点 49 | Node newNode = new Node(newData); 50 | //新节点的next指向prevNode的下一个节点 51 | newNode.next = prevNode.next; 52 | //将新节点插入在prevNode之后 53 | prevNode.next = newNode; 54 | //将新节点的prev指向prevNode 55 | newNode.prev = prevNode; 56 | 57 | //newNode的下一个节点的prev指向newNode 58 | if (newNode.next != null) 59 | newNode.next.prev = newNode; 60 | } 61 | 62 | //新节点插入到list最后面 63 | public void append(int newData) { 64 | //创建新节点 65 | Node newNode = new Node(newData); 66 | //如果list是空,则新节点作为head节点 67 | if (head == null) { 68 | newNode.prev = null; 69 | head = newNode; 70 | return; 71 | } 72 | 73 | newNode.next = null; 74 | //找到最后一个节点 75 | Node last = head; 76 | while (last.next != null) { 77 | last = last.next; 78 | } 79 | //插入 80 | last.next = newNode; 81 | newNode.prev = last; 82 | return; 83 | } 84 | 85 | //删除特定位置的节点 86 | void deleteNode(int index) 87 | { 88 | // 如果是空的,直接返回 89 | if (head == null) 90 | return; 91 | 92 | // head节点 93 | Node temp = head; 94 | 95 | // 如果是删除head节点 96 | if (index == 1) 97 | { 98 | head = temp.next; 99 | return; 100 | } 101 | 102 | // 找到要删除节点的前一个节点 103 | for (int i=1; temp!=null && inext 是要删除的节点,删除节点 111 | Node next = temp.next.next; 112 | temp.next = next; 113 | Node prev = temp.next.prev; 114 | prev.prev = prev; 115 | } 116 | 117 | public void printList() { 118 | Node tnode = head; 119 | while (tnode != null) { 120 | System.out.print(tnode.data + " "); 121 | tnode = tnode.next; 122 | } 123 | System.out.println(" "); 124 | } 125 | 126 | public static void main(String[] args) { 127 | DoublyLinkedList linkedList = new DoublyLinkedList(); 128 | //插入数据 129 | linkedList.push(13); 130 | linkedList.push(20); 131 | linkedList.printList(); 132 | //插入到最后 133 | linkedList.append(34); 134 | linkedList.append(21); 135 | linkedList.printList(); 136 | //在第二个节点之后插入 137 | linkedList.insertAfter(2, 55); 138 | linkedList.printList(); 139 | //删除第三个节点 140 | linkedList.deleteNode(3); 141 | linkedList.printList(); 142 | 143 | 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /list/src/main/java/com/flydean/LinkedList.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version LinkedList 6 | */ 7 | public class LinkedList { 8 | 9 | Node head; // head 节点 10 | 11 | //Node表示的是Linked list中的节点,包含一个data数据和下一个节点的引用 12 | class Node { 13 | int data; 14 | Node next; 15 | //Node的构造函数 16 | Node(int d) { 17 | data = d; 18 | } 19 | } 20 | 21 | //插入到linkedList的头部 22 | public void push(int newData) { 23 | //构建要插入的节点 24 | Node newNode = new Node(newData); 25 | //新节点的next指向现在的head节点 26 | newNode.next = head; 27 | //现有的head节点指向新的节点 28 | head = newNode; 29 | } 30 | 31 | //插入在第几个元素之后 32 | public void insertAfter(int index, int newData) { 33 | Node prevNode = head; 34 | for (int i = 1; i < index; i++) { 35 | if (prevNode == null) { 36 | System.out.println("输入的index有误,请重新输入"); 37 | return; 38 | } 39 | prevNode = prevNode.next; 40 | } 41 | //创建新的节点 42 | Node newNode = new Node(newData); 43 | //新节点的next指向prevNode的下一个节点 44 | newNode.next = prevNode.next; 45 | //将新节点插入在prevNode之后 46 | prevNode.next = newNode; 47 | } 48 | 49 | //新节点插入到list最后面 50 | public void append(int newData) { 51 | //创建新节点 52 | Node newNode = new Node(newData); 53 | //如果list是空,则新节点作为head节点 54 | if (head == null) { 55 | head = newNode; 56 | return; 57 | } 58 | 59 | newNode.next = null; 60 | //找到最后一个节点 61 | Node last = head; 62 | while (last.next != null) { 63 | last = last.next; 64 | } 65 | //插入 66 | last.next = newNode; 67 | return; 68 | } 69 | 70 | //删除特定位置的节点 71 | void deleteNode(int index) 72 | { 73 | // 如果是空的,直接返回 74 | if (head == null) 75 | return; 76 | 77 | // head节点 78 | Node temp = head; 79 | 80 | // 如果是删除head节点 81 | if (index == 1) 82 | { 83 | head = temp.next; 84 | return; 85 | } 86 | 87 | // 找到要删除节点的前一个节点 88 | for (int i=1; temp!=null && inext 是要删除的节点,删除节点 96 | Node next = temp.next.next; 97 | temp.next = next; 98 | } 99 | 100 | public void printList() { 101 | Node tnode = head; 102 | while (tnode != null) { 103 | System.out.print(tnode.data + " "); 104 | tnode = tnode.next; 105 | } 106 | System.out.println(" "); 107 | } 108 | 109 | public static void main(String[] args) { 110 | LinkedList linkedList = new LinkedList(); 111 | //插入数据 112 | linkedList.push(13); 113 | linkedList.push(20); 114 | linkedList.printList(); 115 | //插入到最后 116 | linkedList.append(34); 117 | linkedList.append(21); 118 | linkedList.printList(); 119 | //在第二个节点之后插入 120 | linkedList.insertAfter(2, 55); 121 | linkedList.printList(); 122 | //删除第三个节点 123 | linkedList.deleteNode(3); 124 | linkedList.printList(); 125 | 126 | 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.example 8 | learn-algorithm 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | sorting 13 | list 14 | stack 15 | queue 16 | tree 17 | heap 18 | hashTable 19 | cyclefinding 20 | leecode 21 | recursion 22 | trie 23 | array 24 | binarySearch 25 | binarySearch 26 | binarySearch 27 | binary 28 | 29 | 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | 1.18.10 35 | 36 | 37 | 38 | ch.qos.logback 39 | logback-classic 40 | 1.2.3 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-compiler-plugin 50 | 3.8.1 51 | 52 | 17 53 | 17 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /queue/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | queue 13 | 14 | 15 | -------------------------------------------------------------------------------- /queue/src/main/java/com/flydean/ArrayDeQueue.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * 使用循环数组来存储数据 5 | * @author wayne 6 | * @version ArrayDeQueue 7 | */ 8 | public class ArrayDeQueue { 9 | 10 | //存储数据的数组 11 | private int[] array; 12 | //head索引 13 | private int head; 14 | //real索引 15 | private int rear; 16 | //数组容量 17 | private int capacity; 18 | 19 | public ArrayDeQueue(int capacity){ 20 | this.capacity=capacity; 21 | this.head=-1; 22 | this.rear =-1; 23 | this.array= new int[capacity]; 24 | } 25 | 26 | public boolean isEmpty(){ 27 | return head == -1; 28 | } 29 | 30 | public boolean isFull(){ 31 | return (rear +1)%capacity==head; 32 | } 33 | 34 | public int getQueueSize(){ 35 | if(head == -1){ 36 | return 0; 37 | } 38 | return (rear +1-head+capacity)%capacity; 39 | } 40 | 41 | //从尾部入队列 42 | public void insertLast(int data){ 43 | if(isFull()){ 44 | System.out.println("Queue is full"); 45 | }else{ 46 | //从尾部插入ArrayDeque 47 | rear = (rear +1)%capacity; 48 | array[rear]= data; 49 | //如果插入之前队列为空,将head指向real 50 | if(head == -1 ){ 51 | head = rear; 52 | } 53 | } 54 | } 55 | 56 | //从头部入队列 57 | public void insertFront(int data){ 58 | if(isFull()){ 59 | System.out.println("Queue is full"); 60 | }else{ 61 | //从头部插入ArrayDeque 62 | head = (head + capacity - 1) % capacity; 63 | array[head]= data; 64 | //如果插入之前队列为空,将real指向head 65 | if(rear == -1 ){ 66 | rear = head; 67 | } 68 | } 69 | } 70 | 71 | 72 | //从头部取数据 73 | public int deleteFront(){ 74 | int data; 75 | if(isEmpty()){ 76 | System.out.println("Queue is empty"); 77 | return -1; 78 | }else{ 79 | data= array[head]; 80 | //如果只有一个元素,则重置head和real 81 | if(head == rear){ 82 | head= -1; 83 | rear = -1; 84 | }else{ 85 | head = (head+1)%capacity; 86 | } 87 | return data; 88 | } 89 | } 90 | 91 | //从尾部取数据 92 | public int deleteLast(){ 93 | int data; 94 | if(isEmpty()){ 95 | System.out.println("Queue is empty"); 96 | return -1; 97 | }else{ 98 | data= array[rear]; 99 | //如果只有一个元素,则重置head和real 100 | if(head == rear){ 101 | head= -1; 102 | rear = -1; 103 | }else{ 104 | rear = (rear + capacity - 1)%capacity; 105 | } 106 | return data; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /queue/src/main/java/com/flydean/ArrayQueue.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * 使用循环数组来存储数据 5 | * @author wayne 6 | * @version ArrayQueue 7 | */ 8 | public class ArrayQueue { 9 | 10 | //存储数据的数组 11 | private int[] array; 12 | //head索引 13 | private int head; 14 | //real索引 15 | private int rear; 16 | //数组容量 17 | private int capacity; 18 | 19 | public ArrayQueue (int capacity){ 20 | this.capacity=capacity; 21 | this.head=-1; 22 | this.rear =-1; 23 | this.array= new int[capacity]; 24 | } 25 | 26 | public boolean isEmpty(){ 27 | return head == -1; 28 | } 29 | 30 | public boolean isFull(){ 31 | return (rear +1)%capacity==head; 32 | } 33 | 34 | public int getQueueSize(){ 35 | if(head == -1){ 36 | return 0; 37 | } 38 | return (rear +1-head+capacity)%capacity; 39 | } 40 | 41 | //从尾部入队列 42 | public void enQueue(int data){ 43 | if(isFull()){ 44 | System.out.println("Queue is full"); 45 | }else{ 46 | //从尾部插入 47 | rear = (rear +1)%capacity; 48 | array[rear]= data; 49 | //如果插入之前队列为空,将head指向real 50 | if(head == -1 ){ 51 | head = rear; 52 | } 53 | } 54 | } 55 | 56 | //从头部取数据 57 | public int deQueue(){ 58 | int data; 59 | if(isEmpty()){ 60 | System.out.println("Queue is empty"); 61 | return -1; 62 | }else{ 63 | data= array[head]; 64 | //如果只有一个元素,则重置head和real 65 | if(head == rear){ 66 | head= -1; 67 | rear = -1; 68 | }else{ 69 | head = (head+1)%capacity; 70 | } 71 | return data; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /queue/src/main/java/com/flydean/DyncArrayDeQueue.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * 使用动态循环数组来存储数据 5 | * @author wayne 6 | * @version DyncArrayDeQueue 7 | */ 8 | public class DyncArrayDeQueue { 9 | 10 | //存储数据的数组 11 | private int[] array; 12 | //head索引 13 | private int head; 14 | //real索引 15 | private int rear; 16 | //数组容量 17 | private int capacity; 18 | 19 | public DyncArrayDeQueue(int capacity){ 20 | this.capacity=capacity; 21 | this.head=-1; 22 | this.rear =-1; 23 | this.array= new int[capacity]; 24 | } 25 | 26 | public boolean isEmpty(){ 27 | return head == -1; 28 | } 29 | 30 | public boolean isFull(){ 31 | return (rear +1)%capacity==head; 32 | } 33 | 34 | public int getQueueSize(){ 35 | if(head == -1){ 36 | return 0; 37 | } 38 | return (rear +1-head+capacity)%capacity; 39 | } 40 | 41 | //因为是循环数组,这里不能做简单的数组拷贝 42 | private void extendQueue(){ 43 | int newCapacity= capacity*2; 44 | int[] newArray= new int[newCapacity]; 45 | //先全部拷贝 46 | System.arraycopy(array,0,newArray,0,array.length); 47 | //如果real((a, b) -> (b - a)); 9 | PriorityQueue queMax = new PriorityQueue((a, b) -> (a - b)); 10 | 11 | queMin.offer(1); 12 | queMin.offer(2); 13 | System.out.println(queMin.peek()); 14 | System.out.println(queMin.poll()); 15 | 16 | queMax.offer(1); 17 | queMax.offer(2); 18 | System.out.println(queMax.peek()); 19 | System.out.println(queMax.poll()); 20 | 21 | 22 | 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /recursion/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | recursion 13 | 14 | 15 | -------------------------------------------------------------------------------- /recursion/src/main/java/com/flydean/CoinChange.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * 硬币兑换问题 5 | * @author wayne 6 | * @version CoinChange, 2020/8/24 7 | */ 8 | public class CoinChange { 9 | 10 | public int f(int v){ 11 | int[] coins = new int[]{1,3,4,5}; 12 | if (v == 0) return 0; /* base case */ 13 | /* recursive caseS */ 14 | var ans = 99; //设置一个最大值 15 | for (var i = 0; i < 4; i++) 16 | if (v-coins[i] >= 0) 17 | ans = Math.min(ans, 1 + f(v-coins[i])); 18 | return ans; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /recursion/src/main/java/com/flydean/FactorialNumber.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * 阶乘 5 | * @author wayne 6 | * @version FactorialNumber, 2020/8/23 7 | */ 8 | public class FactorialNumber { 9 | 10 | public int f(int n){ 11 | if (n <= 1) /* base case */ 12 | return 1; 13 | else /* recursive case */ 14 | return n*f(n-1); 15 | } 16 | 17 | public static void main(String[] args) { 18 | FactorialNumber number=new FactorialNumber(); 19 | System.out.println(number.f(4)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /recursion/src/main/java/com/flydean/Fibonacci.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * 斐波那契数列 5 | * @author wayne 6 | * @version Fibonacci, 2020/8/23 7 | */ 8 | public class Fibonacci { 9 | 10 | public int f(int n){ 11 | if (n <= 1) /* base case */ 12 | return n; 13 | else /* recursive caseS */ 14 | return f(n-1) + f(n-2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /recursion/src/main/java/com/flydean/GCD.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version GCD, 2020/8/24 6 | */ 7 | public class GCD { 8 | 9 | public int f(int a, int b){ 10 | if (b == 0) /* base case */ 11 | return a; 12 | else /* recursive case */ 13 | return f(b, a%b); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /recursion/src/main/java/com/flydean/Knapsack.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * 0-1 背包问题 5 | * @author wayne 6 | * @version knapsack, 2020/8/24 7 | */ 8 | public class Knapsack { 9 | 10 | /** 11 | * 12 | * @param count 背包中的元素个数 13 | * @param packageWeight 背包能够容纳的总重量 14 | * @return 15 | */ 16 | public int f (int count, int packageWeight){ 17 | int[] values= new int[]{100, 70, 50, 10}; 18 | int[] weights=new int[]{10, 4, 6, 12}; 19 | 20 | /* base caseS */ 21 | if (packageWeight == 0 || count < 0) return 0; 22 | else if (weights[count] > packageWeight) return f(count-1, packageWeight); 23 | return Math.max( 24 | values[count] + f(count-1, packageWeight-weights[count]), /* take */ 25 | f(count-1, packageWeight)); /* not take */ 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /recursion/src/main/java/com/flydean/LongestSubSeq.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * 数组的最长递增子序列 5 | * @author wayne 6 | * @version LongestSubSeq, 2020/8/24 7 | */ 8 | public class LongestSubSeq { 9 | 10 | public int f(int i){ 11 | int[] numbers = new int[]{-7,10,9,2,3,8,8,1}; 12 | if (i == 0) return 1; /* base case */ 13 | /* recursive caseS */ 14 | var ans = 1; 15 | for (var j = 0; j < i; j++) 16 | if (numbers[j] < numbers[i]) 17 | ans = Math.max(ans, f(j)+1); 18 | return ans; 19 | } 20 | 21 | public static void main(String[] args) { 22 | LongestSubSeq longestSubSeq= new LongestSubSeq(); 23 | System.out.println(longestSubSeq.f(4)); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /recursion/src/main/java/com/flydean/NChooseK.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * N中选K 5 | * @author wayne 6 | * @version NChooseK, 2020/8/24 7 | */ 8 | public class NChooseK { 9 | 10 | public int f(int n, int k){ 11 | if (k == 0 || k == n) /* base caseS */ 12 | return 1; 13 | else /* recursive caseS */ 14 | return f(n-1, k-1) + /* take */ 15 | f(n-1, k); /* not take */ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /recursion/src/main/java/com/flydean/TravelingSalesmen.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version TravelingSalesmen, 2020/8/24 6 | */ 7 | public class TravelingSalesmen { 8 | 9 | public int f(int u, int m){ 10 | int[][] arrays = new int[][]{{0, 20, 42, 35}, {20, 0, 30, 34}, {42, 30, 0, 12}, {35, 34, 12, 0}}; 11 | if (m == (1<<4)-1) return arrays[u][0]; 12 | var ans = 99; /* recursive caseS */ 13 | for (var v = 0; v < 4; v++) 14 | if (v != u && ((m & (1< 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | sorting 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sorting/src/main/java/com/flydean/BubbleSort.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | /** 6 | * @author wayne 7 | * @version BubbleSort 8 | */ 9 | @Slf4j 10 | public class BubbleSort { 11 | 12 | public void doBubbleSort(int[] array){ 13 | log.info("排序前的数组为:{}",array); 14 | //外层循环,遍历所有轮数 15 | for(int i=0; i< array.length-1; i++){ 16 | //内层循环,两两比较,选中较大的数字,进行交换 17 | for(int j=0; jarray[j+1]){ 19 | //交换两个数字 20 | int temp = array[j]; 21 | array[j] = array[j+1]; 22 | array[j+1] = temp; 23 | } 24 | } 25 | log.info("第{}轮排序后的数组为:{}", i+1, array); 26 | } 27 | } 28 | 29 | public static void main(String[] args) { 30 | int[] array= {29,10,14,37,20,25,44,15}; 31 | BubbleSort bubbleSort=new BubbleSort(); 32 | bubbleSort.doBubbleSort(array); 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /sorting/src/main/java/com/flydean/BubbleSort1.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | /** 6 | * @author wayne 7 | * @version BubbleSort 8 | */ 9 | @Slf4j 10 | public class BubbleSort1 { 11 | 12 | public void doBubbleSort(int[] array){ 13 | log.info("排序前的数组为:{}",array); 14 | //外层循环,遍历所有轮数 15 | for(int i=0; i< array.length-1; i++){ 16 | //内层循环,两两比较,选中较大的数字,进行交换, 最后的i个数字已经排完序了,不需要再进行比较 17 | for(int j=0; jarray[j+1]){ 19 | //交换两个数字 20 | int temp = array[j]; 21 | array[j] = array[j+1]; 22 | array[j+1] = temp; 23 | } 24 | } 25 | log.info("第{}轮排序后的数组为:{}", i+1, array); 26 | } 27 | } 28 | 29 | public static void main(String[] args) { 30 | int[] array= {29,10,14,37,20,25,44,15}; 31 | BubbleSort1 bubbleSort=new BubbleSort1(); 32 | bubbleSort.doBubbleSort(array); 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /sorting/src/main/java/com/flydean/BubbleSort2.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | /** 6 | * @author wayne 7 | * @version BubbleSort 8 | */ 9 | @Slf4j 10 | public class BubbleSort2 { 11 | 12 | public void doBubbleSort(int[] array){ 13 | log.info("排序前的数组为:{}",array); 14 | //外层循环,遍历所有轮数 15 | for(int i=0; i< array.length-1; i++){ 16 | //添加一个flag,如果这一轮都没有排序,说明排序已经结束,可以提前退出 17 | boolean flag=false; 18 | //内层循环,两两比较,选中较大的数字,进行交换, 最后的i个数字已经排完序了,不需要再进行比较 19 | for(int j=0; jarray[j+1]){ 21 | //交换两个数字 22 | int temp = array[j]; 23 | array[j] = array[j+1]; 24 | array[j+1] = temp; 25 | flag = true; 26 | } 27 | } 28 | log.info("第{}轮排序后的数组为:{}", i+1, array); 29 | if(!flag) 30 | { 31 | log.info("本轮未发生排序变化,排序结束"); 32 | return; 33 | } 34 | } 35 | } 36 | 37 | public static void main(String[] args) { 38 | int[] array= {29,10,14,37,20,25,44,15}; 39 | BubbleSort2 bubbleSort=new BubbleSort2(); 40 | bubbleSort.doBubbleSort(array); 41 | } 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /sorting/src/main/java/com/flydean/CountingSort.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | /** 6 | * @author wayne 7 | * @version CountingSort 8 | */ 9 | @Slf4j 10 | public class CountingSort { 11 | 12 | public void doCountingSort(int[] array){ 13 | int n = array.length; 14 | 15 | // 存储排序过后的数组 16 | int output[] = new int[n]; 17 | 18 | // count数组,用来存储统计各个元素出现的次数 19 | int count[] = new int[10]; 20 | for (int i=0; i<10; ++i) { 21 | count[i] = 0; 22 | } 23 | log.info("初始化count值:{}",count); 24 | 25 | // 将原始数组中数据出现次数存入count数组 26 | for (int i=0; i 0){ 35 | output[j++]=i; 36 | } 37 | } 38 | log.info("构建output之后的output值:{}",output); 39 | 40 | //将排序后的数组写回原数组 41 | for (int i = 0; i=0; i--) 39 | { 40 | output[count[array[i]]-1] = array[i]; 41 | --count[array[i]]; 42 | } 43 | log.info("构建output之后的output值:{}",output); 44 | 45 | //将排序后的数组写回原数组 46 | for (int i = 0; i= 0 && array[j] > key) { 24 | array[j + 1] = array[j]; 25 | j = j - 1; 26 | } 27 | //最后的j+1的位置就是需要插入新元素的位置 28 | array[j + 1] = key; 29 | log.info("第{}轮排序后的数组为:{}", i+1, array); 30 | } 31 | 32 | } 33 | 34 | public static void main(String[] args) { 35 | int[] array= {29,10,14,37,20,25,44,15}; 36 | InsertionSort insertionSort=new InsertionSort(); 37 | insertionSort.doInsertSort(array); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sorting/src/main/java/com/flydean/MergeSort.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | /** 6 | * @author wayne 7 | * @version MergeSort, 2020/7/8 11:38 下午 8 | */ 9 | @Slf4j 10 | public class MergeSort { 11 | 12 | /** 13 | *合并两部分已排序好的数组 14 | * @param array 待合并的数组 15 | * @param low 数组第一部分的起点 16 | * @param mid 数组第一部分的终点,也是第二部分的起点-1 17 | * @param high 数组第二部分的终点 18 | */ 19 | private void merge(int[] array, int low, int mid, int high) { 20 | // 要排序的数组长度 21 | int length = high-low+1; 22 | // 我们需要一个额外的数组存储排序过后的结果 23 | int[] temp= new int[length]; 24 | //分成左右两个数组 25 | int left = low, right = mid+1, tempIdx = 0; 26 | //合并数组 27 | while (left <= mid && right <= high) { 28 | temp[tempIdx++] = (array[left] <= array[right]) ? array[left++] : array[right++]; 29 | } 30 | //一个数组合并完了,剩下的一个继续合并 31 | while (left <= mid) temp[tempIdx++] = array[left++]; 32 | while (right <= high) temp[tempIdx++] = array[right++]; 33 | //将排序过后的数组拷贝回原数组 34 | for (int k = 0; k < length; k++) array[low+k] = temp[k]; 35 | } 36 | 37 | public void doMergeSort(int[] array, int low, int high){ 38 | // 要排序的数组 array[low..high] 39 | //使用二分法进行递归,当low的值大于或者等于high的值的时候,就停止递归 40 | if (low < high) { 41 | //获取中间值的index 42 | int mid = (low+high) / 2; 43 | //递归前面一半 44 | doMergeSort(array, low , mid ); 45 | //递归后面一半 46 | doMergeSort(array, mid+1, high); 47 | //递归完毕,将排序过后的数组的两部分合并 48 | merge(array, low, mid, high); 49 | log.info("merge之后的数组:{}",array); 50 | } 51 | } 52 | 53 | public static void main(String[] args) { 54 | int[] array= {29,10,14,37,20,25,44,15}; 55 | MergeSort mergeSort=new MergeSort(); 56 | log.info("merge之前的数组为:{}",array); 57 | mergeSort.doMergeSort(array,0, array.length-1); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /sorting/src/main/java/com/flydean/QuickSort.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | /** 6 | * @author wayne 7 | * @version QuickSort 8 | */ 9 | @Slf4j 10 | public class QuickSort { 11 | 12 | /** 13 | * partition方法的主要作用就是以中心节点为界,将数组分为两部分, 14 | * 左边部分小于中心节点,右边部分大于中心节点 15 | * @param array 16 | * @param i 17 | * @param j 18 | * @return 19 | */ 20 | private int partition(int[] array, int i, int j) { 21 | //选择最左侧的元素作为中心点,middleValue就是中心点的值 22 | int middleValue = array[i]; 23 | int middleIndex = i; 24 | //从i+1遍历整个数组 25 | for (int k = i+1; k <= j; k++) { 26 | //如果数组元素小于middleValue,表示middleIndex需要右移一位 27 | //右移之后,我们需要将小于middleValue的array[k]移动到middleIndex的左边, 28 | // 最简单的办法就是交换k和middleIndex的值 29 | if (array[k] < middleValue) { 30 | middleIndex++; 31 | //交换数组的两个元素 32 | swap(array, k , middleIndex); 33 | } //如果数组元素大于等于middleValue,则继续向后遍历,middleIndex值不变 34 | } 35 | // 最后将中心点放入middleIndex位置 36 | swap(array, i, middleIndex); 37 | return middleIndex; 38 | } 39 | 40 | /** 41 | * 交互数组的两个元素 42 | * @param array 43 | * @param i 44 | * @param m 45 | */ 46 | private void swap(int[] array, int i, int m){ 47 | int temp = array[i]; 48 | array[i] = array[m]; 49 | array[m] = temp; 50 | } 51 | 52 | public void doQuickSort(int[] array, int low, int high) { 53 | //递归的结束条件 54 | if (low < high) { 55 | //找出中心节点的值 56 | int middleIndex = partition(array, low, high); 57 | //数组分成了三部分: 58 | // a[low..high] ~> a[low..m–1], pivot, a[m+1..high] 59 | //递归遍历左侧部分 60 | doQuickSort(array, low, middleIndex-1); 61 | // a[m] 是中心节点,已经排好序了,不需要继续遍历 62 | //递归遍历右侧部分 63 | doQuickSort(array, middleIndex+1, high); 64 | log.info("QuickSort之后的数组:{}",array); 65 | } 66 | } 67 | 68 | public static void main(String[] args) { 69 | int[] array= {29,10,14,37,20,25,44,15}; 70 | QuickSort quickSort=new QuickSort(); 71 | log.info("QuickSort之前的数组为:{}",array); 72 | quickSort.doQuickSort(array,0, array.length-1); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /sorting/src/main/java/com/flydean/RadixSort.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * @author wayne 9 | * @version RadixSort 10 | */ 11 | @Slf4j 12 | public class RadixSort { 13 | 14 | public void doRadixSort(int[] array, int digit){ 15 | int n = array.length; 16 | 17 | // 存储排序过后的数组 18 | int output[] = new int[n]; 19 | 20 | // count数组,用来存储统计各个元素出现的次数 21 | int count[] = new int[10]; 22 | Arrays.fill(count,0); 23 | log.info("初始化count值:{}",count); 24 | 25 | // 将原始数组中数据出现次数存入count数组 26 | for (int i=0; i=0; i--) 40 | { 41 | output[count[(array[i]/digit)%10]-1] = array[i]; 42 | count[(array[i]/digit)%10]--; 43 | } 44 | log.info("构建output之后的output值:{}",output); 45 | 46 | //将排序后的数组写回原数组 47 | for (int i = 0; i mx){ 56 | mx = array[i]; 57 | } 58 | return mx; 59 | } 60 | 61 | public static void main(String[] args) { 62 | int[] array= {1221, 15, 20, 3681, 277, 5420, 71, 1522, 4793}; 63 | RadixSort radixSort=new RadixSort(); 64 | log.info("radixSort之前的数组为:{}",array); 65 | //拿到数组的最大值,用于计算digit 66 | int max = radixSort.getMax(array); 67 | //根据位数,遍历进行count排序 68 | for (int digit = 1; max/digit > 0; digit *= 10){ 69 | radixSort.doRadixSort(array,digit); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /sorting/src/main/java/com/flydean/RandomQuickSort.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.Random; 6 | 7 | /** 8 | * @author wayne 9 | * @version RandomQuickSort 10 | */ 11 | @Slf4j 12 | public class RandomQuickSort { 13 | 14 | /** 15 | * partition方法的主要作用就是以中心节点为界,将数组分为两部分, 16 | * 左边部分小于中心节点,右边部分大于中心节点 17 | * @param array 18 | * @param i 19 | * @param j 20 | * @return 21 | */ 22 | private int partition(int[] array, int i, int j) { 23 | //随机选择一个元素作为中心点,middleValue就是中心点的值 24 | int randomIndex=i+new Random().nextInt(j-i); 25 | log.info("randomIndex:{}",randomIndex); 26 | //首先将randomIndex的值和i互换位置,就可以复用QuickSort的逻辑 27 | swap(array, i , randomIndex); 28 | int middleValue = array[i]; 29 | int middleIndex = i; 30 | //从i遍历整个数组 31 | for (int k = i+1; k <= j; k++) { 32 | //如果数组元素小于middleValue,表示middleIndex需要右移一位 33 | //右移之后,我们需要将小于middleValue的array[k]移动到middleIndex的左边, 34 | // 最简单的办法就是交换k和middleIndex的值 35 | if (array[k] < middleValue) { 36 | middleIndex++; 37 | //交换数组的两个元素 38 | swap(array, k , middleIndex); 39 | } //如果数组元素大于等于middleValue,则继续向后遍历,middleIndex值不变 40 | } 41 | // 最后将中心点放入middleIndex位置 42 | swap(array, i, middleIndex); 43 | return middleIndex; 44 | } 45 | 46 | /** 47 | * 交互数组的两个元素 48 | * @param array 49 | * @param i 50 | * @param m 51 | */ 52 | private void swap(int[] array, int i, int m){ 53 | int temp = array[i]; 54 | array[i] = array[m]; 55 | array[m] = temp; 56 | } 57 | 58 | public void doQuickSort(int[] array, int low, int high) { 59 | //递归的结束条件 60 | if (low < high) { 61 | //找出中心节点的值 62 | int middleIndex = partition(array, low, high); 63 | //数组分成了三部分: 64 | // a[low..high] ~> a[low..m–1], pivot, a[m+1..high] 65 | //递归遍历左侧部分 66 | doQuickSort(array, low, middleIndex-1); 67 | // a[m] 是中心节点,已经排好序了,不需要继续遍历 68 | //递归遍历右侧部分 69 | doQuickSort(array, middleIndex+1, high); 70 | log.info("QuickSort之后的数组:{}",array); 71 | } 72 | } 73 | 74 | public static void main(String[] args) { 75 | int[] array= {29,10,14,37,20,25,44,15}; 76 | RandomQuickSort quickSort=new RandomQuickSort(); 77 | log.info("QuickSort之前的数组为:{}",array); 78 | quickSort.doQuickSort(array,0, array.length-1); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /sorting/src/main/java/com/flydean/SelectionSort.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | /** 6 | * @author wayne 7 | * @version SelectionSort 8 | */ 9 | @Slf4j 10 | public class SelectionSort { 11 | 12 | public void doSelectionSort(int[] array){ 13 | log.info("排序前的数组为:{}",array); 14 | //外层循环,遍历所有轮数 15 | for(int i=0; i< array.length-1; i++){ 16 | //内层循环,找出最小的那个数字 17 | int minIndex=i; 18 | for(int j=i+1;j array[maxIndex]) 21 | { 22 | maxIndex = j; 23 | } 24 | } 25 | //每次选择完成后,将maxIndex所在元素和length-i-1的元素互换 26 | int temp = array[array.length-i-1]; 27 | array[array.length-i-1] = array[maxIndex]; 28 | array[maxIndex] = temp; 29 | log.info("第{}轮排序后的数组为:{}", i+1, array); 30 | } 31 | } 32 | 33 | public static void main(String[] args) { 34 | int[] array= {29,10,14,37,20,25,44,15}; 35 | SelectionSort1 selectionSort=new SelectionSort1(); 36 | selectionSort.doSelectionSort(array); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /stack/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | stack 13 | 14 | 15 | -------------------------------------------------------------------------------- /stack/src/main/java/com/flydean/ArrayStack.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version ArrayStack 6 | */ 7 | public class ArrayStack { 8 | 9 | //实际存储数据的数组 10 | private int[] array; 11 | //stack的容量 12 | private int capacity; 13 | //stack头部指针的位置 14 | private int topIndex; 15 | 16 | public ArrayStack(int capacity){ 17 | this.capacity= capacity; 18 | array = new int[capacity]; 19 | //默认情况下topIndex是-1,表示stack是空 20 | topIndex=-1; 21 | } 22 | 23 | /** 24 | * stack 是否为空 25 | * @return 26 | */ 27 | public boolean isEmpty(){ 28 | return topIndex == -1; 29 | } 30 | 31 | /** 32 | * stack 是否满了 33 | * @return 34 | */ 35 | public boolean isFull(){ 36 | return topIndex == array.length -1 ; 37 | } 38 | 39 | public void push(int data){ 40 | if(isFull()){ 41 | System.out.println("Stack已经满了,禁止插入"); 42 | }else{ 43 | array[++topIndex]=data; 44 | } 45 | } 46 | 47 | public int pop(){ 48 | if(isEmpty()){ 49 | System.out.println("Stack是空的"); 50 | return -1; 51 | }else{ 52 | return array[topIndex--]; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /stack/src/main/java/com/flydean/DyncArrayStack.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version DyncArrayStack 6 | */ 7 | public class DyncArrayStack { 8 | 9 | //实际存储数据的数组 10 | private int[] array; 11 | //stack的容量 12 | private int capacity; 13 | //stack头部指针的位置 14 | private int topIndex; 15 | 16 | public DyncArrayStack(int capacity){ 17 | this.capacity= capacity; 18 | array = new int[capacity]; 19 | //默认情况下topIndex是-1,表示stack是空 20 | topIndex=-1; 21 | } 22 | 23 | /** 24 | * stack 是否为空 25 | * @return 26 | */ 27 | public boolean isEmpty(){ 28 | return topIndex == -1; 29 | } 30 | 31 | /** 32 | * stack 是否满了 33 | * @return 34 | */ 35 | public boolean isFull(){ 36 | return topIndex == array.length -1 ; 37 | } 38 | 39 | public void push(int data){ 40 | if(isFull()){ 41 | System.out.println("Stack已经满了,stack扩容"); 42 | expandStack(); 43 | } 44 | array[++topIndex]=data; 45 | } 46 | 47 | public int pop(){ 48 | if(isEmpty()){ 49 | System.out.println("Stack是空的"); 50 | return -1; 51 | }else{ 52 | return array[topIndex--]; 53 | } 54 | } 55 | 56 | //扩容stack,这里我们简单的使用倍增方式 57 | private void expandStack(){ 58 | int[] expandedArray = new int[capacity* 2]; 59 | System.arraycopy(array,0, expandedArray,0, capacity); 60 | capacity= capacity*2; 61 | array= expandedArray; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /stack/src/main/java/com/flydean/LinkedListStack.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version LinkedListStack, 2020/7/12 9:08 下午 6 | */ 7 | public class LinkedListStack { 8 | 9 | private Node headNode; 10 | 11 | class Node { 12 | int data; 13 | Node next; 14 | //Node的构造函数 15 | Node(int d) { 16 | data = d; 17 | } 18 | } 19 | 20 | public void push(int data){ 21 | if(headNode == null){ 22 | headNode= new Node(data); 23 | }else{ 24 | Node newNode= new Node(data); 25 | newNode.next= headNode; 26 | headNode= newNode; 27 | } 28 | } 29 | 30 | public int top(){ 31 | if(headNode ==null){ 32 | return -1; 33 | }else{ 34 | return headNode.data; 35 | } 36 | } 37 | 38 | public int pop(){ 39 | if(headNode ==null){ 40 | System.out.println("Stack是空的"); 41 | return -1; 42 | }else{ 43 | int data= headNode.data; 44 | headNode= headNode.next; 45 | return data; 46 | } 47 | } 48 | 49 | public boolean isEmpty(){ 50 | return headNode==null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /stringMatch/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /stringMatch/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.1.5 9 | 10 | 11 | com.flydean 12 | stringMatch 13 | 0.0.1-SNAPSHOT 14 | stringMatch 15 | stringMatch 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | test 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-maven-plugin 37 | 38 | 39 | paketobuildpacks/builder-jammy-base:latest 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /stringMatch/src/main/java/com/flydean/stringmatch/KMP.java: -------------------------------------------------------------------------------- 1 | package com.flydean.stringmatch; 2 | 3 | /** 4 | * Knuth-Morris-Pratt(KMP)算法是一种用于在一个文本串中查找一个模式串出现位置的字符串匹配算法。它的特点在于避免了对于每个位置的匹配都回溯到模式串的开头,从而减少了比较的次数,提高了匹配效率。 5 | *

6 | * ### KMP算法的基本思想: 7 | * 1. **预处理模式串:** KMP算法首先对模式串进行预处理,得到一个部分匹配表(Partial Match Table),记为`next[]`。这个表用于指导匹配过程中的跳跃。 8 | *

9 | * 2. **匹配过程:** 在匹配过程中,当发生不匹配时,根据部分匹配表中的信息,调整模式串的位置,使得不必回溯到模式串的开头,从而提高匹配效率。 10 | *

11 | * ### 部分匹配表 `next[]` 的构建: 12 | * - `next[i]` 表示当第 `i` 个字符不匹配时,模式串应该跳跃的位置。 13 | * - 对于模式串 `p`,`next[0] = -1`,`next[1] = 0`。 14 | * - 对于 `i > 1`,若 `p[0...k-1]` 是 `p[0...i-1]` 的最大相同前缀后缀,令 `next[i] = k`,否则令 `k = next[k]`。 15 | *

16 | * ### KMP算法步骤: 17 | * 1. 初始化文本串指针 `i` 和模式串指针 `j`。 18 | * 2. 若当前字符匹配,则 `i` 和 `j` 同时后移。 19 | * 3. 若当前字符不匹配,根据 `next[j]` 调整 `j` 的位置。 20 | * 4. 重复步骤2-3,直到找到匹配或文本串遍历完。 21 | *

22 | * ### 算法复杂度: 23 | * - KMP算法的时间复杂度是 O(n + m),其中 n 是文本串的长度,m 是模式串的长度。 24 | * - 部分匹配表的构建时间是 O(m)。 25 | *

26 | * KMP算法在大规模文本匹配中具有较高的效率,尤其在一些大数据处理场景下表现优越。 27 | */ 28 | 29 | import java.util.Arrays; 30 | 31 | public class KMP { 32 | public static void main(String[] args) { 33 | String str1 = "BBC ABCDAB ABCDABCDABDE"; 34 | String str2 = "ABCDAB"; 35 | int[] next = KMP_next(str2); 36 | System.out.println("next=" + Arrays.toString(next)); 37 | int index = KmpSearch(str1, str2); 38 | System.out.println(index); 39 | } 40 | 41 | //KMP搜索算法 42 | public static int KmpSearch(String str1, String str2) { 43 | int[] next = KMP_next(str2); 44 | //遍历 45 | for (int i = 0, j = 0; i < str1.length(); i++) { 46 | while (j > 0 && str1.charAt(i) != str2.charAt(j)) { 47 | j = next[j - 1]; 48 | } 49 | if (str1.charAt(i) == str2.charAt(j)) { 50 | j++; 51 | } 52 | if (j == str2.length()) { 53 | return i - j + 1; 54 | } 55 | } 56 | return -1; 57 | } 58 | 59 | //获取到一个字符串的部分匹配值 60 | public static int[] KMP_next(String dest) { 61 | //创建一个数组next,保存部分匹配值 62 | int[] next = new int[dest.length()]; 63 | next[0] = 0;//如果字符串是长度为1 部分匹配值就是0 64 | for (int i = 1, j = 0; i < dest.length(); i++) { 65 | //当dest.charAt(j) != dest.charAt(i),我们需要从next[j-1]获取新的j 66 | //知道我们发现有dest.charAt(j) == dest.charAt(i)成立才停止 67 | while (j > 0 && dest.charAt(j) != dest.charAt(i)) { 68 | j = next[j - 1]; 69 | } 70 | //当dest.charAt(j) == dest.charAt(i)满足时,部分匹配值就是+1 71 | if (dest.charAt(j) == dest.charAt(i)) { 72 | j++; 73 | } 74 | next[i] = j; 75 | } 76 | return next; 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /stringMatch/src/main/java/com/flydean/stringmatch/KMPTest.java: -------------------------------------------------------------------------------- 1 | package com.flydean.stringmatch; 2 | 3 | import java.util.Arrays; 4 | 5 | public class KMPTest { 6 | 7 | public static void main(String[] args) { 8 | String str1 = "BBC ABCDAB ABCDABCDABDE"; 9 | String str2 = "ABCDAB"; 10 | int[] next = getNext(str2); 11 | System.out.println("next=" + Arrays.toString(next)); 12 | int index = KmpSearch(str1, str2); 13 | System.out.println(index); 14 | } 15 | 16 | 17 | public static int KmpSearch(String str1, String str2) { 18 | int[] next = getNext(str2); 19 | for (int i = 0, j = 0; i < str1.length(); i++) { 20 | while (j > 0 && str1.charAt(i) != str2.charAt(j)) { 21 | j = next[j - 1]; 22 | } 23 | if (str1.charAt(i) == str2.charAt(j)) { 24 | j++; 25 | } 26 | if (j == str2.length()) { 27 | return i - j + 1; 28 | } 29 | } 30 | return -1; 31 | } 32 | 33 | public static int[] getNext(String dest) { 34 | 35 | int[] next = new int[dest.length()]; 36 | next[0] = 0; 37 | for (int i = 1, j = 0; i < dest.length(); i++) { 38 | while (j > 0 && dest.charAt(i) != dest.charAt(j)) { 39 | j = next[j - 1]; 40 | } 41 | if (dest.charAt(i) == dest.charAt(j)) { 42 | j++; 43 | } 44 | next[i] = j; 45 | } 46 | return next; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /stringMatch/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tree/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | tree 13 | 14 | 15 | -------------------------------------------------------------------------------- /tree/src/main/java/com/flydean/BinarySearchTree.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version BinarySearchTree, 2020/7/20 6 | */ 7 | public class BinarySearchTree { 8 | 9 | //根节点 10 | Node root; 11 | 12 | class Node { 13 | int data; 14 | Node left; 15 | Node right; 16 | 17 | public Node(int data) { 18 | this.data = data; 19 | left = right = null; 20 | } 21 | } 22 | 23 | //搜索方法,默认从根节点搜索 24 | public Node search(int data){ 25 | return search(root,data); 26 | } 27 | 28 | //递归搜索节点 29 | private Node search(Node node, int data) 30 | { 31 | // 如果节点匹配,则返回节点 32 | if (node==null || node.data==data) 33 | return node; 34 | 35 | // 节点数据大于要搜索的数据,则继续搜索左边节点 36 | if (node.data > data) 37 | return search(node.left, data); 38 | 39 | // 如果节点数据小于要搜素的数据,则继续搜索右边节点 40 | return search(node.right, data); 41 | } 42 | 43 | // 插入新节点,从根节点开始插入 44 | public void insert(int data) { 45 | root = insert(root, data); 46 | } 47 | 48 | //递归插入新节点 49 | private Node insert(Node node, int data) { 50 | 51 | //如果节点为空,则创建新的节点 52 | if (node == null) { 53 | node = new Node(data); 54 | return node; 55 | } 56 | 57 | //节点不为空,则进行比较,从而递归进行左侧插入或者右侧插入 58 | if (data < node.data) 59 | node.left = insert(node.left, data); 60 | else if (data > node.data) 61 | node.right = insert(node.right, data); 62 | 63 | //返回插入后的节点 64 | return node; 65 | } 66 | 67 | // 删除新节点,从根节点开始删除 68 | void delete(int data) 69 | { 70 | root = delete(root, data); 71 | } 72 | 73 | //递归删除节点 74 | Node delete(Node node, int data) 75 | { 76 | //如果节点为空,直接返回 77 | if (node == null) return node; 78 | 79 | //遍历左右两边的节点 80 | if (data < node.data) 81 | node.left = delete(node.left, data); 82 | else if (data > root.data) 83 | node.right = delete(node.right, data); 84 | 85 | //如果节点匹配 86 | else 87 | { 88 | //如果是单边节点,直接返回其下面的节点 89 | if (node.left == null) 90 | return node.right; 91 | else if (node.right == null) 92 | return node.left; 93 | 94 | //如果是双边节点,则先找出右边最小的值,作为根节点,然后将删除最小值过后的右边的节点,作为根节点的右节点 95 | node.data = minValue(node.right); 96 | 97 | // 从右边删除最小的节点 98 | node.right = delete(node.right, node.data); 99 | } 100 | 101 | return node; 102 | } 103 | 104 | //查找节点的最小值 105 | int minValue(Node node) 106 | { 107 | int minv = node.data; 108 | while (node.left != null) 109 | { 110 | minv = node.left.data; 111 | node = node.left; 112 | } 113 | return minv; 114 | } 115 | 116 | //中序遍历BST 117 | public void inOrder(){ 118 | inOrder(root); 119 | } 120 | 121 | //递归中序遍历 122 | private void inOrder(Node node){ 123 | if (node != null) { 124 | inOrder(node.left); 125 | System.out.println(node.data); 126 | inOrder(node.right); 127 | } 128 | } 129 | 130 | public static void main(String[] args) { 131 | BinarySearchTree binarySearchTree= new BinarySearchTree(); 132 | binarySearchTree.insert(34); 133 | binarySearchTree.insert(21); 134 | binarySearchTree.insert(15); 135 | binarySearchTree.insert(44); 136 | binarySearchTree.insert(43); 137 | binarySearchTree.insert(37); 138 | binarySearchTree.insert(12); 139 | binarySearchTree.inOrder(); 140 | binarySearchTree.delete(34); 141 | binarySearchTree.inOrder(); 142 | } 143 | 144 | 145 | 146 | 147 | } 148 | -------------------------------------------------------------------------------- /tree/src/main/java/com/flydean/FenwickTree.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * FenwickTree也叫做BIT(Binary Indexed Tree),树状数组 7 | * 是一种用于高效处理对一个存储数字的列表进行更新及求前缀和的数据结构。 8 | * @author wayne 9 | * @version FenwickTree, 2020/8/22 10 | */ 11 | public class FenwickTree { 12 | 13 | private ArrayList ft; 14 | 15 | private int lowBitOne(int S) { return (S & (-S)); } 16 | 17 | public FenwickTree() {} 18 | 19 | // 初始化数组,值为0 20 | public FenwickTree(int n) { 21 | ft = new ArrayList<>(); 22 | for (int i = 0; i <= n; i++) ft.add(0); 23 | } 24 | 25 | public int rangeSumQuery(int j) { // 范围查询 1 - j 26 | int sum = 0; for (; j > 0; j -= lowBitOne(j)) { 27 | sum += ft.get(j); 28 | } 29 | return sum; 30 | } 31 | 32 | public int rangeSumQuery(int i, int j) { // 范围查询 i - j 33 | return rangeSumQuery(j) - rangeSumQuery(i-1); 34 | } 35 | 36 | // 构造FenwickTree,更新相应的值 37 | void update(int i, int v) { 38 | for (; i < ft.size(); i += lowBitOne(i)){ 39 | ft.set(i, ft.get(i)+v); 40 | } 41 | } 42 | 43 | public static void main(String[] args) { 44 | // idx 0 1 2 3 4 5 6 7 8 9 10, no index 0! 45 | FenwickTree ft = new FenwickTree(10); // ft = {-,0,0,0,0,0,0,0, 0,0,0} 46 | ft.update(2, 1); // ft = {-,0,1,0,1,0,0,0, 1,0,0}, idx 2,4,8 => +1 47 | ft.update(4, 1); // ft = {-,0,1,0,2,0,0,0, 2,0,0}, idx 4,8 => +1 48 | ft.update(5, 2); // ft = {-,0,1,0,2,2,2,0, 4,0,0}, idx 5,6,8 => +2 49 | ft.update(6, 3); // ft = {-,0,1,0,2,2,5,0, 7,0,0}, idx 6,8 => +3 50 | ft.update(7, 2); // ft = {-,0,1,0,2,2,5,2, 9,0,0}, idx 7,8 => +2 51 | ft.update(8, 1); // ft = {-,0,1,0,2,2,5,2,10,0,0}, idx 8 => +1 52 | ft.update(9, 1); // ft = {-,0,1,0,2,2,5,2,10,1,1}, idx 9,10 => +1 53 | System.out.printf("%d\n", ft.rangeSumQuery(1, 1)); // 0 => ft[1] = 0 54 | System.out.printf("%d\n", ft.rangeSumQuery(1, 2)); // 1 => ft[2] = 1 55 | System.out.printf("%d\n", ft.rangeSumQuery(1, 6)); // 7 => ft[6] + ft[4] = 5 + 2 = 7 56 | System.out.printf("%d\n", ft.rangeSumQuery(1, 10)); // 11 => ft[10] + ft[8] = 1 + 10 = 11 57 | System.out.printf("%d\n", ft.rangeSumQuery(3, 6)); // 6 => rsq(1, 6) - rsq(1, 2) = 7 - 1 58 | 59 | ft.update(5, 2); // update demo 60 | System.out.printf("%d\n", ft.rangeSumQuery(1, 10)); // now 13 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /tree/src/main/java/com/flydean/MaxSegmentTree.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version MaxSegmentTree, 2020/8/18 6 | * 最大线段树,非叶子节点存储的是范围内的最大值 7 | */ 8 | public class MaxSegmentTree { 9 | 10 | private int[] segmentTree; //新构建的segmentTree,对于满二叉树 最后一层的节点数乘以2 大致就是整棵树的节点数。 11 | //但是线段树并不一定是满二叉树,但是一定是平衡二叉树,所以需要多冗余一层。也就是 乘以4 就足以盛放所有的节点数 12 | //新构建的segmentTree 以index=1为起点 13 | private int[] originalArray; //原始数组 14 | private int n; //原始数组的长度 15 | private int left (int p) { return p << 1; } //左子结点为2*p 16 | private int right(int p) { return (p << 1) + 1; } //右子结点为2*p + 1 17 | 18 | /** 19 | * 构建segmentTree 20 | * @param treeIndex 当前需要添加节点的索引 21 | * @param arrayLeft 数组的左边界 22 | * @param arrayRight 数组的右边界 23 | */ 24 | private void build(int treeIndex, int arrayLeft, int arrayRight) { 25 | if (arrayLeft == arrayRight) //如果相等则随便选择一个赋值 26 | segmentTree[treeIndex] = originalArray[arrayLeft]; 27 | else { // 否则分别构建左侧子树和右侧子树,并根据我们需要构建的segmentTree类型来设置当前节点的值 28 | build(left(treeIndex) , arrayLeft , (arrayLeft + arrayRight) / 2); 29 | build(right(treeIndex), (arrayLeft + arrayRight) / 2 + 1, arrayRight); 30 | int p1 = segmentTree[left(treeIndex)], p2 = segmentTree[right(treeIndex)]; 31 | segmentTree[treeIndex] = (p1 >= p2) ? p1 : p2; 32 | } } 33 | 34 | 35 | /** 36 | * 范围查询 37 | * @param treeIndex 当前要查找的节点index 38 | * @param arrayLeft 数组左边界 39 | * @param arrayRight 数组右边界 40 | * @param searchLeft 搜索左边界 41 | * @param searchRight 搜索右边界 42 | * @return 43 | */ 44 | private int rangeQuery(int treeIndex, int arrayLeft, int arrayRight, int searchLeft, int searchRight) { 45 | if (searchLeft > arrayRight || searchRight < arrayLeft) return -1; // 搜索超出数组范围 46 | if (arrayLeft >= searchLeft && arrayRight <= searchRight) return segmentTree[treeIndex]; // 搜索的是整个数组范围,则直接返回根元素 47 | 48 | // 否则左右搜索 49 | int p1 = rangeQuery(left(treeIndex) , arrayLeft, (arrayLeft+arrayRight) / 2, searchLeft, searchRight); 50 | int p2 = rangeQuery(right(treeIndex), (arrayLeft+arrayRight) / 2 + 1, arrayRight, searchLeft, searchRight); 51 | 52 | if (p1 == -1) return p2; // 如果超出范围,则返回另外一个 53 | if (p2 == -1) return p1; 54 | return (p1 >= p2) ? p1 : p2; } //返回最小的那个 55 | 56 | 57 | /** 58 | * 更新数组中的某个节点 59 | * @param treeIndex 树的index 60 | * @param arrayLeft 数组左边界 61 | * @param arrayRight 数组右边界 62 | * @param arrayIndex 要更新的数组index 63 | * @param newValue 要更新的值 64 | * @return 65 | */ 66 | private int updatePoint(int treeIndex, int arrayLeft, int arrayRight, int arrayIndex, int newValue) { 67 | // 设置i 和 j 等于要更新的数组index 68 | int i = arrayIndex, j = arrayIndex; 69 | 70 | // arrayIndex超出范围,则直接返回 71 | if (i > arrayRight || j < arrayLeft) 72 | return segmentTree[treeIndex]; 73 | 74 | // 左右两个index相等 75 | if (arrayLeft == i && arrayRight == j) { 76 | originalArray[i] = newValue; // 找到要更新的index 77 | return segmentTree[treeIndex] = originalArray[i]; // 更新segmentTree 78 | } 79 | 80 | // 分别获得左右子树的最小值 81 | int p1, p2; 82 | p1 = updatePoint(left(treeIndex) , arrayLeft , (arrayLeft + arrayRight) / 2, arrayIndex, newValue); 83 | p2 = updatePoint(right(treeIndex), (arrayLeft + arrayRight) / 2 + 1, arrayRight , arrayIndex, newValue); 84 | 85 | // 更新treeIndex的值 86 | return segmentTree[treeIndex] = (p1 >= p2) ? p1 : p2; 87 | } 88 | 89 | public MaxSegmentTree(int[] array) { 90 | originalArray = array; n = originalArray.length; // 拷贝原始数组 91 | segmentTree = new int[4 * n]; //初始化新数组,长度是4*n 92 | for (int i = 0; i < 4 * n; i++) segmentTree[i] = 0; 93 | build(1, 0, n - 1); // 递归构建segmentTree,以index=1为起点 94 | } 95 | 96 | public int rangeQuery(int i, int j) { return rangeQuery(1, 0, n - 1, i, j); } // overloading 97 | 98 | public int updatePoint(int idx, int newValue) { 99 | return updatePoint(1, 0, n - 1, idx, newValue); 100 | } 101 | 102 | public static void main(String[] args) { 103 | int[] A = new int[] { 18, 17, 13, 19, 15, 11, 20 }; // the original array 104 | MaxSegmentTree st = new MaxSegmentTree(A); 105 | 106 | System.out.printf(" idx 0, 1, 2, 3, 4, 5, 6\n"); 107 | System.out.printf(" A is {18,17,13,19,15, 11,20}\n"); 108 | System.out.printf("RMQ(1, 3) = %d\n", st.rangeQuery(1, 3)); // answer = 13 109 | 110 | System.out.printf(" idx 0, 1, 2, 3, 4, 5, 6\n"); 111 | System.out.printf("Now, modify A into {18,17,13,19,15,100,20}\n"); 112 | st.updatePoint(5, 100); // update A[5] from 11 to 100 113 | System.out.printf("These values do not change\n"); 114 | System.out.printf("RMQ(1, 3) = %d\n", st.rangeQuery(1, 3)); // 13 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tree/src/main/java/com/flydean/MinSegmentTree.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version MinSegmentTree, 2020/8/18 6 | * 最小线段树,非叶子节点存储的是范围内的最小值 7 | */ 8 | public class MinSegmentTree { 9 | 10 | private int[] segmentTree; //新构建的segmentTree,对于满二叉树 最后一层的节点数乘以2 大致就是整棵树的节点数。 11 | //但是线段树并不一定是满二叉树,但是一定是平衡二叉树,所以需要多冗余一层。也就是 乘以4 就足以盛放所有的节点数 12 | //新构建的segmentTree 以index=1为起点 13 | private int[] originalArray; //原始数组 14 | private int n; //原始数组的长度 15 | private int left (int p) { return p << 1; } //左子结点为2*p 16 | private int right(int p) { return (p << 1) + 1; } //右子结点为2*p + 1 17 | 18 | /** 19 | * 构建segmentTree 20 | * @param treeIndex 当前需要添加节点的索引 21 | * @param arrayLeft 数组的左边界 22 | * @param arrayRight 数组的右边界 23 | */ 24 | private void build(int treeIndex, int arrayLeft, int arrayRight) { 25 | if (arrayLeft == arrayRight) //如果相等则随便选择一个赋值 26 | segmentTree[treeIndex] = originalArray[arrayLeft]; 27 | else { // 否则分别构建左侧子树和右侧子树,并根据我们需要构建的segmentTree类型来设置当前节点的值 28 | build(left(treeIndex) , arrayLeft , (arrayLeft + arrayRight) / 2); 29 | build(right(treeIndex), (arrayLeft + arrayRight) / 2 + 1, arrayRight); 30 | int p1 = segmentTree[left(treeIndex)], p2 = segmentTree[right(treeIndex)]; 31 | segmentTree[treeIndex] = (p1 <= p2) ? p1 : p2; 32 | } } 33 | 34 | 35 | /** 36 | * 范围查询 37 | * @param treeIndex 当前要查找的节点index 38 | * @param arrayLeft 数组左边界 39 | * @param arrayRight 数组右边界 40 | * @param searchLeft 搜索左边界 41 | * @param searchRight 搜索右边界 42 | * @return 43 | */ 44 | private int rangeQuery(int treeIndex, int arrayLeft, int arrayRight, int searchLeft, int searchRight) { 45 | if (searchLeft > arrayRight || searchRight < arrayLeft) return -1; // 搜索超出数组范围 46 | if (arrayLeft >= searchLeft && arrayRight <= searchRight) return segmentTree[treeIndex]; // 搜索的是整个数组范围,则直接返回根元素 47 | 48 | // 否则左右搜索 49 | int p1 = rangeQuery(left(treeIndex) , arrayLeft, (arrayLeft+arrayRight) / 2, searchLeft, searchRight); 50 | int p2 = rangeQuery(right(treeIndex), (arrayLeft+arrayRight) / 2 + 1, arrayRight, searchLeft, searchRight); 51 | 52 | if (p1 == -1) return p2; // 如果超出范围,则返回另外一个 53 | if (p2 == -1) return p1; 54 | return (p1 <= p2) ? p1 : p2; } //返回最小的那个 55 | 56 | 57 | /** 58 | * 更新数组中的某个节点 59 | * @param treeIndex 树的index 60 | * @param arrayLeft 数组左边界 61 | * @param arrayRight 数组右边界 62 | * @param arrayIndex 要更新的数组index 63 | * @param newValue 要更新的值 64 | * @return 65 | */ 66 | private int updatePoint(int treeIndex, int arrayLeft, int arrayRight, int arrayIndex, int newValue) { 67 | // 设置i 和 j 等于要更新的数组index 68 | int i = arrayIndex, j = arrayIndex; 69 | 70 | // arrayIndex超出范围,则直接返回 71 | if (i > arrayRight || j < arrayLeft) 72 | return segmentTree[treeIndex]; 73 | 74 | // 左右两个index相等 75 | if (arrayLeft == i && arrayRight == j) { 76 | originalArray[i] = newValue; // 找到要更新的index 77 | return segmentTree[treeIndex] = originalArray[i]; // 更新segmentTree 78 | } 79 | 80 | // 分别获得左右子树的最小值 81 | int p1, p2; 82 | p1 = updatePoint(left(treeIndex) , arrayLeft , (arrayLeft + arrayRight) / 2, arrayIndex, newValue); 83 | p2 = updatePoint(right(treeIndex), (arrayLeft + arrayRight) / 2 + 1, arrayRight , arrayIndex, newValue); 84 | 85 | // 更新treeIndex的值 86 | return segmentTree[treeIndex] = (p1 <= p2) ? p1 : p2; 87 | } 88 | 89 | public MinSegmentTree(int[] array) { 90 | originalArray = array; n = originalArray.length; // 拷贝原始数组 91 | segmentTree = new int[4 * n]; //初始化新数组,长度是4*n 92 | for (int i = 0; i < 4 * n; i++) segmentTree[i] = 0; 93 | build(1, 0, n - 1); // 递归构建segmentTree,以index=1为起点 94 | } 95 | 96 | public int rangeQuery(int i, int j) { return rangeQuery(1, 0, n - 1, i, j); } // overloading 97 | 98 | public int updatePoint(int idx, int newValue) { 99 | return updatePoint(1, 0, n - 1, idx, newValue); 100 | } 101 | 102 | public static void main(String[] args) { 103 | int[] A = new int[] { 18, 17, 13, 19, 15, 11, 20 }; // the original array 104 | MinSegmentTree st = new MinSegmentTree(A); 105 | 106 | System.out.printf(" idx 0, 1, 2, 3, 4, 5, 6\n"); 107 | System.out.printf(" A is {18,17,13,19,15, 11,20}\n"); 108 | System.out.printf("RMQ(1, 3) = %d\n", st.rangeQuery(1, 3)); // answer = 13 109 | 110 | System.out.printf(" idx 0, 1, 2, 3, 4, 5, 6\n"); 111 | System.out.printf("Now, modify A into {18,17,13,19,15,100,20}\n"); 112 | st.updatePoint(5, 100); // update A[5] from 11 to 100 113 | System.out.printf("These values do not change\n"); 114 | System.out.printf("RMQ(1, 3) = %d\n", st.rangeQuery(1, 3)); // 13 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tree/src/main/java/com/flydean/SuffixTree.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author wayne 7 | * @version SuffixTree, 2020/11/7 8 | */ 9 | public class SuffixTree { 10 | 11 | SuffixTrieNode root = new SuffixTrieNode(); 12 | 13 | // Constructor (Builds a trie of suffies of the 14 | // given text) 15 | SuffixTree(String txt) { 16 | 17 | // Consider all suffixes of given string and 18 | // insert them into the Suffix Trie using 19 | // recursive function insertSuffix() in 20 | // SuffixTrieNode class 21 | for (int i = 0; i < txt.length(); i++) 22 | root.insertSuffix(txt.substring(i), i); 23 | } 24 | 25 | /* Prints all occurrences of pat in the Suffix Trie S 26 | (built for text) */ 27 | void searchTree(String pat) { 28 | 29 | // Let us call recursive search function for 30 | // root of Trie. 31 | // We get a list of all indexes (where pat is 32 | // present in text) in variable 'result' 33 | List result = root.search(pat); 34 | 35 | // Check if the list of indexes is empty or not 36 | if (result == null) 37 | System.out.println("Pattern not found"); 38 | else { 39 | 40 | int patLen = pat.length(); 41 | 42 | for (Integer i : result) 43 | System.out.println("Pattern found at position " + 44 | (i - patLen)); 45 | } 46 | } 47 | 48 | // driver program to test above functions 49 | public static void main(String args[]) { 50 | 51 | // Let us build a suffix trie for text 52 | String txt = "www.flydean.com"; 53 | SuffixTree S = new SuffixTree(txt); 54 | 55 | System.out.println("Search for 'ww'"); 56 | S.searchTree("ww"); 57 | 58 | System.out.println("\nSearch for 'flydean'"); 59 | S.searchTree("flydean"); 60 | 61 | System.out.println("\nSearch for 'ea'"); 62 | S.searchTree("ea"); 63 | 64 | System.out.println("\nSearch for 'com'"); 65 | S.searchTree("com"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tree/src/main/java/com/flydean/SuffixTrieNode.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | /** 7 | * @author wayne 8 | * @version SuffixTrieNode, 2020/11/7 9 | */ 10 | public class SuffixTrieNode { 11 | 12 | final static int MAX_CHAR = 256; 13 | 14 | SuffixTrieNode[] children = new SuffixTrieNode[MAX_CHAR]; 15 | List indexes; 16 | 17 | SuffixTrieNode() // Constructor 18 | { 19 | // Create an empty linked list for indexes of 20 | // suffixes starting from this node 21 | indexes = new LinkedList(); 22 | 23 | // Initialize all child pointers as NULL 24 | for (int i = 0; i < MAX_CHAR; i++) 25 | children[i] = null; 26 | } 27 | 28 | // A recursive function to insert a suffix of 29 | // the text in subtree rooted with this node 30 | void insertSuffix(String s, int index) { 31 | 32 | // Store index in linked list 33 | indexes.add(index); 34 | 35 | // If string has more characters 36 | if (s.length() > 0) { 37 | 38 | // Find the first character 39 | char cIndex = s.charAt(0); 40 | 41 | // If there is no edge for this character, 42 | // add a new edge 43 | if (children[cIndex] == null) 44 | children[cIndex] = new SuffixTrieNode(); 45 | 46 | // Recur for next suffix 47 | children[cIndex].insertSuffix(s.substring(1), 48 | index + 1); 49 | } 50 | } 51 | 52 | // A function to search a pattern in subtree rooted 53 | // with this node.The function returns pointer to a 54 | // linked list containing all indexes where pattern 55 | // is present. The returned indexes are indexes of 56 | // last characters of matched text. 57 | List search(String s) { 58 | 59 | // If all characters of pattern have been 60 | // processed, 61 | if (s.length() == 0) 62 | return indexes; 63 | 64 | // if there is an edge from the current node of 65 | // suffix tree, follow the edge. 66 | if (children[s.charAt(0)] != null) 67 | return (children[s.charAt(0)]).search(s.substring(1)); 68 | 69 | // If there is no edge, pattern doesnt exist in 70 | // text 71 | else 72 | return null; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tree/src/main/java/com/flydean/TernaryTree.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | import java.util.HashSet; 4 | 5 | /** 6 | * @author wayne 7 | * @version TernaryTree, 2020/11/6 8 | */ 9 | public class TernaryTree { 10 | 11 | enum NodeType 12 | { 13 | COMPLETED, 14 | UNCOMPLETED 15 | } 16 | 17 | static class Node 18 | { 19 | public char word; 20 | 21 | public Node leftChild, centerChild, rightChild; 22 | 23 | public NodeType type; 24 | 25 | public Node(char ch, NodeType type) 26 | { 27 | word = ch; 28 | this.type = type; 29 | } 30 | } 31 | 32 | private Node _root; 33 | 34 | private HashSet _hashSet; 35 | 36 | 37 | /** 38 | * 向node插入 s 中的 index 位的字符 39 | * @param s 整个单词 40 | * @param index 单词中的制定字符位 41 | * @param node 要被插入到的树的节点 42 | */ 43 | private void insert(String s, int index, Node node) 44 | { 45 | if (null == node) 46 | { 47 | node = new Node(s.charAt(index), NodeType.UNCOMPLETED); 48 | } 49 | 50 | if (s.charAt(index) < node.word) 51 | { 52 | this.insert(s, index, node.leftChild); 53 | } 54 | else if (s.charAt(index) > node.word) 55 | { 56 | this.insert(s, index, node.rightChild); 57 | } 58 | else 59 | { 60 | if (index + 1 == s.length()) 61 | { 62 | node.type = NodeType.COMPLETED; 63 | } 64 | else 65 | { 66 | this.insert(s, index + 1, node.centerChild); 67 | } 68 | } 69 | } 70 | 71 | /** 72 | * 将单词 s 插入到树中 73 | * @param s 74 | */ 75 | public void insert(String s) 76 | { 77 | if (s == null || s.length() == 0 ) 78 | { 79 | return ; 80 | } 81 | 82 | insert(s, 0, _root); 83 | } 84 | 85 | /** 86 | * 查找特定的单词 87 | * @param s 待查找的单词 88 | * @return 89 | */ 90 | public Node find(String s) 91 | { 92 | if (s == null || s.length() == 0 ) 93 | { 94 | return null; 95 | } 96 | 97 | int pos = 0; 98 | Node node = _root; 99 | _hashSet = new HashSet(); 100 | while (node != null) 101 | { 102 | if (s.charAt(pos) < node.word) 103 | { 104 | node = node.leftChild; 105 | } 106 | else if (s.charAt(pos) > node.word) 107 | { 108 | node = node.rightChild; 109 | } 110 | else 111 | { 112 | if (++pos == s.length()) 113 | { 114 | _hashSet.add(s); 115 | return node.centerChild; 116 | } 117 | 118 | node = node.centerChild; 119 | } 120 | } 121 | 122 | return null; 123 | } 124 | 125 | /** 126 | * 前缀匹配 127 | * @param prefix 128 | * @param node 129 | */ 130 | private void DFS(String prefix, Node node) 131 | { 132 | if (node != null) 133 | { 134 | if (NodeType.COMPLETED == node.type) 135 | { 136 | _hashSet.add(prefix + node.word); 137 | } 138 | 139 | DFS(prefix, node.leftChild); 140 | DFS(prefix + node.word, node.centerChild); 141 | DFS(prefix, node.rightChild); 142 | } 143 | } 144 | 145 | /** 146 | * 相识度查找 147 | * @param s 要查找的单词 148 | * @return 149 | */ 150 | public HashSet findSimilar(String s) 151 | { 152 | Node node = this.find(s); 153 | this.DFS(s, node); 154 | return _hashSet; 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /trie/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | learn-algorithm 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | trie 13 | 14 | 15 | -------------------------------------------------------------------------------- /trie/src/main/java/com/flydean/Trie.java: -------------------------------------------------------------------------------- 1 | package com.flydean; 2 | 3 | /** 4 | * @author wayne 5 | * @version Trie, 2020/11/5 6 | */ 7 | public class Trie { 8 | // 假如字典中的单词只有26个英文字母 9 | static final int ALPHABET_SIZE = 26; 10 | 11 | // Trie的node节点 12 | static class TrieNode 13 | { 14 | TrieNode[] children = new TrieNode[ALPHABET_SIZE]; 15 | 16 | // 这个节点是否是结束节点 17 | boolean isEndOfWord; 18 | 19 | //初始化TireNode节点 20 | TrieNode(){ 21 | isEndOfWord = false; 22 | for (int i = 0; i < ALPHABET_SIZE; i++) 23 | children[i] = null; 24 | } 25 | } 26 | 27 | static TrieNode root= new TrieNode(); 28 | 29 | // 如果当前level中不存在,那么就会在特定的位置插入新的节点 30 | // 如果当前level中存在该字符,则从其子节点继续插入 31 | // 最后,将该叶子节点标记为isEndOfWord。 32 | static void insert(String key) 33 | { 34 | int level; 35 | int length = key.length(); 36 | int index; 37 | 38 | TrieNode currentNode = root; 39 | 40 | for (level = 0; level < length; level++) 41 | { 42 | index = key.charAt(level) - 'a'; 43 | if (currentNode.children[index] == null) 44 | currentNode.children[index] = new TrieNode(); 45 | 46 | currentNode = currentNode.children[index]; 47 | } 48 | 49 | // 将叶子节点标记为 isEndOfWord 50 | currentNode.isEndOfWord = true; 51 | } 52 | 53 | // 执行搜索,也是按level来进行查询 54 | static boolean search(String key) 55 | { 56 | int level; 57 | int length = key.length(); 58 | int index; 59 | TrieNode currentNode = root; 60 | 61 | for (level = 0; level < length; level++) 62 | { 63 | index = key.charAt(level) - 'a'; 64 | 65 | if (currentNode.children[index] == null) 66 | return false; 67 | 68 | currentNode = currentNode.children[index]; 69 | } 70 | 71 | return (currentNode != null && currentNode.isEndOfWord); 72 | } 73 | 74 | //判断该节点是否有子节点 75 | static boolean hasChild(TrieNode currentNode) 76 | { 77 | for (int i = 0; i < ALPHABET_SIZE; i++) 78 | if (currentNode.children[i] != null) 79 | return true; 80 | return false; 81 | } 82 | 83 | static TrieNode remove(TrieNode currentNode, String key, int level ){ 84 | if(currentNode ==null){ 85 | return null; 86 | } 87 | 88 | int length = key.length(); 89 | 90 | //正在处理最后一个字符 91 | if(level == length){ 92 | //将当前节点的标志位删除 93 | if(currentNode.isEndOfWord){ 94 | currentNode.isEndOfWord= false; 95 | } 96 | //如果没有子节点,则只接受删除该节点 97 | if (!hasChild(currentNode)) { 98 | currentNode = null; 99 | } 100 | 101 | return currentNode; 102 | } 103 | 104 | // 如果不是最后一个节点,则递归调用其子节点 105 | int index = key.charAt(level) - 'a'; 106 | currentNode.children[index] = 107 | remove(currentNode.children[index], key, level + 1); 108 | 109 | // 如果当前节点既没有子节点,也不是其他单词的结束节点,那么直接将这个节点删除即可。 110 | if (!hasChild(currentNode) && currentNode.isEndOfWord == false) { 111 | currentNode = null; 112 | } 113 | return currentNode; 114 | } 115 | 116 | public static void main(String args[]) 117 | { 118 | String keys[] = {"www", "flydean", "com", "is", "a", 119 | "good", "website"}; 120 | 121 | // 构造Trie tree 122 | int i; 123 | for (i = 0; i < keys.length ; i++) 124 | insert(keys[i]); 125 | } 126 | } 127 | --------------------------------------------------------------------------------