Algorithm Notes
  • Introduction
  • Search & Backtracking 搜索与回溯
    • Tree 与 BackTracking 的比较
    • Subsets, Combination 与 Permutation
    • Subsets & Combinations & Combination Sum
    • 枚举法
    • N 皇后 + 矩阵 Index Trick
    • Sudoku 数独 + 矩阵 Index Trick
    • Word Ladder I & II
    • Number of ways 类
    • DFS flood filling
    • Strobogrammatic 数生成
    • String 构造式 DFS + Backtracking
    • Word Pattern I & II
    • (G) Binary Watch
    • (FB) Phone Letter Combination
    • 常见搜索问题的迭代解法
  • String,字符串类
    • 多步翻转法
    • Substring 结构和遍历
    • Palindrome 问题
    • Palindrome Continued
    • String / LinkedList 大数运算
    • 序列化与压缩
    • 5/24 String 杂题
    • Knuth–Morris–Pratt 字符串匹配
    • Lempel–Ziv–Welch 字符串压缩算法
    • (G) Decode String
    • (G) UTF-8 Validation
  • Binary Tree,二叉树
    • 各种 Binary Tree 定义
    • LCA 类问题
    • 三序遍历,vertical order
    • Post order traversal 的应用
    • Min/Max/Balanced Depth
    • BST
    • 子树结构
    • Level Order traversal
    • Morris 遍历
    • 修改结构
    • 创建 / 序列化
    • 子树组合,BST query
    • 路径与路径和
    • NestedInteger 类
    • (FB) 从 Binary Tree Path 看如何递归转迭代
    • (FB) Binary Tree Path 比较路径大小
    • 比较好玩的 Binary Tree 概率题
  • Segment & Fenwick Tree,区间树
    • Segment Tree 基础操作
    • Segment Tree 的应用
    • Fenwick Tree (Binary Indexed Tree)
    • Range Sum Query 2D - Immutable
  • Union-Find,并查集
    • Union-Find,并查集基础
    • Union-Find, 并查集应用
  • Dynamic Programming, 动态规划
    • 6/20, 入门 House Robber
    • 7/12, Paint Fence / House
    • 6/24, 滚动数组
    • 6/24, 记忆化搜索
    • 6/24, 博弈类 DP
    • 博弈类DP, Flip Game
    • 6/25, 区间类DP
    • 6/27, subarray 划分类,股票
    • 7/2, 字符串类
    • Bomb Enemies
    • 8/2,背包问题
    • (G) Max Vacation
    • (11/4新增) AST 子树结构 DP
  • LinkedList,链表
    • 6/9, LinkedList,反转与删除
    • 6/11, LinkedList 杂题
    • (FB) 链表的递归与倒序打印
  • LinkedIn 面经,算法题
    • 6/17, LinkedIn 面经题
    • 6/28, LinkedIn 面经题
    • 7/6, LinkedIn 面经
    • Shortest Word Distance 类
    • DFA Parse Integer
  • Two Pointers,双指针
    • 3 Sum, 3 Sum Closest / Smaller, 4 Sum
    • 对撞型,灌水类
    • 对撞型,partition类
    • Wiggle Sort I & II
    • 双指针,窗口类
    • 双指针,窗口类
    • Heap,排序 matrix 中的 two pointers
  • Bit & Math,位运算与数学
    • Bit Manipulation,对于 '1' 位的操作
    • Math & Bit Manipulation, Power of X
    • 坐标系 & 数值计算类
    • Add Digits
    • 用 int 做字符串 signature
  • Interval 与 扫描线
    • Range Addition & LCS
    • 7/5, Interval 类,扫描线
  • Trie,字典树
    • 6/9, Trie, 字典树
  • 单调栈,LIS
    • 4/13 LIS
    • 栈, 单调栈
    • Largest Divisible Subset
  • Binary Search 类
    • Matrix Binary Search
    • Array Binary Search
    • Find Peak Element I & II
    • **Median of Two Sorted Arrays
  • Graph & Topological Sort,图 & 拓扑排序
    • 有向 / 无向 图的基本性质和操作
    • 拓扑排序, DFS 做法
    • 拓扑排序, BFS 做法
    • Course Schedule I & II
    • Alien Dictionary
    • Undirected Graph, BFS
    • Undirected Graph, DFS
    • 矩阵,BFS 最短距离探索
    • 欧拉回路,Hierholzer算法
    • AI, 迷宫生成
    • AI, 迷宫寻路算法
    • (G) Deep Copy 无向图成有向图
  • 括号与数学表达式的计算
  • Iterator 类
  • Majority Element,Moore's Voting
  • Matrix Inplace Operations
  • 常见数据结构设计
  • (G) Design / OOD 类算法题
  • 随机算法 & 数据结构
  • (FB) I/O Buffer
  • (FB) Simplify Path, H-Index I & II
  • (FB) Excel Sheet, Remove Duplicates
  • Integer 的构造,操作,序列化
  • Frequency 类问题
  • Missing Number 类,元素交换,数组环形跳转
  • 8/10, Google Tag
  • (FB) Rearrange String k Distance Apart
  • Abstract Algebra
    • Chap1 -- Why Abstract Algebra ?
    • Chap2 -- Operations
    • Chap3 -- The Definition of Groups
