菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
472
0

排序

原创
05/13 14:22
阅读数 92095

排序算法:

常用的排序算法有:快排、归并、堆排序。

该中题型只要是利用排序来解题。

Kth Element问题,也就是第K个元素的问题。

快速选择

用于求解 Kth Element 问题,也就是第 K 个元素的问题。

可以使用快速排序的 partition() 进行实现。需要先打乱数组,否则最坏情况下时间复杂度为 O(N2)。

用于求解 TopK Elements 问题,也就是 K 个最小元素的问题。可以维护一个大小为 K 的最小堆,最小堆中的元素就是最小元素。最小堆需要使用大顶堆来实现,大顶堆表示堆顶元素是堆中最大元素。这是因为我们要得到 k 个最小的元素,因此当遍历到一个新的元素时,需要知道这个新元素是否比堆中最大的元素更小,更小的话就把堆中最大元素去除,并将新元素添加到堆中。所以我们需要很容易得到最大元素并移除最大元素,大顶堆就能很好满足这个要求。

堆也可以用于求解 Kth Element 问题,得到了大小为 k 的最小堆之后,因为使用了大顶堆来实现,因此堆顶元素就是第 k 大的元素。

快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。

可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。

1.Kth Element

215. Kth Largest Element in an Array (Medium)

Input: [3,2,1,5,6,4] and k = 2
Output: 5

题目描述:找到倒数第 k 个的元素。

(1)排序 :时间复杂度 O(NlogN),空间复杂度 O(1)

public int findKthLargest(int[] nums, int k) {
    Arrays.sort(nums);
    return nums[nums.length - k];
}

(2)堆 :时间复杂度 O(NlogK),空间复杂度 O(K)。

思路:

小顶堆

使用小顶堆存放元素,堆顶是所有元素中最小元素,那么当堆中超过了K个元素,则将堆顶元素删除,一直循环到所有元素遍历一遍,且保持小顶堆元素个数始终为K个元素,则堆顶元素即为所求元素。

大顶堆

使用大顶堆存放元素,堆定是序列中最大元素,那么当堆内元素超过了n-k个元素,则将堆定删除,一直循环到遍历完所有元素,且保持大顶堆中元素个数始终为n-k个,则堆顶元素即为所求元素。
public int findKthLargest(int[] nums, int k) {
    PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小顶堆
    for (int val : nums) {
        pq.add(val);
        if (pq.size() > k)  // 维护堆的大小为 K
            pq.poll();
    }
    return pq.peek();
}

(3)快速选择 :时间复杂度 O(N),空间复杂度 O(1)

public int findKthLargest(int[] nums, int k) {
    k = nums.length - k;
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int j = partition(nums, l, h);
        if (j == k) {
            break;
        } else if (j < k) {
            l = j + 1;
        } else {
            h = j - 1;
        }
    }
    return nums[k];
}

private int partition(int[] a, int l, int h) {
    int i = l, j = h + 1;
    while (true) {
        while (a[++i] < a[l] && i < h) ;
        while (a[--j] > a[l] && j > l) ;
        if (i >= j) {
            break;
        }
        swap(a, i, j);
    }
    swap(a, l, j);
    return j;
}

private void swap(int[] a, int i, int j) {
    int t = a[i];
    a[i] = a[j];
    a[j] = t;
}

桶排序

1. 出现频率最多的 k 个元素

347. Top K Frequent Elements (Medium)

Given [1,1,1,2,2,3] and k = 2, return [1,2].

思路:

使用map存放,元素和元素对应个数,使用小顶堆:
小顶堆中存放的是map的键,但是在建立小顶堆的时,使用键对应map的值进行建堆。还可以这样玩儿 骚操作。
这就是所谓的桶排序,即根据map中的值来对键进行排序,本题使用相对简单的方法,常见的map根据值排序,相对复杂。

代码实现:

