概述
程序 = 算法 + 數據結構
- 算法是計算機科學的本質,是計算機世界的基石。算法決定了程序如何運行
- 數據結構決定了程序的數據如何被存儲
算法的復雜度
時間復雜度
定性描述算法的運行時間
- O(1) => 常數時間 => 運行時間與問題的規模無關 => 哈希桶 | 數組隨機尋址 => 數組(Array) | 線性表(Linear Table)
- O(n) => 線性時間 => 運行時間與問題的規模成正比 => 遍歷 | 求數組、鏈表的最大值
- O(log(n)) => 對數時間 => 每次操作其所需要的額外計算時間會變小 => 二分查找 | 二叉樹
- O(n*log(n)) => 基于比較的排序算法的下界
- O(n^2) => 解決問題的復雜度和問題規模的平方成正比 => 冒泡排序
- 時間復雜度計算時忽略常數 =>
O(n) == O(2n)
- 時間復雜度的計算中,高階復雜度會吞并低階復雜度 =>
O(n^2) + O(n) == O(n^2)
=> 對數組進行排序后遍歷,復雜度是多少? =>O(n*log(n)) + O(n) == O(n*log(n))
- 最好時間復雜度
- 最壞時間復雜度
- 平均時間復雜度
線性時間復雜度
- 求數組、鏈表的最大值 | 和
- 尋找數組中的重復元素
- 判斷鏈表是否存在環(快慢指針)
- 求階乘
- 合并兩個鏈表
- 翻轉鏈表
對數時間復雜度
遍歷二叉樹
- 深度優先遍歷 DFS(Depth First Search) => 遞歸
- 前序遍歷(根節點 => 左節點 => 右節點)
- 中序遍歷(左節點 => 根節點 => 右節點)
- 后序遍歷(左節點 => 右節點 => 根節點)
- 廣度優先遍歷 BFS(Breadth First Search) => 經典的廣度優先遍歷算法 => 隊列(先進先出 FIFO)
遞歸
- 將大問題分解成小問題
- 假設小問題已經解決
- 對分解的小問題進行求解
空間復雜度
解決問題所需要的輔助空間的大小
- 常數空間復雜度 => O(1) => 只需要固定大小的輔助空間 => 尋找最大值 | 求數組所有元素的和 | 非遞歸計算階乘
- 線性空間復雜度 => O(n) => 需要的輔助空間和問題規模成正比 => 遞歸計算階乘 | 帶緩存(備忘錄)的 斐波那契數列 求值
帶緩存(備忘錄)的 斐波那契數列 求值
斐波那契數列 => 1 1 2 3 5 8 13 21 34 55 89 ...
- F0 = 0
- F1 = 1
- Fn = Fn-1 + Fn-2
class Counter {
HashMap<Integer, Integer> cache = new HashMap<>();
public int fibonacci(int n) {
if (n == 0 || n == 1) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
public int fibonacci4Cache(int n) {
if (cache.containsKey(n)) {
// 緩存命中 cache hit
return cache.get(n);
}
// cache miss
if (n == 0 || n == 1) {
return 1;
}
int result = fibonacci4Cache(n - 1) + fibonacci4Cache(n - 2);
cache.put(n, result);
return result;
}
}
數據結構
數組 | 線性表
java.util.ArrayList
=> 擴容
- 隨機尋址 => RandomAccess => 常數時間 => O(1)
- 插入 | 刪除 => 線性時間 => O(n)
- 查找
- 無序數組查找 => 線性時間 => O(n)
- 有序數組查找 => 對數時間(二分查找[遞歸 & 非遞歸]) => O(log(n))
二分查找
應用于有序數組的快速算法
- 非遞歸 => 雙指針
public static int binarySearch(int[] array, int key) { int low = 0; int high = array.length - 1; while (low <= high) { int mid = (low + high) / 2; int midVal = array[mid]; if (midVal < key) { low = mid + 1; } else if (midVal > key) { high = mid - 1; } else { return mid; // key found } } return -(low + 1); // key not found. }
- 遞歸
public static int binarySearchForRecursion(int[] array, int target, int start, int end) { int mid = (start + end) >>> 1; int midVal = array[mid]; if (start > end) { return -1; } else if (target == array[start]) { return start; } else if (target == midVal) { return mid; } else if (target == array[end]) { return end; } else if (array[start] < target && target < midVal) { return binarySearchForRecursion(array, target, start + 1, mid - 1); } else { return binarySearchForRecursion(array, target, mid + 1, end - 1); } }
鏈表 & 雙向鏈表
java.util.LinkedList
=> 雙向鏈表(Doubly-linked list) => addAll
& get
- 尋址 => 線性時間 => O(n)
- 插入 | 刪除 => 常數時間 => O(1)
- 查找 => 線性時間 => O(n)
遍歷鏈表數據
for(Node current = head; current != null; current = current.next) {
System.out.println(current.value);
}
如果要刪除一個數據,需要將前一個數據的 next
指向下一個數據,之后將刪除數據的 next
刪除
翻轉鏈表
// TODO
判斷鏈表是否成環
// TODO
棧 Stack
java.util.Stack extends java.util.Vector
=> push
& pop
FILO(First In Last Out)
應用:方法棧
手寫一個棧的實現
public class Stack {
private final LinkedList<Object> data = new LinkedList<>();
// 將一個元素推入棧中
public void push(Object obj) {
data.add(obj);
}
// 將一個元素從棧頂彈出
public Object pop() {
return data.removeLast();
}
// 看看棧頂的元素,不彈出來
public Object peek() {
return data.getLast();
}
}
隊列 Queue
java.util.Queue.java
=> java.util.LinkedList.java
FIFO(First In First Out)
應用:線程池
手寫一個隊列的實現
// TODO => add | offer | remove | poll => java.util.LindedList
哈希表
java.util.HashMap
=> put
& get
- 哈希表的時間復雜度指的是平均時間復雜度
- 查找 | 插入 | 刪除 => 常數時間 => O(1)
-
哈希算法 & 碰撞
- Java8之前 => 哈希桶 + 鏈表
- Java8之后 => 哈希桶 + 鏈表 | 紅黑樹
二叉樹
搜索二叉樹
紅黑樹
自平衡二叉查找樹 => 修改、插入、刪除之后可以自己變成平衡的 => java.util.TreeSet
(java.util.TreeMap
) & java.util.concurrent.ConcurrentSkipListSet
=> put
& remove
平衡二叉樹很難做成線程安全的,在旋轉、修改的過程中多個線程并發訪問會有問題
二叉樹前序遍歷(根節點 => 左節點 => 右節點)
- 遞歸
class OrderTraversal {
public List<Integer> preorderTraversalRecursion(TreeNode root) {
List<Integer> result = new ArrayList<>();
result.add(root.val);
if (root.left != null) {
result.addAll(preorderTraversalRecursion(root.left));
}
if (root.right != null) {
result.addAll(preorderTraversalRecursion(root.right));
}
return result;
}
}
- 非遞歸
class OrderTraversal {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> treeNodeStack = new Stack<>();
TreeNode node = root;
while (node != null || !treeNodeStack.isEmpty()) {
while (node != null) {
treeNodeStack.add(node);
result.add(node.val);
node = node.left;
}
node = treeNodeStack.pop().right;
}
return result;
}
}
二叉樹中序遍歷(左節點 => 根節點 => 右節點)
- 遞歸
class OrderTraversal {
public List<Integer> middleOrderTraversalRecursion(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root.left != null) {
result.addAll(middleOrderTraversalRecursion(root.left));
}
result.add(root.val);
if (root.right != null) {
result.addAll(middleOrderTraversalRecursion(root.right));
}
return result;
}
}
- 非遞歸
class OrderTraversal {
public List<Integer> middleOrderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> treeNodeStack = new Stack<>();
TreeNode node = root;
while (node != null || !treeNodeStack.isEmpty()) {
while (node != null) {
treeNodeStack.push(node);
node = node.left;
}
node = treeNodeStack.pop();
result.add(node.val);
node = node.right;
}
return result;
}
}
二叉樹后序遍歷(左節點 => 右節點 => 根節點)
// TODO
二叉樹廣度優先遍歷 BFS
// 將根節點加入隊列,算法開始
// 如果隊列不為空,則進入循環
// 隊列頭節點出隊列 & 同時將其孩子依次加入隊列
public static List<Integer> bfs(Node root) {
List<Integer> list = new ArrayList<>();
Queue<Node> queue = new LinkedList<>(Collections.singleton(root));
while (!queue.isEmpty()) {
Node head = queue.remove();
list.add(head.value);
if (head.left != null) {
queue.add(head.left);
}
if (head.right != null) {
queue.add(head.right);
}
}
return list;
}
算法
排序算法
- 穩定 vs 不穩定 => 如果兩個相等的數字,排序前和排序后位置保持不變,則是穩定的,否則是不穩定的。
- 基于比較的排序時間復雜度下限是
O(n * log(n))
- Java 默認使用
DualPivotQuicksort.sort
排序 =>Arrays.sort
&Collections.sort
冒泡排序
時間復雜度 => O(n^2)
public class BubbleSort {
public static void main(String[] args) {
bubbleSort(new int[]{1, 5, 1, 6, 2, 7, 3, 8});
}
public static void bubbleSort(int[] array) {
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length - i - 1; j++) {
int next = array[j + 1];
if (array[j] > next) {
array[j] = next;
array[j + 1] = array[j];
}
}
}
}
}
快速排序
- 每次將等待排序的數據劃分成兩部分
- 遞歸進行
- 直到所有數據有序
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class QuickSort {
public static void main(String[] args) {
int[] array = {5, 1, 6, 2, 4, 2, 3};
List<Integer> list = quickSort(Arrays.asList(5, 1, 6, 2, 4, 2, 3));
quickSortForInPlace(array);
System.out.println(list);
}
// 分治法(Divide and conquer)需要O(n)的額外空間
public static List<Integer> quickSort(List<Integer> list) {
if (list.size() <= 1) {
return list;
}
List<Integer> left = new ArrayList<>(), pivot = new ArrayList<>(), right = new ArrayList<>();
Integer pivotValue = list.get(0);
for (Integer element : list) {
if (element < pivotValue) {
left.add(element);
} else if (element.equals(pivotValue)) {
pivot.add(element);
} else {
right.add(element);
}
}
return Stream.of(quickSort(left), pivot, quickSort(right)).flatMap(Collection::stream).collect(Collectors.toList());
}
// 原地(in-place)分割版本
public static void quickSortForInPlace(int[] array) {
quickSortPartition(array, 0, array.length - 1);
}
// 對數組中的某個區域進行排序
public static void quickSortPartition(int[] array, int left, int right) {
if (right > left) {
// 將 [left - right] 區域分成了
// [left, pivotIndex - 1] 和 [pivotIndex + 1, right] 兩個
int pivotIndex = partition(array, left, right);
quickSortPartition(array, left, pivotIndex - 1);
quickSortPartition(array, pivotIndex + 1, right);
}
}
public static int partition(int[] array, int left, int right) {
// 挑選最右側的作為基準值
int pivotValue = array[right];
int storeIndex = left;
for (int i = left; i < right; i++) {
if (array[i] <= pivotValue) {
swap(array, i, storeIndex);
storeIndex += 1;
}
}
swap(array, storeIndex, right);
return storeIndex;
}
private static void swap(int[] x, int a, int b) {
int t = x[a];
x[a] = x[b];
x[b] = t;
}
}
桶排序
- 不基于比較排序,所以不受O(n * log(n))限制
- 總的時間復雜度 => O(n)
- 空間復雜度 => O(n)
動態規劃
通過把原問題分解為相對簡單的子問題的方式求解復雜問題的方法
首先將問題分解,假設子問題已經解決 => 然后推導出來一個狀態轉移公式 => 狀態是如何從更小規模的子問題到難題轉移的
上臺階問題
// TODO
有一樓梯共m級,若每次只能跨上一級或兩級,要走上m級,總共有多少走法?
硬幣問題
public class Coins {
// 硬幣:[1, 5, 10, 25]
// f(k, n) 只用前k種硬幣組成n分的情況
// = f(k-1, n - 0 * COIN) // 只用前k-1種硬幣組成n分的情況(0個第k個硬幣)
// + f(k-1, n - 1 * COIN) // 只用前k-1種硬幣組成 n - 1 * COIN 分的情況(1個第k個硬幣)
// + f(k-1, n - 2 * COIN) // 只用前k-1種硬幣組成 n - 2 * COIN 分的情況(2個第k個硬幣)
// + ...
// + f(k-1, n - i * COIN) // 只用前k-1種硬幣組成 n - i * COIN 分的情況(i個第k個硬幣)
// 直到 n - i * COIN 的值為 0
public static int[] coin = new int[]{0, 1, 5, 10, 25};
public static int[] coins = new int[]{1, 5, 10, 25};
public static HashMap<Integer, Integer> cache = new HashMap<>();
public static int f(int k, int n) {
if (k == 1) {
return 1;
}
int currentCoin = coin[k];
int result = 0, i = 0;
while ((n - i * currentCoin) >= 0) {
result += f(k - 1, n - i * currentCoin);
i++;
}
return result;
}
public static int waysToChange(int n) {
return f(4, n);
}
public static void main(String[] args) {
System.out.println(waysToChange(6));
System.out.println(howManyCoins(6));
}
// 假定 f(n) 是組成n分情況的總和
// 對于 每個硬幣 COIN
// = 不使用 COIN + 使用 COIN
// = 使用0個COIN + 使用1個COIN + 使用2個COIN + ... + 使用i個COIN
public static int howManyCoins(int n) {
int[] result = new int[n + 1];
result[0] = 1;
for (int coin : coins) {
// 對于每個 COIN,可以組成 COIN 分,COIN + 1 分 ... n分
for (int i = coin; i <= n; i++) {
result[i] = (result[i] + result[i - coin]) % 1000000007;
}
}
return result[n];
}
}
不同的二叉搜索樹
public class BinarySearchTree {
// f(n) n個節點二叉搜索樹
// g(i) i為根節點,n個節點的二叉搜索樹
// 左子樹一定有 i - 1 個元素 => 左子樹有 f(i-1) 種情況
// 右子樹一定有 n - i 個元素 => 右子樹有 f(n-i) 中情況
// f(n) = g(1) + g(2) + g(3) + ... + g(n-1) + g(n)
// = f(0) * f(n-1) + f(1) * f(n-2) + f(2) * f(n-3) + ... + f(n-2) * f(1) + f(n-1) * f(0)
// f(3) = g(1) + g(2) + g(3)
// g(1) 左子樹一定有0個元素,右子樹一定有2個元素
// g(2) 左子樹一定有1個元素,右子樹一定有1個元素
// g(3) 左子樹一定有2個元素,右子樹一定有0個元素
public static int recursion(int n) {
if (n == 0) {
return 1;
}
if (n == 1 || n == 2) {
return n;
}
int result = 0;
for (int i = 0; i < n; i++) {
result += recursion(i) * recursion(n - i - 1);
}
return result;
}
public static int loop(int n) {
int[] result = new int[n + 1];
result[0] = 1;
if (n >= 0) {
result[1] = 1;
}
if (n >= 1) {
result[2] = 2;
}
for (int i = 3; i <= n; i++) {
// 計算第i個位置上的結果,第i個位置上的結果由前i-1個結果生成
int sum = 0;
for (int j = 0; j < i; j++) {
sum += result[j] * result[i - j - 1];
}
result[i] = sum;
}
return result[n];
}
public static void main(String[] args) {
System.out.println(loop(3));
}
}
分治
- 把大問題分解成較小的問題,分別求解,之后將小問題的解合起來,同時設置一些退出條件
// 數組求和
public class ArraySum {
public static int sum(int[] array) {
return sum(array, 0, array.length);
}
public static int sum(int[] array, int start, int end) {
if (start == end) {
return array[start];
}
if (start > end) {
return 0;
}
int mid = (start + end) / 2;
return sum(array, start, mid - 1) + array[mid] + sum(array, mid + 1, end);
}
}
- Fork/Join 框架 => 分而治之的實例
// ForkJoinPool 解決數組求和
public class ArraySumForForkJoinPool {
public static int sumParallel(int[] array) throws ExecutionException, InterruptedException {
ForkJoinPool pool = new ForkJoinPool();
return pool.submit(new SubArraySum(array, 0, array.length - 1)).get();
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println(sumParallel(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9}));
}
}
class SubArraySum extends RecursiveTask<Integer> {
private final int[] array;
private final int start;
private final int end;
public SubArraySum(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (start == end) {
return array[start];
}
if (start > end) {
return 0;
}
int mid = (start + end) / 2;
SubArraySum leftArrayTask = new SubArraySum(array, start, mid - 1);
SubArraySum rightArrayTask = new SubArraySum(array, mid + 1, end);
leftArrayTask.fork();
rightArrayTask.fork();
System.out.println(Thread.currentThread().getName() + ", " + start + ", " + end);
return leftArrayTask.join() + array[mid] + rightArrayTask.join();
}
}
最大子數組和
class MaxSubarraySum {
public static void main(String[] args) {
System.out.println(new MaxSubarraySum().maxSubArray(new int[]{-2, 1, -3, 4, -1, 2, 1, -5, 4}));
}
public int maxSubArray(int[] nums) {
return maxSubArray(nums, 0, nums.length - 1);
}
public int maxSubArray(int[] nums, int start, int end) {
if (start == end) {
return nums[end];
}
if (start > end) {
return 0;
}
if (start + 1 == end) {
return Math.max(Math.max(nums[start], nums[end]), nums[start] + nums[end]);
}
int mid = (start + end) / 2;
int leftMax = maxSubArray(nums, start, mid - 1);
int rightMax = maxSubArray(nums, mid + 1, end);
int midMax = getMidMax(nums, start, end, mid);
return Math.max(Math.max(leftMax, rightMax), midMax);
}
private int getMidMax(int[] nums, int start, int end, int mid) {
int leftSum = 0;
int leftMax = Integer.MIN_VALUE;
for (int i = mid - 1; i >= start; i--) {
leftSum += nums[i];
if (leftSum > leftMax) {
leftMax = leftSum;
}
}
int rightSum = 0;
int rightMax = Integer.MIN_VALUE;
for (int i = mid + 1; i <= end; i++) {
rightSum += nums[i];
if (rightSum > rightMax) {
rightMax = rightSum;
}
}
return Math.max(leftMax, 0) + nums[mid] + Math.max(rightMax, 0);
}
}
數組中出現次數超過一半的數字
class MajorityElement {
public static void main(String[] args) {
System.out.println(new MajorityElement().majorityElement(new int[]{4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 4, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5}));
}
public int majorityElement(int[] nums) {
return majorityElement(nums, 0, nums.length - 1);
}
public int majorityElement(int[] nums, int start, int end) {
if (start == end || start + 1 == end) {
return nums[start];
}
int mid = (start + end) / 2;
int left = majorityElement(nums, start, mid - 1);
int right = majorityElement(nums, mid, end);
if (left == right) {
return left;
}
// 左右兩側數組中出現次數最多的數字不相同
int leftCount = getCount(nums, start, mid - 1, left);
int rightCount = getCount(nums, mid, end, right);
if (leftCount > rightCount) {
return left;
}
return right;
}
public int getCount(int[] nums, int start, int end, int target) {
return (int) IntStream.range(start, end + 1).filter(i -> nums[i] == target).count();
}
}
WordCount
class WordCount {
// 統計一個文件中的每個單詞出現的數量
// 例如:I am a good boy a
// I -> 1
// am -> 1
// a -> 2
// ...
public static Map<String, Long> sumParallel(List<File> files) throws ExecutionException, InterruptedException {
ForkJoinPool pool = new ForkJoinPool();
return pool.submit(new CountWord(files)).get();
}
}
class CountWord extends RecursiveTask<Map<String, Long>> {
private final List<File> files;
public CountWord(List<File> files) {
this.files = files;
}
@Override
protected Map<String, Long> compute() {
if (files.isEmpty()) {
return Collections.emptyMap();
}
if (files.size() == 1) {
return count(files.get(0));
}
int mid = files.size() / 2;
CountWord left = new CountWord(files.subList(0, mid));
CountWord right = new CountWord(files.subList(mid, files.size()));
left.fork();
right.fork();
return Stream.of(left.join(), right.join())
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
Long::sum
));
}
// 統計一個文件的單詞
private Map<String, Long> count(File file) {
String str;
try {
str = new String(Files.readAllBytes(file.toPath()));
} catch (IOException e) {
throw new RuntimeException(e);
}
return Stream.of(str.split("http://s+")).collect(Collectors.groupingBy(word -> word, Collectors.counting()));
}
}
貪心
買賣股票的最佳時機 II
class Solution {
public int maxProfit(int[] prices) {
int profit = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] > prices[i - 1]) {
profit += (prices[i] - prices[i - 1]);
}
}
return profit;
}
}
無重疊區間
class EraseOverlapIntervals {
public static void main(String[] args) {
System.out.println(new EraseOverlapIntervals().eraseOverlapIntervals(new int[][]{new int[]{1, 100}, new int[]{11, 22}, new int[]{1, 11}, new int[]{2, 12}}));
}
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, Comparator.comparingInt(o -> o[1]));
int result = 0;
int[] current = intervals[0];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] < current[1]) {
// 重疊了
result += 1;
} else {
current = intervals[i];
}
}
return result;
}
}
回溯
八皇后問題
class SolveNQueens {
public static void main(String[] args) {
System.out.println(new SolveNQueens().solveNQueens(8).size());
}
public List<List<String>> solveNQueens(int n) {
return solveNQueens(n, n);
}
// 解決N皇后問題的第x層
// 在解決第x層之前,假設第x-1層已經得到的解決
public List<List<String>> solveNQueens(int n, int x) {
if (x == 1) {
// 正在棋盤的第1行擺放皇后
List<List<String>> solutionOfFirstRow = new ArrayList<>();
for (int i = 1; i <= n; i++) {
solutionOfFirstRow.add(Collections.singletonList(queenAt(i, n)));
}
return solutionOfFirstRow;
}
// 每個元素代表一個解
// 其中的每個String都是N長度的
// 例如:現在正在解決4皇后問題的第3層,那么假設第2層已經得到了解決
// [
// [".Q.."]
// ["...Q"]
// ]
List<List<String>> resultOfXMinus1 = solveNQueens(n, x - 1);
List<List<String>> result = new ArrayList<>();
for (List<String> solutionOfXMinus1 : resultOfXMinus1) {
// 對于上一行的每個可行的解,擺放N次棋子,檢查可行性,若可行,將其加入結果集中
for (int i = 1; i <= n; i++) {
// 第x層有n種可以擺放的方案
if (isValid(solutionOfXMinus1, i, x)) {
List<String> solution = new ArrayList<>(solutionOfXMinus1);
solution.add(queenAt(i, n));
result.add(solution);
}
}
}
return result;
}
// 若把皇后擺放在第x行第i列的位置,不和之前的解沖突,則返回 true,否則返回 false
// 所有的坐標都是從1開始的
private boolean isValid(List<String> solutionOfXMinus1, int i, int x) {
for (int rowIndex = 0; rowIndex < solutionOfXMinus1.size(); rowIndex++) {
// 例如: "...Q"
String row = solutionOfXMinus1.get(rowIndex);
int queenRowIndex = rowIndex + 1;
int queenColIndex = row.indexOf("Q") + 1;
// 在同一列
if (queenColIndex == i) {
return false;
}
// 在對角線
if (((queenRowIndex + queenColIndex) == (x + i)) || ((queenRowIndex - queenColIndex) == (x - i))) {
return false;
}
}
return true;
}
// 創造一個長度為n的字符串,其中第i列是Q
private String queenAt(int i, int n) {
StringBuilder item = new StringBuilder();
for (int j = 1; j <= n; j++) {
item.append(i == j ? "Q" : ".");
}
return item.toString();
}
}
應用
計算匹配的括號
public class ParenthesisMatching {
// [] => valid
// {[]} => valid
// [(]) => invalid
public static void main(String[] args) {
System.out.println(isValid("[]"));
System.out.println(isValid("{[]}"));
System.out.println(isValid("{[]}()"));
System.out.println(isValid("{[]}([)]"));
}
public static boolean isValid(String string) {
Map<Character, Character> bracketMap = new HashMap<>();
bracketMap.put('[', ']');
bracketMap.put('(', ')');
bracketMap.put('{', '}');
if (string == null || string.isEmpty()) {
return true;
}
Deque<Character> stack = new ArrayDeque<>();
char[] chars = string.toCharArray();
for (char ch : chars) {
if ("[{(".indexOf(ch) >= 0) {
stack.push(ch);
continue;
}
Character head = stack.peek();
if ("]})".indexOf(ch) >= 0) {
if (head != null && ch == bracketMap.get(head)) {
stack.pop();
continue;
} else {
return false;
}
}
throw new IllegalArgumentException("參數非法:" + string);
}
return stack.isEmpty();
}
}
知識點
- TreeSet 的復雜度 == O(log(n)) => ArrayList -> TreeSet => 把算法復雜度從線性時間下降到對數時間
- 機械硬盤轉速單位 RPM(Revolution(s) Per Minute),每分鐘轉速
- 哈希桶 | 哈希表的缺陷 => 在有限的容量中存放無限多可能的對象 => 一定存在若干個對象的哈希值相同,需要放置在同一個位置上 => 使用鏈表串起來
- 學院派經典哈希表的實現
- 數組來存儲哈希表中的元素
- 鏈表處理碰撞
-
()
=> parentheses -
[]
=> square bracket -
{}
=> curly bracket
-
- 二叉樹可以退化至鏈表,此時時間復雜度由O(log(n)) 變為 O(n) =>
1024個數據,平衡二叉樹 O(log(n)) 1024 -> 10, 鏈表O(n) 1024 -> 1024 - 算法題 =>
- 溝通,理解題意
- 給出暴力解(不動腦子)
- 分析問題,溝通
- 給出正常的算法解 => 優化(剪枝)