主成分个数 - 快排中partition的深入理解

算法课课后习题对深化理解某一算法确实很有帮助.. 这一次课程学习了快速排序,每一次排序都涉及一个partition操作,也就是把数组分为比pivot大的部分,和比pivot小的部分。

这个题目是在线性时间内找到某一长N的数组中出现次数超过某一比例,如N/3的全部元素。

https://leetcode.com/problems/majority-element-ii/

Given an integer array of size  n , find all elements that appear more than  ⌊ n/3 ⌋  times. The algorithm should run in linear time and in O(1) space.
起初一看这和partition有什么关系?确实,如果坚定这一信念会得到一个很简单的基于moore投票的算法,稳定简单(两次遍历即可):

import java.util.List;
import java.util.ArrayList;

public class MajorityNumber2 {
    public List majorityElement(int[] nums) {
        int n = nums.length;
        Integer n1 = null, n2 = null;
        int c1 = 0, c2 = 0;

        for (int id = 0; id < n; id++) {
            if(n1 != null && nums[id] == n1.intValue()) c1++;
            else if (n2 != null && nums[id] == n2.intValue()) c2++;
            else if (c1 == 0) {
                n1 = nums[id];
                c1 = 1;
            }
            else if (c2 == 0) {
                n2 = nums[id];
                c2 = 1;
            }
            else {
                c1--;
                c2--;
            }
        }
        c1 = c2 = 0;
        for (int id = 0; id < n; id++) {
            if (nums[id] == n1.intValue()) c1++;
            else if (nums[id] == n2.intValue()) c2++;
        }
        List rst = new ArrayList<>();
        if (c1 > n/3) rst.add(n1);
        if (c2 > n/3) rst.add(n2);
        return rst;
    }

}

但是,其实这个问题还有一种思考方法。对于某一个元素,如果超过一定比例出现,比如说一半,那它在排好序的数组中一定会处在中间点上。

类似地,超过三分之一,那一定会出现在某一个三等分点上...

那么,类似于moore投票中找candidate的方法,我可以直接用partition找到排名在某等分点上的数,从而把所有的candidate找出来,然后再遍历一次确认这些candidate中有哪些是真的主成分。partition耗时最差是线性,总共来说时间同样是线性的

import java.util.List;
import java.util.ArrayList;
public class Solution {
    public List majorityElement(int[] nums) {
        int n = nums.length;
        List rst = new ArrayList<>();
        if (n <= 1) {
            for (int i = 0; i < n; i++)
                rst.add(nums[i]);
            return rst;
        }
        if (n == 2) {
            rst.add(nums[0]);
            if (nums[0] != nums[1])
                rst.add(nums[1]);
            return rst;
        }
        int n3thc = 0; // the n/3-th counter
        int n2thc = 0; // the 2n/3-th counter
        int n3th = select(nums, n/3);
        int n2th = select(nums, 2*n/3);

        for (int i = 0; i < n; i++) {
            if (nums[i] == n3th) n3thc++;
            else if (nums[i] == n2th) n2thc++;
        }

        if (n3thc > n/3) rst.add(n3th);
        if (n2thc > n/3) rst.add(n2th);
        return rst;
    }

    private static void swap(int[] a, int p, int q) {
        int tmp = a[p];
        a[p] = a[q];
        a[q] = tmp;
    }

    private static int partition(int[] nums, int lo, int hi) { // descending partition
        int i = lo + 1, j = hi;
        for (;;) {
            while (nums[lo] < nums[i] && i < j) i++;
            while (nums[lo] > nums[j] && i < j) j--;
            if (i >= j) break;
            swap(nums, i, j);
        }
        swap(nums, lo, j);
        return j;
    }

    private static int select(int[] nums, int k) { // find the k-th largest in nums
        int lo = 0, hi = nums.length - 1;
        while (lo < hi) {
            int j = partition(nums, lo, hi);
            if (j < k) lo = j + 1;
            else if (j > k) hi = j - 1;
            else return nums[k];
        }
        return nums[k];
    }
}



你可能感兴趣的:(课程学习报告,OJ解题报告,Java)