public List<Integer> topKFrequent(int[] nums, int k) {

        Map<Integer,Integer> map=new HashMap<>();
        for (int num : nums) {
            map.put(num,map.getOrDefault(num,0)+1);
        }

        PriorityQueue<Integer> heap =
                new PriorityQueue<Integer>((n1, n2) -> map.get(n1) - map.get(n2));

        //进小顶堆,并保持小顶堆始终含有k个元素,若超过k个元素,则删除队首元素,则这k个元素是map的键中前k大的元素
        for (Integer integer : map.keySet()) {
            heap.add(integer);
            if(heap.size()>k){
                heap.remove();
            }
        }

        List<Integer> list=new ArrayList<>();
        //当小顶堆不空时候,将数据输出到list
        while (!heap.isEmpty()){
             list.add(heap.remove());
        }
        Collections.reverse(list);
        return list;
    }

2.按照字符出现次数对字符串排序

451. Sort Characters By Frequency (Medium)

Input:
"tree"

Output:
"eert"

Explanation:
'e' appears twice while 'r' and 't' both appear once.
So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also a valid answer.

思路:

使用map统计出字符和对应出现频次,key为字符,value为值,
使用优先队列来根据value的值来对key进行构建大顶堆,
大顶堆出队,并根据出现的频次来加入StringBuilder,
最后返回sb.toString()

代码实现:

public static String frequencySort2(String s) {

        Map<Character, Integer> map = new HashMap<>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            map.put( c, map.getOrDefault(c, 0) + 1);
        }
        PriorityQueue<Character> pq = new PriorityQueue(((o1, o2) -> map.get(o2) - map.get(o1)));
        for (Character character : map.keySet()) {
            pq.add(character);
        }
        //使用StringBuilder来进行添加字符,更加高效
        StringBuilder sb=new StringBuilder();
        while (!pq.isEmpty()) {
            Character key = pq.remove();
            for (Integer i = 0; i < map.get(key); i++) {
                sb.append(key);
            }

        }
        return sb.toString();
    }

荷兰国旗问题

荷兰国旗包含三种颜色:红、白、蓝。

有三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。

1.按颜色进行排序

75. Sort Colors (Medium)

思路:

本问题被称为 荷兰国旗问题
,最初由 Edsger W. Dijkstra提出。
其主要思想是给每个数字设定一种颜色,并按照荷兰国旗颜色的顺序进行调整。

我们用三个指针(p0, p2 和curr)来分别追踪0的最右边界,2的最左边界和当前考虑的元素。
本解法的思路是沿着数组移动 curr 指针,若nums[curr] = 0,则将其与 nums[p0]互换;若 nums[curr] = 2 ,则与 nums[p2]互换。

流程:

初始化0的最优边界: p0=0
初始化2的最左边界: p2=n-1
初始化当前考虑的元素符号: cuur=0
while cuur <= p2
	若 nums[cuur]=0 :交换第 curr个和第 p0个元素,并将指针都向右移。
	若 nums[cuur]=2 :交换第 curr个和第p2个元素,并肩p2指针左移。
	若 nums[cree]=1 :将指针 curr右移

代码实现:

/**
* 荷兰国旗问题
* @param nums
*/
public void sortColors(int[] nums) {

    int curr=0,p0=0,p2=nums.length-1;

    while (curr<=p2){
        if (nums[curr]==0){
            //此处必须是curr++ p0++ ,curr>=p0 ,
            //将两坐标位置元素交换后,此时curr指向的位置元素不需排序,
            //因为是已经排过序的,即对于p0都是已经排过序的,     
            //可以画图模拟
            swap(nums,curr++,p0++);
        }else if (nums[curr]==2){
            //此处必须是curr不变,p2--,curr<=p0,
            //将两坐标元素交换后此时,此时curr指向的元素是未曾被排序的,
            //所以还需要继续排序,可以画图模拟
            swap(nums,curr,p2--);
        }else {
            curr++;
        }
    }
}
/**
* 交换位置
* @param nums
* @param i
* @param j
*/
void swap(int nums[],int i,int j){
    int tem=nums[i];
    nums[i]=nums[j];
    nums[j]=tem;
}

发表评论

0/200
472 点赞
0 评论
收藏
为你推荐 换一批