Powered by GitBook
On this page
  • Kth Largest Element in an Array
  • Kth Smallest Element in a Sorted Matrix
  • Nuts & Bolts Problem
  • 总的来说;
  • Sort Colors
  • O(n) 时间复杂度
  • Sort Colors II
  • Sort Letters by Case

Was this helpful?

  1. Two Pointers,双指针

对撞型,partition类

Previous对撞型,灌水类NextWiggle Sort I & II

Last updated 4 years ago

Was this helpful?

  • partition 类双指针

这类问题我一向喜欢用 "<=" 作为循环和跳过元素的判断条件,然后用 swap. 和九章模板上的写法不太一样。

  • 需要规范一下自己写 array 问题的变量名用法

  • left, right 代表移动指针。

  • start, end 代表区间起始。

public class Solution {
    public int findKthLargest(int[] nums, int k) {
        return partition(nums, k, 0, nums.length - 1);
    }

    private int partition(int[] nums, int k, int start, int end){

        int pivot = nums[start];
        int left = start;
        int right = end;

        while(left <= right){
            while(left <= right && nums[left] >= pivot) left++;
            while(left <= right && nums[right] <= pivot) right--;
            if(left < right) swap(nums, left, right);
        }

        swap(nums, start, right);

        if(k == right + 1) return nums[right];
        if(k > right + 1){
            return partition(nums, k, right + 1, end);
        } else {
            return partition(nums, k, start, right - 1);
        }
    }

    private void swap(int[] nums, int a, int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}
  • 这题也可以用 3-way partitioning 的思路写,复杂度一样

public class Solution {
    public int findKthLargest(int[] nums, int k) {
        return partition(nums, k, 0, nums.length - 1);
    }

    private int partition(int[] nums, int k, int start, int end){

        int pivot = nums[start];
        int left = start;
        int right = end;
        int cur = start;

        while(cur <= right){
            if(nums[cur] > pivot){
                swap(nums, cur ++, left ++);
            } else if (nums[cur] < pivot){
                swap(nums, cur, right--);
            } else {
                cur ++;
            }
        }

        right = cur - 1;

        if(k == right + 1) return nums[right];
        if(k > right + 1){
            return partition(nums, k, right + 1, end);
        } else {
            return partition(nums, k, start, right - 1);
        }
    }

    private void swap(int[] nums, int a, int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}

Quick Select 在 2D 上的应用。

注意这题复杂度最优的解法是用 heap 做类似多路归并的。不过某种原因目前在 LC 上这种解法要比用 heap 的快一倍。。

public class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        if(matrix == null || matrix.length == 0) return -1;

        int rows = matrix.length;
        int cols = matrix[0].length;

        return kthHelper(matrix, 0, rows * cols - 1, k);
    }

