LeetCode刷题之排序

280 阅读3分钟

215. 数组中的第K个最大元素(Medium)

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4]k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6]k = 4
输出: 4

说明:

  • 你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

方法一:利用自带的排序算法

直接利用java自带的排序算法进行排序,然后返回数组中第k-1个元素即可

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

方法二:优先级队列

使用优先级队列,本质上和使用java自带的排序算法相同

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

方法三:用快速选择来找到第k大的元素,快速选择是快速排序的变形,不同的是不用递归进左右两部分数组,只需要进其中一个就行,这样就有点类似于二分法查找元素了

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int left = 0;
        int right = nums.length - 1;
        //二分法的思想
        while(true){
            int pos = partition(nums, left, right);
            if(pos == k - 1){
                return nums[pos];
            }
            if(pos > k - 1){
                right = pos - 1;
            }else{
                left = pos + 1;
            }
        }
    }
    
    //partition过程
    private int partition(int[] nums, int left, int right){
        int pivot = nums[left]; //中轴值
        int l = left + 1; //左边数组终点
        int r = right; //右边数组起点
        while(l <= r){
            if(nums[l] < pivot && nums[r] > pivot){
                //数组从大到小排列
                swap(nums, l++, r--);
            }
            if(nums[l] >= pivot){
                ++l;
            }
            if(nums[r] <= pivot){
                --r;
            }
        }
        swap(nums, left, r); //讲left上的元素换到r上,就可以得到pivot左边元素大,右边小
        return r;
    }
    
    //交换
    private void swap(int[] nums, int l, int r){
        int tmp = nums[l];
        nums[l] = nums[r];
        nums[r] = tmp;
    }
}

347. Top K Frequent Elements(出现频率最多的k个元素)

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:
输入: nums = [1], k = 1
输出: [1]

说明:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。

解法一:小根堆

先使用HashMap建立起数字与次数的对应关系,然后使用堆排序,建立一个大小为k的小根堆,然后将元素添加进去,然后再 pop 出来

class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        HashMap<Integer,Integer> map = new HashMap();
        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
        }
        // 遍历map,用最小堆保存频率最大的k个元素
        PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                return map.get(a) - map.get(b);
            }
        });
        for (Integer key : map.keySet()) {
            if (pq.size() < k) {
                pq.add(key);
            } else if (map.get(key) > map.get(pq.peek())) {
                pq.remove();
                pq.add(key);
            }
        }
        // 取出最小堆中的元素
        List<Integer> res = new ArrayList<>();
        while (!pq.isEmpty()) {
            res.add(pq.remove());
        }
        return res;
    }
}

解法二:桶排序

先使用 HashMap 建立起数字与次数的对应关系,然后使用桶排序,

public class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
        }
        //桶排序,频率作为数组的下标,对于出现频率相同的数字,存入同个下标
        List<Integer>[] bucket = new List[nums.length + 1];
        for (Integer key : map.keySet()) {
            int i = map.get(key); //获取数字出现的频率作为下标
            if (bucket[i] == null){
                bucket[i] = new ArrayList<Integer>();
            }
            bucket[i].add(key);
        }

        List<Integer> result = new ArrayList<Integer>();
        //倒序遍历数组获取k个数
        for (int i = bucket.length - 1; i >= 0 && result.size() < k; i--) {
            if (bucket[i] == null){
                continue;
            }
            result.addAll(bucket[i]);
        }
        return result;
    }
}

451. 根据字符出现频率排序(Medium)

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:

输入:
"tree"

输出:
"eert"

解释:
'e'出现两次,'r''t'都只出现一次。
因此'e'必须出现在'r''t'之前。此外,"eetr"也是一个有效的答案。

示例 2:

输入:
"cccaaa"

输出:
"cccaaa"

解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:
输入:
"Aabb"

输出:
"bbAa"

解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。

解法一:堆排序

题意是给定一个字符串,按照字符出现次数递减排序,跟上题思路类似,可以使用堆排序

class Solution {
    public String frequencySort(String s) {
        //自定义一个Item类
        class Item{

            public Item(Character character, Integer count) {
                this.character = character;
                this.count = count;
            }

            private Character character;
            private int count;
        }
        
        //使用优先级队列存储,自定义比较器
        Map<Character,Integer> map = new HashMap<>();
        PriorityQueue<Item> priorityQueue = new PriorityQueue<>((item1,item2)->item2.count-item1.count);

        for (int i=0;i<s.length();i++){
            char c = s.charAt(i);
            map.put(c,map.get(c)==null?1:map.get(c)+1);
        }
        
        map.forEach((k,v)->{
            final Item item = new Item(k, v);
            priorityQueue.offer(item);
        });
        StringBuilder stringBuilder = new StringBuilder();
        while(!priorityQueue.isEmpty()){
            final Item item = priorityQueue.poll();
            for (int i = 0; i < item.count; i++) {
                stringBuilder.append(item.character);
            }
        }
        return stringBuilder.toString();

    }
}

解法二:桶排序

使用桶排序,跟上题相同,先用HashMap统计好每个字符出现的次数,然后用一个数组存储,最后从后往前遍历存储进StringBuilder

public static String frequencySort(String s) {
        HashMap<Character, Integer> map = new HashMap<Character, Integer>();
        for (int i = 0; i < s.length(); i++) {
            map.put(s.charAt(i), map.getOrDefault(s.charAt(i), 0) + 1);
        }

        List<Character>[] chs = new List[s.length() + 1];
        for (Character c : map.keySet()) {
            int fre = map.get(c);
            if (chs[fre] == null){
                chs[fre] = new ArrayList<Character>();
            }
            chs[fre].add(c);
        }

        StringBuilder sb = new StringBuilder();
        List<Character> result = new ArrayList<Character>();
        for (int i = chs.length - 1; i >= 0; i--) {
            if (chs[i] == null){
                continue;
            }
            for (int j = 0; j < chs[i].size(); j++) {
                for (int l = 0; l < i; l++) {
                    sb.append(chs[i].get(j));
                }
            }
        }
        return sb.toString();
    }

75. 颜色分类(Medium)

给定一个包含红色、白色和蓝色,一共 n个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

注意:
不能使用代码库中的排序函数来解决这道题。

示例:  
输入: [2,0,2,1,1,0]  
输出: [0,0,1,1,2,2]

解法一:由于题目规定的颜色只有三种,用“0,1,2”表示,那么可以用扫描两遍数组,第一遍对颜色计数,第二遍按照颜色个数重写数组。

解法二:归并排序

该题属于典型的荷兰国旗问题,可以使用归并排序的思路,将一个数组分为三块,左边的是‘0’,中间的是‘1’,右边的是‘2’,设好2个指针zero=-1和two=nums.length,从前往后扫描,遇到0就与++zero交换,遇到1就i++,遇到2余--two交换。一遍之后就排序好了。

class Solution {
    //for循环版本
    public void sortColors(int[] nums) {
        int zero = -1;
        int two = nums.length;
        for(int i = 0; i < nums.length; i++){
            if(nums[i] == 0){
                swap(nums, ++zero, i);
            }else if(nums[i] == 2){
                swap(nums, --two, i--);
            }
        }
    }
    
    //while循环版本
    public void sortColors(int[] nums) {
        int zero = -1;
        int two = nums.length;
        int cur = 0;
        while(cur < two){
            if(nums[cur] == 0){
                swap(nums, ++zero, cur++);
            }else if(nums[cur] == 2){
                swap(nums, --two, cur);
            }else{
                cur++;
            }
        }
    }
    
    private void swap(int[] nums, int i, int j) {
        int t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }
}
}