剑指offer——排序算法

(一)剑指Offer 40. 最小的k个数

在这里插入图片描述
基本思路:
1.这是经典的topK问题,最直接的想法就是对其进行排序,然后取出排序后的前k个数。那么问题就转化为了如何选择排序算法上。
2.由于这里只需要找到前k个数,我们在选择排序算法时,希望能够有一个排序算法,通过有限的轮数找到排序好的前k个数,这样后面的排序过程我们就不需要再进行了。
3.一个较好的方法是使用快排,快排的时间复杂度为O(nlogn)。我们首先选择一个数,进行一轮排序,此时小于此数的均在左侧,大于的均在右侧;若左侧的个数少于k个,则我们对右侧的任意值再进行一轮快排;若多于k个,则对左侧的任意值再进行一轮快排。

class Solution {
    public void quickSort(int[] arr, int left, int right,int k){
        if(left >= right){
            return ;
        }
        int l = left, r = right;//待排序的左右两端指针
        int base = arr[l];//找到的基准数字
        while(l < r){//当l=r时停止
            //先找到比base小的数
            while(arr[r] >= base && r > l){
                //注意:r>l,因为l的左侧都是比base小的值,这是应该的
                r--;
            }
            arr[l] = arr[r];//将其放置到base的位置
            //找到比base大的数,放在r的位置
            while(arr[l] <= base && l < r){
                l++;
            }
            arr[r] = arr[l];
        }
        arr[l] = base;
        if(l > k){
            quickSort(arr,left,l-1,k);
        }else if(l < k){
            quickSort(arr,l+1,right,k);
        }else if(l == k){
            return;
        }
    }
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0) {
            return new int[0];
        } else if (arr.length <= k) {
            return arr;
        }
        quickSort(arr,0,arr.length-1,k);
        int[] res = new int[k];
        for(int i = 0; i < k; i++){
            res[i] = arr[i];
        }
        return res;
    }
}

还有一个实现比较简单的方法是用小根堆,由于java中本身提供了PriotyQueue结构,所以我们只需要根据是最大根堆还是最小跟堆来重写比较器即可。

class Solution {
    //堆
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        // 默认是小根堆,实现大根堆需要重写一下比较器。
        Queue<Integer> pq = new PriorityQueue<>((v1, v2) -> v2 - v1);
        for (int num: arr) {
            if (pq.size() < k) {
                pq.offer(num);//添加num
            } else if (num < pq.peek()) {//只保存k个,多出来的直接丢弃
                pq.poll();
                pq.offer(num);
            }
        }
        
        // 返回堆中的元素
        int[] res = new int[pq.size()];
        int idx = 0;
        for(int num: pq) {
            res[idx++] = num;
        }
        return res;
    }
}

(二)剑指offer 41. 数据流中的中位数

在这里插入图片描述
基本思路:
1.最直接的方法就是对数字进行排序,然后直接返回中间一个或两个数字即可。这种方法的时间主要用于排序,大概是O(nlogn)的时间复杂度,在这一题中超时了。
2.堆插入和删除需要 O(log n),于是考虑用堆来进行排序。这一题非常巧妙地采用了大顶堆和小顶堆结合的方法,小顶堆放序列中数值大的那部分,大顶堆放序列中较小的那部分。
3.将小顶堆中的根节点元素设为m,凡是比m大的放入小顶堆,比m小的放入大顶堆。但是在插入一个堆时,都不能直接插入,要先插入对方的堆,再从对方堆里面拿去堆顶部元素,这样才能保证取到的是最大(小)值。

class MedianFinder {
    Queue<Integer> A, B;
    public MedianFinder() {
        A = new PriorityQueue<>(); // 小顶堆,保存较大的一半
        B = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
    }
    public void addNum(int num) {
        if(A.size() != B.size()) {
        //当A的元素个数大于B的元素个数时,向B添加一个元素,使二者相等
            A.add(num);
            B.add(A.poll());
        } else {
        //向A添加元素:小顶堆的元素个数可以大于大顶堆1个
            B.add(num);
            A.add(B.poll());
        }
    }
    public double findMedian() {
        return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) / 2.0;
    }
}