    private int kthHelper(int[][] matrix, int start, int end, int k){
        int rows = matrix.length;
        int left = start;
        int right = end;
        int pivot = matrix[start / rows][start % rows];

        while(left <= right){
            while(left <= right && matrix[left / rows][left % rows] <= pivot) left++;
            while(left <= right && matrix[right / rows][right % rows] >= pivot) right--;

            if(left < right) swap(matrix, left, right);
        }
        swap(matrix, start, right);

        if(right + 1 == k){
            return matrix[right / rows][right % rows];
        } else if(right + 1 < k){
            return kthHelper(matrix, right + 1, end, k);
        } else {
            return kthHelper(matrix, start, right - 1, k);
        }
    }

    private void swap(int[][] matrix, int a, int b){
        int rows = matrix.length;

        int temp = matrix[a / rows][a % rows];
        matrix[a / rows][a % rows] = matrix[b / rows][b % rows];
        matrix[b / rows][b % rows] = temp;
    }
}

一道去年曾经卡了我两天的题目。到最后才发现是他们 test case 写错了。。。

这题和 quick select 的 partition 思路完全一致,但是细节不同。

  • 所有的不等式判断条件都没有 '=' 号。

  • 所有元素 1-1 对应,不会有多个元素等于 pivot.

  • pivot 元素来自外部,而不是内部已知位置。

如果 partition array 不带等于号的写,数组里有多个元素等于 pivot 就会挂了。

原因在于,在这题中,每次 partition 用的是外部元素,而且 array 里有且只有一个正确 pivot 元素与其对应,所以最终一定会 left == right 停留在 pivot 上。

这题如果像 partition 一样全带 '=' 去判断,其实也是可以正确把数组 partition 的,但是不同于 quick select 里面固定用 num[start] 做 pivot,这种写法里面我们不知道正确的 pivot 位置在哪。

假如按 left <= right,各种 cmp >= 0 <= 0 挪动

【2,1,|7|,8,3,9,|4|,5,6】,pivot = 5

【2,1,4,|8|,|3|,9,7,5,6】

【2,1,4,|3|,|8|,9,7,5,6】

总的来说;

  • 全带等号的比较方式,可以 partition,要手动找 pivot 位置做收尾 swap,适用于自己选好 pivot 的情况

  • 全不带等号的比较方式,如果 pivot 元素有唯一对应,可以 partition 并且 left == right 停留在 pivot 上;否则多个等于 pivot 时会死循环,因为不带等号无法让指针跨过 等于 pivot 的元素。

/**
 * public class NBCompare {
 *     public int cmp(String a, String b);
 * }
 * You can use compare.cmp(a, b) to compare nuts "a" and bolts "b",
 * if "a" is bigger than "b", it will return 1, else if they are equal,
 * it will return 0, else if "a" is smaller than "b", it will return -1.
 * When "a" is not a nut or "b" is not a bolt, it will return 2, which is not valid.
*/
public class Solution {
    /**
     * @param nuts: an array of integers
     * @param bolts: an array of integers
     * @param compare: a instance of Comparator
     * @return: nothing
     */
    public void sortNutsAndBolts(String[] nuts, String[] bolts, NBComparator compare) {
        // write your code here
        helper(nuts, bolts, compare, 0, nuts.length - 1);
    }

    private void helper(String[] nuts, String[] bolts, NBComparator compare, 
                        int start, int end){
        if(start >= end) return;
        int pivot = partitionNuts(nuts, bolts[start], start, end, compare);
        partitionBolts(bolts, nuts[pivot], start, end, compare);

        helper(nuts, bolts, compare, start, pivot - 1);
        helper(nuts, bolts, compare, pivot + 1, end);
    }

    private int partitionNuts(String[] nuts, String bolt, int start, int end,
                                 NBComparator compare){
        int left = start;
        int right = end;

        while(left < right){
            while(left < right && compare.cmp(nuts[left], bolt) < 0) left ++;
            while(left < right && compare.cmp(nuts[right], bolt) > 0) right --;
            if(left < right) swap(nuts, left, right);
        }

        // 最终 left = right ,都停留在 pivot 上
        return left;
    }

