0
点赞
收藏
分享

微信扫一扫

剑指Offer6_堆、快排_最小的k个数


一、题目描述

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

剑指Offer6_堆、快排_最小的k个数_数据结构

二、解答

1.直接调API

直接向面试官展示调API的熟练度…

剑指Offer6_堆、快排_最小的k个数_堆_02

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        Arrays.sort(arr);
        int[] res = new int[k];
        for(int i = 0;i<k;i++){
            res[i] = arr[i];
        }
        return res;

    }
}

2.堆

剑指Offer6_堆、快排_最小的k个数_数据结构_03

分析

  • 使用大根堆 维护当前最小的 k 个数,此时堆顶是当前k个数最大的,接下来 数组剩下的元素 需要和 当前堆顶元素比较即可
  • 大的话,直接丢弃
  • 小的话,插入堆的最底层最后位置(保证是完全二叉树),然后从底向上进行堆化,即理解为末尾总是最小的值。

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        int[] vec = new int[k];
        if (k == 0) { // 排除 0 的情况
            return vec;
        }
        // 优先队列实现堆,传入 Comparator 可以决定是大根堆,小根堆
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
            public int compare(Integer num1, Integer num2) {
                return num2 - num1;
            }
        });
        // 先对数组前k的元素建堆
        for (int i = 0; i < k; ++i) {
            queue.offer(arr[i]);
        }
        // 出现比 大根堆 的 堆顶 元素更小的值,需要插入到 优先队列尾,重新进行堆化
        for (int i = k; i < arr.length; ++i) {
            if (queue.peek() > arr[i]) {
                queue.poll();
                queue.offer(arr[i]);
            }
        }
        // 结果
        for (int i = 0; i < k; ++i) {
            vec[i] = queue.poll();
        }
        return vec;
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/zui-xiao-de-kge-shu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3.快排

分析

  • 关于快排可以看这个视频:https://www.bilibili.com/video/BV1at411T75o?from=search&seid=5725303854576012858&spm_id_from=333.337.0.0
  • 快排关于中间轴的划分可以是随机的,也可以采用默认的最左边 作为随机轴,右边值同理

随机选取随机值作为轴

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
    	// 快排
        randomizedSelected(arr, 0, arr.length - 1, k);
        int[] vec = new int[k];
        for (int i = 0; i < k; ++i) {
            vec[i] = arr[i];
        }
        return vec;
    }

    private void randomizedSelected(int[] arr, int l, int r, int k) {
        if (l >= r) {
            return;
        }
        // 随机划分
        int pos = randomizedPartition(arr, l, r);
        int num = pos - l + 1;
        // 最后一次快排的中间轴 左边的元素正是结果
        
        if (k == num) {
            return;
        } else if (k < num) {
        	
            randomizedSelected(arr, l, pos - 1, k);
        } else {
            randomizedSelected(arr, pos + 1, r, k - num);
        }
    }

    // 基于随机的划分
    private int randomizedPartition(int[] nums, int l, int r) {
    	// 随机选中间值,选完之后跟 右边界 交换
        int i = new Random().nextInt(r - l + 1) + l;
        swap(nums, r, i);
        // 快排
        return partition(nums, l, r);
    }

	// 快排
    private int partition(int[] nums, int l, int r) {
        int pivot = nums[r];
        int i = l - 1;
        for (int j = l; j <= r - 1; ++j) {
            if (nums[j] <= pivot) {
                i = i + 1;
                swap(nums, i, j);
            }
        }
        swap(nums, i + 1, r);
        return i + 1;
    }

	// 交换
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/zui-xiao-de-kge-shu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

或者 默认选择 快排区间的最左边值作为中心轴

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        // 最后一个参数表示我们要找的是下标为k-1的数
        return quickSearch(arr, 0, arr.length - 1, k - 1);
    }

    private int[] quickSearch(int[] nums, int lo, int hi, int k) {
        // 每快排切分1次,找到排序后下标为j的元素,如果j恰好等于k就返回j以及j左边所有的数;
        int j = partition(nums, lo, hi);
        // 经过若干次 刚好 中间值为 k
        if (j == k) {
            return Arrays.copyOf(nums, j + 1);
        }
        // 否则根据下标j与k的大小关系来决定继续切分左段还是右段。
        return j > k? quickSearch(nums, lo, j - 1, k): quickSearch(nums, j + 1, hi, k);
    }

    // 快排切分,返回下标j,使得比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边。
    private int partition(int[] nums, int lo, int hi) {
    	// 默认选第一个元素为 中间值
        int v = nums[lo];
        int i = lo, j = hi + 1;
        while (true) {
        	// 左边小的直接跳
            while (++i <= hi && nums[i] < v);
            // 右边大的直接跳
            while (--j >= lo && nums[j] > v);
            // 中间轴现身
            if (i >= j) {
                break;
            }
            // 小的出现在右边,需要扔到左边
            // 大的出现在左边,需要扔到右边
            int t = nums[j];
            nums[j] = nums[i];
            nums[i] = t;
        }
        
        nums[lo] = nums[j];
        // 中间值移到中间
        nums[j] = v;
        return j;
    }
}

或者 默认选择快排区间 的最右边界值作为 中间轴

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if(k == 0) return new int[k];

        find(arr, 0, arr.length-1, k);
        int result[] = new int[k];
        for(int i = 0; i < k; i++) {
            result[i] = arr[i];
        }
        return result;
    }

    public void find(int[] arr, int l, int r, int k) {
    	// 快排中间轴
        int pos = partition(arr, l, r);
        if(pos+1 == k) return;
        if(pos+1 > k) {
            find(arr, l, pos-1, k);
        } else {
            find(arr, pos+1, r, k);
        }
    }

    public int partition(int[] arr, int l, int r) {
    	
        int p = arr[r];
        int i = l-1;
        for(int j = l; j < r; j++) {
        	// 右边的出现小值,需要换到左边
            if(arr[j] <= p) {
            	// 左边界
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i+1, r);

        return i+1;
    }

    public void swap(int[] arr, int a, int b) {
        int t = arr[a];
        arr[a] = arr[b];
        arr[b] = t;
    }
}


举报

相关推荐

0 条评论