(三)剑指Offer45.把数组排成最小的数

在这里插入图片描述
基本思路:
1.这道题实际就是要对数字进行排序,首位数字越小的应当越前,但是由于存在位数不同等原因,需要对排序的依据进行修改。
2.排序方法采用快速排序,并对快速排序中比较的依据进行修改。若拼接字符串 x + y > y + x,则 x>y ;

class Solution {
    void quickSort(String[] words, int l, int r){
        //快速排序
        if(l >= r) return;
        // int base = l;
        int i = l, j = r;
        while(i < j){
            while((words[j] + words[l]).compareTo(words[l] + words[j]) >= 0 && i < j) {
                j--;
            }//找到第一个比base小的值
            while((words[i] + words[l]).compareTo(words[l] + words[i]) < 0 && i < j){//不是小于等于
                i++;
            } //找到第一个比base大的值
            
            String tmp = words[i];
            words[i] = words[j];
            words[j] = tmp;
        }
        if((words[l]+words[i]).compareTo(words[i]+words[l]) > 0){
            words[i] = words[l];
            words[l] = words[j];
        }
        
        quickSort(words, l, i - 1);
        quickSort(words, i+1 , r);        
    }
    public String minNumber(int[] nums) {
        //重新定义排序规则:若x+y > y+x,则认为x>y
        //采用快速排序
        String[] words = new String[nums.length];
        for(int i  = 0; i < nums.length; i++){
            
            words[i] = String.valueOf(nums[i]);//转化为字符串
        }
        // 调用函数:strList.sort((s1, s2) -> (s1 + s2).compareTo(s2 + s1));
        quickSort(words,0,words.length-1);
        StringBuilder res = new StringBuilder();
        for(String s : words){
            res.append(s);
        }
        return res.toString();

    }
}


(四)剑指 Offer 51. 数组中的逆序对

在这里插入图片描述
基本思路:
1.最简单的方法是遍历一遍数组,对于每个数组都查找前面比它大的值,显然这样的时间效率很低,是O(n^2)
2.这一题的做法非常巧妙,它基于归并排序的过程,归并排序分为两个步骤,排序和合并,其中排序是不断拆分递归,直到划分为只有一个元素的小组来实现的。故合并时,我们添加一个比较的环节,如果左边组的数字比右边的小,我们就发现了逆序数,应当计算进去。

public class Solution {
    public int reversePairs(int[] nums) {
        int len = nums.length;

        if (len < 2) {
            return 0;
        }

        int[] copy = new int[len];
        for (int i = 0; i < len; i++) {
            copy[i] = nums[i];
        }

        int[] temp = new int[len];//在最开始就声明一个空的temp,避免每次都创建!!!不然会超时!!!太恶心了
        return reversePairs(copy, 0, len - 1, temp);
    }

   
    private int reversePairs(int[] nums, int left, int right, int[] temp) {
        if (left == right) {
            return 0;
        }

        int mid = left + (right - left) / 2;
        int leftPairs = reversePairs(nums, left, mid, temp);
        int rightPairs = reversePairs(nums, mid + 1, right, temp);

        if (nums[mid] <= nums[mid + 1]) {
            return leftPairs + rightPairs;
        }

        int crossPairs = mergeAndCount(nums, left, mid, right, temp);
        return leftPairs + rightPairs + crossPairs;
    }

    private int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) {
        for (int i = left; i <= right; i++) {
            temp[i] = nums[i];
        }

        int i = left;
        int j = mid + 1;

        int count = 0;
        for (int k = left; k <= right; k++) {

            if (i == mid + 1) {
                nums[k] = temp[j];
                j++;
            } else if (j == right + 1) {
                nums[k] = temp[i];
                i++;
            } else if (temp[i] <= temp[j]) {
                nums[k] = temp[i];
                i++;
            } else {
                nums[k] = temp[j];
                j++;
                count += (mid - i + 1);
            }
        }
        return count;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值