    private int partitionBolts(String[] bolts, String nut, int start, int end,
                                  NBComparator compare){
        int left = start;
        int right = end;

        while(left < right){
            while(left < right && compare.cmp(nut, bolts[left]) > 0) left ++;
            while(left < right && compare.cmp(nut, bolts[right]) < 0) right --;
            if(left < right) swap(bolts, left, right);
        }

        return right;
    }

    private void swap(String[] arr, int a, int b){
        String temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
};
  • 一张图说明 Dijkstra 的 3-way partitioning,左右指针维护 < key 和 > key 的元素,[left , cur - 1] 为 = key 的元素,[cur, right] 为未知元素。

  • 只有在和 right 换元素时,cur 指针的位置是不动的,因为下一轮还要看一下换过来的元素是不是 < key 要放到左边。

O(n) 时间复杂度

public class Solution {
    public void sortColors(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        int i = 0;

        while(i <= right){
            if(nums[i] < 1){
                swap(nums, left++, i++);
            } else if(nums[i] > 1){
                swap(nums, i, right--);
            } else {
                i ++;
            }
        }
    }

    private void swap(int[] nums, int a, int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}

3-way partition 的进阶版本,k-way partition.

  • 利用已知最大和最小 key,维护双指针对撞

  • 数组结构是

  • 【最小key】【cur + unknown】【最大key】

这种写法的复杂度分析稍微麻烦一些,最大为 O(n * k/2) 每次复杂度上限为O(n),最多进行 k / 2 次循环,然而要考虑每次迭代会固定去掉头尾两部分数组,导致 n 逐渐变小,实际的复杂度要根据 k 个数字的分部情况来看,k 的分布越偏向于【1,k】 的两侧,复杂度就越低。

class Solution {
    /**
     * @param colors: A list of integer
     * @param k: An integer
     * @return: nothing
     */
    public void sortColors2(int[] colors, int k) {
        // write your code here

        int left = 0;
        int right = colors.length - 1;
        int cur;
        int lowColor = 1;
        int highColor = k;
        while(lowColor < highColor){
            cur = left;
            while(cur <= right){
                if(colors[cur] == lowColor){
                    swap(colors, cur ++, left ++);
                } else if(colors[cur] == highColor){
                    swap(colors, cur, right --);
                } else {
                    cur ++;
                }
            }

            lowColor ++;
            highColor --;
        }
    }

    private void swap(int[] colors, int a, int b){
        int temp = colors[a];
        colors[a] = colors[b];
        colors[b] = temp;
    }

}

顺着 3-way partitioning 的思路,其实这种写法也很适合解决 2-way partitioning 的问题。

public class Solution {
    /** 
     *@param chars: The letter array you should sort by Case
     *@return: void
     */
    public void sortLetters(char[] chars) {
        //write your code here
        int left = 0;
        int right = chars.length - 1;
        int cur = 0;

        while(cur <= right){
            if(chars[cur] >= 'a'){
                swap(chars, cur ++, left ++);
            } else if(chars[cur] < 'a'){
                swap(chars, cur, right--);
            } else {
                cur ++;
            }
        }    

    }

    private void swap(char[] chars, int a, int b){
        char temp = chars[a];
        chars[a] = chars[b];
        chars[b] = temp;
        return;
    }
}

非常有名的算法,人称“荷兰旗算法” ,经典的 "3-way partitioning"算法,用于解决 quick sort 类排序算法中对于重复元素的健壮性问题,在原有 2-way partitioning 的基础上把所有 val == key 的元素集中于数组中间,实现【(小于),(等于),(大于)】的分区。

Kth Largest Element in an Array
Kth Smallest Element in a Sorted Matrix
Nuts & Bolts Problem
Sort Colors
Dutch national flag problem
Princeton 公开课的课件,讲 2-way 和 3-way partitioning
Sort Colors II
Sort Letters by Case