Some basic ideas of classic problems/algorithms.
BinarySearch
2020更新:根据这个算法总结了一个不那么绕的二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 int binarySearch (int [] nums, int target) { if (nums == null || nums.length == 0 ) { return -1 ; } int left = 0 ; int right = nums.length - 1 ; while (left <= right) { int mid = left + (right - left) / 2 ; if (nums[mid] == target) { return nums[mid]; } else if (nums[mid] < target) { left = mid + 1 ; } else if (nums[mid] > target) { right = mid - 1 ; } } return -1 ; } int binarySearchFirst (int [] nums, int target) { if (nums == null || nums.length == 0 ) { return -1 ; } int left = 0 ; int right = nums.length; while (left < right) { int mid = left + (right - left) / 2 ; if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1 ; } else if (nums[mid] > target) { right = mid; } } return left; } int binarySearchLast (int [] nums, int target) { if (nums == null || nums.length == 0 ) { return -1 ; } int left = 0 ; int right = nums.length; while (left < right) { int mid = left + (right - left) / 2 ; if (nums[mid] == target) { left = mid + 1 ; } else if (nums[mid] < target) { left = mid + 1 ; } else if (nums[mid] > target) { right = mid; } } return left - 1 ; }
Deprecated
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 private int binarySearchInsertionPos (int [] nums, int target) { int start = -1 , end = nums.length; while (end - start > 1 ) { int mid = start + (end - start) / 2 ; if (nums[mid] >= target) { end = mid; } else { start = mid; } } return end; } private int binarySearchSqrt (int x) { if (x < 1 ) { return 0 ; } long start = -1 , end = (long )x + 1 ; while (end - start > 1 ) { long mid = start + (end - start) / 2 ; if (mid > x / mid) { end = mid; } else { start = mid; } } return (int )start; } private int binarySearchFirst (int [] nums, int target) { int start = -1 , end = nums.length; while (end - start > 1 ) { int mid = start + (end - start) / 2 ; if (nums[mid] >= target) { end = mid; } else { start = mid; } } return end < nums.length && nums[end] == target ? end : -1 ; } private int binarySearchLast (int [] nums, int target) { int start = -1 , end = nums.length; while (end - start > 1 ) { int mid = start + (end - start) / 2 ; if (nums[mid] <= target) { start = mid; } else { end = mid; } } return start >= 0 && nums[start] == target ? start : -1 ; }
Sort Selection Sort 外层循环i从头到尾、内层循环从i往后,每次找最小值所在的index,内层循环结束后和头部swap,这样每次都可以把当前最小值放到最前面。 1 2 3 4 5 6 7 8 9 10 11 private void selectionSort (int [] arr) { for (int i = 0 ; i < arr.length; i++) { int index = i; for (int j = i + 1 ; j < arr.length; j++) { if (arr[j] < arr[index]) { index = j; } } swap(arr, i, index); } }
Insertion Sort 外层循环i从前往后遍历,内层循环从i往前,每次将相邻的顺序反了的元素swap。 1 2 3 4 5 6 7 private void insertionSort (int [] arr) { for (int i = 0 ; i < arr.length; i++) { for (int j = i; j > 0 && arr[j] < arr[j - 1 ]; j--) { swap(arr, j, j - 1 ); } } }
Shell Sort 插入排序的升级版,循环的interval是从大到小的。排序的本质是消除逆序对,如果增大interval,则一次swap可能会消除更多的逆序对,因此可以突破O(N^2). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void shellSort (int [] arr) { int h = 1 ; while (h < arr.length / 3 ) { h = 3 * h + 1 ; } while (h >= 1 ) { for (int i = h; i < arr.length; i++) { for (int j = i; j >= h && arr[j] < arr[j - h]; j -= h) { swap(arr, j, j - h); } } h /= 3 ; } }
Bubble Sort 外层循环从头到尾,内层循环从尾到i + 1不断把较小值放到前面。 1 2 3 4 5 6 7 8 9 private void bubbleSort (int [] arr) { for (int i = 0 ; i < arr.length; i++) { for (int j = arr.length - 1 ; j > i; j--) { if (arr[j] < arr[j - 1 ]) { swap(arr, j, j - 1 ); } } } }
Bucket Sort 木桶排序牺牲了空间换取速度,达到了O(N)。统计每个值出现的个数,然后依次放回原数组。 1 2 3 4 5 6 7 8 9 10 11 12 13 private void bucketSort (int [] arr) { int bucket = new int [1000010 ]; for (int i = 0 ; i < arr.length; i++) { bucket[arr[i]]++; } int j = 0 , k = 0 ; while (j < arr.length && k < bucket.length) { while ((bucket[k]--) > 0 ) { arr[j++] = k; } k++; } }
Radix Sort[====TODO====] 基数排序就是根据每个位数字大小,从个位数、十位数、百位数、千位数…存入bucket 1 2 3 4 5 6 7 8 9 10 private void radixSort (int [] arr, int radix, int maxLen) { int [] temp = new int [arr.length]; int [] bucket = new int [radix]; for (int i = 0 , devide = 1 ; i < maxLen; i++) { Arrays.fill(bucket, 0 ); System.arraycopy(arr, 0 , temp, 0 , arr.length); } }
Merge Sort 分治法思想,将两个序列不断拆分,拆到不能再拆后分别排序后合并。归并排序(以及插入排序)是稳定的。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public void mergeSort (int [] arr) { partition(arr, 0 , arr.length - 1 ); } private void partition (int [] arr, int start, int end) { if (start >= end) { return ; } int mid = (start + end) / 2 ; partition(arr, start, mid); partition(arr, mid + 1 , end); if (arr[mid] < arr[mid + 1 ]) { return ; } merge(arr, start, mid, end); } private void merge (int [] arr, int start, int mid, int end) { int left = start, right = mid + 1 , index = start; int [] temp = new int [arr.length]; while (left <= mid && right <= end) { if (arr[left] < arr[right]) { temp[index++] = arr[left++]; } else { temp[index++] = arr[right++]; } } while (left <= mid) { temp[index] = arr[left++]; } while (right <= end) { temp[index] = arr[right++]; } for (index = start; index <= end; index++) { arr[index] = temp[index]; } }
Quick Sort + Shuffle 定义一个pivot值,找到它的位置使得左边都小于等于它、右边都大于等于它,然后再递归到两边去排。最差情况出现在当已经有序的时候,退化成O(N^2),因为每个pivot都要和后面的比较一波。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 private void shuffle (int [] arr) { for (int i = 0 ; i < arr.length; i++) { swap(arr, i, Random.nextInt(i + 1 )); } } private void quickSort (int [] arr) { quickSort(arr, 0 , arr.length - 1 ); } private void quickSort (int [] arr, int start, int end) { if (start >= end) { return ; } int j = partition(arr, start, end); quickSort(arr, start, j - 1 ); quickSort(arr, j + 1 , end); } private int partition (int [] arr, int start, int end) { int left = start, right = end + 1 , pivot = arr[start]; while (true ) { while (arr[++left] < pivot) { if (left == end) { break ; } } while (arr[--right] > pivot) { if (right == start) { break ; } } if (left >= right) { break ; } swap(arr, left, right); } swap(arr, start, right); return right; }
Big Integer Calculation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class Multiply { public String multiply (String num1, String num2) { if (num1 == null || num2 == null || num1.length() == 0 || num2.length() == 0 ) { return "" ; } int m = num1.length(), n = num2.length(); int [] result = new int [m + n]; char [] cnum1 = num1.toCharArray(), cnum2 = num2.toCharArray(); for (int i = m - 1 ; i >= 0 ; i--) { for (int j = n - 1 ; j >= 0 ; j--) { int mult = (cnum1[i] - '0' ) * (cnum2[j] - '0' ); int first = i + j, second = i + j + 1 ; int sum = mult + result[second]; result[first] += sum / 10 ; result[second] = sum % 10 ; } } StringBuilder sb = new StringBuilder (); int i = 0 ; while (i < result.length && sb.length() == 0 && result[i] == 0 ) { i++; } while (i < result.length) { sb.append(result[i++]); } return sb.length() == 0 ? "0" : sb.toString(); } } class Power { public double myPow (double x, int n) { if (n == 0 || x == 1 ) { return 1 ; } if (n == Integer.MIN_VALUE) { return (1.0 / x) * myPow(x, n + 1 ); } if (n < 0 ) { n = -n; x = 1.0 / x; } return (n & 1 ) != 0 ? x * myPow(x * x, n / 2 ) : myPow(x * x, n / 2 ); } }
GCD 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public int findGcd (int a, int b) { while (b != 0 ) { int t = a; a = b; b = t % b; } return a; } public int findGcd (int a, int b) { if (b > a) { return findGcd(b, a); } return b == 0 ? a : findGcd(b, a % b); }
Union Find 并查集:在UF类中维护一个id数组,这个id表示当前索引的祖宗的索引,若id[idx] = idx
说明它本身就是老大,初始时每个元素都是自己的老大。find(p)
函数就是返回p的老大的索引,union(p, q)
函数就是将索引p和q两伙合并起来,分别找到p和q的老大索引pRoot和qRoot,但是他们谁隶属于谁呢?rank就是在这时候起作用,将rank高的保留,rank低的老大合并到更高的老大那去,若两个老大一样,就随意了,合并后新老大的rank要加加。在UF中还有一个count变量,其实就是算有几个独立老大的,初始时每个1都是老大,而每次成功调用union时count都会减减。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class UF { private int [] id = null ; private int [] rank = null ; private int count = 0 ; public UF (char [][] grid) { int rows = grid.length, cols = grid[0 ].length; id = new int [rows * cols]; rank = new int [rows * cols]; for (int i = 0 ; i < rows; i++) { for (int j = 0 ; j < cols; j++) { if (grid[i][j] == '1' ) { count++; } int temp = i * cols + j; id[temp] = temp; rank[temp] = 0 ; } } } public int find (int p) { while (id[p] != p) { id[p] = id[id[p]]; p = id[p]; } return p; } public void union (int p, int q) { int pRoot = find(p); int qRoot = find(q); if (pRoot == qRoot) { return ; } if (rank[pRoot] > rank[qRoot]) { id[qRoot] = pRoot; } else if (rank[qRoot] > rank[pRoot]) { id[pRoot] = qRoot; } else { id[pRoot] = qRoot; rank[qRoot]++; } count--; } public boolean isConnected (int p, int q) { return find(p) == find(q); } public int getCount () { return count; } }
https://leetcode.com/problems/number-of-islands/
Backtracking 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public List<List<Integer>> subsets (int [] nums) { List<List<Integer>> list = new ArrayList <>(); backtrack(list, new ArrayList <>(), nums, 0 ); return list; } private void backtrack (List<List<Integer>> list , List<Integer> tempList, int [] nums, int start) { list.add(new ArrayList <>(tempList)); for (int i = start; i < nums.length; i++){ tempList.add(nums[i]); backtrack(list, tempList, nums, i + 1 ); tempList.remove(tempList.size() - 1 ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public List<List<Integer>> subsetsWithDup (int [] nums) { List<List<Integer>> list = new ArrayList <>(); Arrays.sort(nums); backtrack(list, new ArrayList <>(), nums, 0 ); return list; } private void backtrack (List<List<Integer>> list, List<Integer> tempList, int [] nums, int start) { list.add(new ArrayList <>(tempList)); for (int i = start; i < nums.length; i++){ if (i > start && nums[i] == nums[i - 1 ]) continue ; tempList.add(nums[i]); backtrack(list, tempList, nums, i + 1 ); tempList.remove(tempList.size() - 1 ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public List<List<Integer>> permute (int [] nums) { List<List<Integer>> list = new ArrayList <>(); backtrack(list, new ArrayList <>(), nums); return list; } private void backtrack (List<List<Integer>> list, List<Integer> tempList, int [] nums) { if (tempList.size() == nums.length) { list.add(new ArrayList <>(tempList)); } else { for (int i = 0 ; i < nums.length; i++) { if (tempList.contains(nums[i])) continue ; tempList.add(nums[i]); backtrack(list, tempList, nums); tempList.remove(tempList.size() - 1 ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public List<List<Integer>> permuteUnique (int [] nums) { List<List<Integer>> list = new ArrayList <>(); Arrays.sort(nums); backtrack(list, new ArrayList <>(), nums, new boolean [nums.length]); return list; } private void backtrack (List<List<Integer>> list, List<Integer> tempList, int [] nums, boolean [] used) { if (tempList.size() == nums.length) { list.add(new ArrayList <>(tempList)); } else { for (int i = 0 ; i < nums.length; i++) { if (used[i] || i > 0 && nums[i] == nums[i-1 ] && !used[i - 1 ]) continue ; used[i] = true ; tempList.add(nums[i]); backtrack(list, tempList, nums, used); used[i] = false ; tempList.remove(tempList.size() - 1 ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Solution { private void dfs (List<List<Integer>> ans, List<Integer> arr, int n, int k, int start) { if (arr.size() == k) { ans.add(new ArrayList (arr)); return ; } for (int i = start; i <= n; i++) { arr.add(i); dfs(ans, arr, n, k, i+1 ); arr.remove(arr.size()-1 ); } } public List<List<Integer>> combine (int n, int k) { List<List<Integer>> ans = new ArrayList <List<Integer>>(); ArrayList<Integer> arr = new ArrayList <Integer>(); if (n <= 0 ) { return ans; } dfs(ans, arr, n, k, 1 ); return ans; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public List<List<Integer>> combinationSum (int [] nums, int target) { List<List<Integer>> list = new ArrayList <>(); Arrays.sort(nums); backtrack(list, new ArrayList <>(), nums, target, 0 ); return list; } private void backtrack (List<List<Integer>> list, List<Integer> tempList, int [] nums, int remain, int start) { if (remain < 0 ) return ; else if (remain == 0 ) list.add(new ArrayList <>(tempList)); else { for (int i = start; i < nums.length; i++){ tempList.add(nums[i]); backtrack(list, tempList, nums, remain - nums[i], i); tempList.remove(tempList.size() - 1 ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public List<List<Integer>> combinationSum2 (int [] nums, int target) { List<List<Integer>> list = new ArrayList <>(); Arrays.sort(nums); backtrack(list, new ArrayList <>(), nums, target, 0 ); return list; } private void backtrack (List<List<Integer>> list, List<Integer> tempList, int [] nums, int remain, int start) { if (remain < 0 ) return ; else if (remain == 0 ) list.add(new ArrayList <>(tempList)); else { for (int i = start; i < nums.length; i++){ if (i > start && nums[i] == nums[i - 1 ]) continue ; tempList.add(nums[i]); backtrack(list, tempList, nums, remain - nums[i], i + 1 ); tempList.remove(tempList.size() - 1 ); } } }
根据pattern构建有限状态机的DP数组,然后再取给定的一堆text中找匹配的索引。dp[j][c] = next
表示,当前是状态j
,遇到了字符c
,应该转移到状态next
. 需要一个辅助状态X
,它永远比当前状态j
落后一个状态,拥有和j
最长的相同前缀,我们给它起了个名字叫「影子状态」.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class KMP { private int [][] dp; private String pattern; public KMP (String pattern) { this .pattern = pattern; int M = this .pattern.length() dp = new int [][256 ]; dp[0 ][this .pattern.charAt(0 )] = 1 ; int X = 0 ; for (int j = 1 ; j < M; j++) { for (int c = 0 ; c < 256 ; c++) { dp[j][c] = dp[X][c]; } dp[j][pattern.charAt(j)] = j + 1 ; X = dp[X][pattern.charAt(j)]; } } public int search (String text) { int M = pattern.length(); int N = text.length(); int j = 0 ; for (int i = 0 ; i < N; i++) { j = dp[j][text.charAt(i)]; if (j == M) { return i - M + 1 ; } } return -1 ; } }
此外再介绍leetcode模版。在构建pattern的dp数组时索引从1开始且单向前进,在text中搜索时索引从0开始且时单向前进。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public int strStr (String haystack, String needle) { if (haystack == null || needle == null || needle.isEmpty()) { return 0 ; } int len = needle.length(); int [] dp = new int [len]; for (int i = 1 ; i < len; i++) { int j = dp[i - 1 ]; while (j > 0 && needle.charAt(j) != needle.charAt(i)) { j = dp[j - 1 ]; } if (needle.charAt(j) == needle.charAt(i)) { j++; } dp[i] = j; } int lenFull = haystack.length(); int j = 0 ; for (int i = 0 ; i < lenFull; i++) { while (j > 0 && haystack.charAt(i) != needle.charAt(j)) { j = dp[j - 1 ]; } if (haystack.charAt(i) == needle.charAt(j)) { j++; } if (j == len) { return i - len + 1 ; } } return -1 ; } }
Stack Tree https://leetcode.com/problems/boundary-of-binary-tree/
Preorder Iterative 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public List<Integer> preorderTraversal (TreeNode root) { List<Integer> output = new LinkedList <>(); LinkedList<TreeNode> stack = new LinkedList <>(); if (root == null ) { return output; } stack.add(root); while (!stack.isEmpty()) { TreeNode node = stack.pollLast(); output.add(node.val); if (node.right != null ) { stack.add(node.right); } if (node.left != null ) { stack.add(node.left); } } return output; }
Inorder Iterative 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public List<Integer> inorderTraversal (TreeNode root) { List<Integer> ans = new ArrayList <>(); if (root == null ) { return ans; } Deque<TreeNode> s = new ArrayDeque <>(); TreeNode curr = root; while (curr != null || !s.isEmpty()) { while (curr != null ) { s.push(curr); curr = curr.left; } curr = s.pop(); ans.add(curr.val); curr = curr.right; } return ans; }
complete二叉树是指每一层都逐步从左到右填充节点,左右孩子高度差顶多为1. 计算有多少个节点在普通二叉树中是O(N)
,而在完美二叉树中节点数就是高度的二次幂,因此O(logN)
求高度即可。而在完全二叉树中直接遍历比较蠢,就得想怎么利用高度做文章。如果左右孩子的高度一样,那就直接照搬完美二叉树;如果不同就继续按照O(N)
递归。时间复杂度为O(logN * logN)
(每次递归的复杂度为logN,总共只需要logN次递归,因为完全二叉树其中一个子树一定为完美二叉树,并不是每一次都会触发递归)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public int countCompleteTreeNodes (TreeNode root) { TreeNode l = root, r = root; int heightLeft = 0 , heightRight = 0 ; while (l != null ) { l = l.left; heightLeft++; } while (r != null ) { r = r.right; heightRight++; } if (heightLeft == heightRight) { return (int )Math.pow(2 , heightLeft) - 1 ; } return 1 + countCompleteTreeNodes(root.left) + countCompleteTreeNodes(root.right); }
Postorder Iterative 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public List<Integer> postorderTraversal (TreeNode root) { List<Integer> retVal = new LinkedList <>(); if (root == null ) { return retVal; } Deque<TreeNode> treeNodeStack = new ArrayDeque <>(); treeNodeStack.push(root); while (!treeNodeStack.isEmpty()) { TreeNode curr = treeNodeStack.pop(); if (curr.left != null ) { treeNodeStack.push(curr.left); } if (curr.right != null ) { treeNodeStack.push(curr.right); } retVal.add(curr.val); } Collections.reverse(retVal); return retVal; }
Trie
String substring palindrome: 516, 647. https://leetcode.com/submissions/detail/121291766/ https://leetcode.com/submissions/detail/121292245/