算法與數據結構

概述

程序 = 算法 + 數據結構

  • 算法是計算機科學的本質,是計算機世界的基石。算法決定了程序如何運行
  • 數據結構決定了程序的數據如何被存儲

算法的復雜度

時間復雜度

定性描述算法的運行時間

  • O(1) => 常數時間 => 運行時間與問題的規模無關 => 哈希桶 | 數組隨機尋址 => 數組(Array) | 線性表(Linear Table)
  • O(n) => 線性時間 => 運行時間與問題的規模成正比 => 遍歷 | 求數組、鏈表的最大值
  • O(log(n)) => 對數時間 => 每次操作其所需要的額外計算時間會變小 => 二分查找 | 二叉樹
  • O(n*log(n)) => 基于比較的排序算法的下界
  • O(n^2) => 解決問題的復雜度和問題規模的平方成正比 => 冒泡排序
  1. 時間復雜度計算時忽略常數 => O(n) == O(2n)
  2. 時間復雜度的計算中,高階復雜度會吞并低階復雜度 => O(n^2) + O(n) == O(n^2) => 對數組進行排序后遍歷,復雜度是多少? => O(n*log(n)) + O(n) == O(n*log(n))
  • 最好時間復雜度
  • 最壞時間復雜度
  • 平均時間復雜度

線性時間復雜度

  • 求數組、鏈表的最大值 | 和
  • 尋找數組中的重復元素
  • 判斷鏈表是否存在環(快慢指針)
  • 求階乘
  • 合并兩個鏈表
  • 翻轉鏈表

對數時間復雜度

二叉樹

遍歷二叉樹
  1. 深度優先遍歷 DFS(Depth First Search) => 遞歸
    • 前序遍歷(根節點 => 左節點 => 右節點)
    • 中序遍歷(左節點 => 根節點 => 右節點)
    • 后序遍歷(左節點 => 右節點 => 根節點)
  2. 廣度優先遍歷 BFS(Breadth First Search) => 經典的廣度優先遍歷算法 => 隊列(先進先出 FIFO)

遞歸

  1. 將大問題分解成小問題
  2. 假設小問題已經解決
  3. 對分解的小問題進行求解

空間復雜度

解決問題所需要的輔助空間的大小

  • 常數空間復雜度 => 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))

二分查找

應用于有序數組的快速算法

  1. 非遞歸 => 雙指針
    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.
    } 
    
  2. 遞歸
    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
平衡二叉樹很難做成線程安全的,在旋轉、修改的過程中多個線程并發訪問會有問題

二叉樹前序遍歷(根節點 => 左節點 => 右節點)

  1. 遞歸
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;
    }
}
  1. 非遞歸
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;
    }
}

二叉樹中序遍歷(左節點 => 根節點 => 右節點)

  1. 遞歸
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;
    }
}
  1. 非遞歸
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;
    }
}

回溯

決策樹

八皇后問題

leetcode

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();
    }
}

知識點

  1. TreeSet 的復雜度 == O(log(n)) => ArrayList -> TreeSet => 把算法復雜度從線性時間下降到對數時間
  2. 機械硬盤轉速單位 RPM(Revolution(s) Per Minute),每分鐘轉速
  3. 哈希桶 | 哈希表的缺陷 => 在有限的容量中存放無限多可能的對象 => 一定存在若干個對象的哈希值相同,需要放置在同一個位置上 => 使用鏈表串起來
  4. 學院派經典哈希表的實現
    • 數組來存儲哈希表中的元素
    • 鏈表處理碰撞
    • () => parentheses
    • [] => square bracket
    • {} => curly bracket
  5. 二叉樹可以退化至鏈表,此時時間復雜度由O(log(n)) 變為 O(n) =>
    1024個數據,平衡二叉樹 O(log(n)) 1024 -> 10, 鏈表O(n) 1024 -> 1024
  6. 算法題 =>
    1. 溝通,理解題意
    2. 給出暴力解(不動腦子)
    3. 分析問題,溝通
    4. 給出正常的算法解 => 優化(剪枝)
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容