牛客网在线编程专题《剑指offer-面试题30》最小的K个数

我的个人微信公众号:Microstrong

微信公众号ID:MicrostrongAI

微信公众号介绍:Microstrong(小强)同学主要研究机器学习、深度学习、计算机视觉、智能对话系统相关内容,分享在学习过程中的读书笔记!期待您的关注,欢迎一起学习交流进步!

知乎主页:https://www.zhihu.com/people/MicrostrongAI/activities

Github:https://github.com/Microstrong0305

个人博客:https://blog.csdn.net/program_developer

题目链接:

https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&tPage=2&rp=2&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

题目:

解题思路:

(1)方法一:利用快排对输入数字进行排序,然后取最小的k个数。

已经AC的代码:

# -*- coding:utf-8 -*-

class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        if len(tinput) < k or len(tinput) == 0:
            return []
        list = self.Sort(tinput, 0, len(tinput) - 1)
        return list[0:k]

    def Sort(self, array, low, high):
        if low < high:
            index = self.partition(array, low, high)
            self.Sort(array, low, index - 1)
            self.Sort(array, index + 1, high)
        return array

    def partition(self, array, low, high):
        key = array[low]
        while low < high:
            while low < high and array[high] >= key:
                high = high - 1
            array[low] = array[high]
            while low < high and array[low] <= key:
                low = low + 1
            array[high] = array[low]
        array[high] = key
        return high


if __name__ == '__main__':
    list = [4, 5, 1, 6, 2, 7, 3, 8]
    solution = Solution()
    print(solution.GetLeastNumbers_Solution(list, 4))

第一次写的时候没有通过,是因为快排边界条件写错了,没有考虑等号的情况,造成程序未在规定的时间内运行结束。错误代码如下:

while low < high:
    while low < high and array[high] > key:
        high = high - 1
    array[low] = array[high]

    while low < high and array[low] < key:
        low = low + 1
    array[high] = array[low]

已经AC的Java代码:

import java.util.*;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> arr=new ArrayList<>();
        if(input.length<k||input.length==0) return arr;
        sort(input,0,input.length-1);
        for(int i=0; i<k; i++)
            arr.add(input[i]);
        return arr;
    }
     public void sort(int[] a, int left, int right){
        if(left<right){
            int mid=isMid(a,left,right);
            sort(a,left,mid-1);
            sort(a,mid+1,right);
        }
    }
    public int isMid(int[] a,int left, int right){
        int mid=a[left];
        while(left<right){
            while(a[right]>=mid && left<right)
                right--;
            a[left]=a[right];
            while(a[left]<=mid && left<right)
                left++;
            a[right]=a[left];
        }
        a[left]=mid;
        return left;
    }
}

 Python快排聪明人的写法,AC通过:

# -*- coding:utf-8 -*-
class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        def quick_sort(lst):
            if not lst:
                return []
            pivot = lst[0]
            left = quick_sort([x for x in lst[1:] if x < pivot])
            right = quick_sort([x for x in lst[1:] if x >= pivot])
            return left + [pivot] + right

        if tinput == [] or k > len(tinput):
            return []
        tinput = quick_sort(tinput)
        return tinput[: k]

if __name__ == '__main__':
    list = [4, 5, 1, 6, 2, 7, 3, 8]
    solution = Solution()
    print solution.GetLeastNumbers_Solution(list, 4)

(2)方法二:基于Partition函数O(n)的算法

利用快速排序中的获取分割(中轴)点位置函数partition

基于数组的第k个数字来调整,使得比第k个数字小的所有数字都位于数组的左边,比第k个数字大的所有数字都位于数组的右边。调整之后,位于数组左边的k个数字就是最小的k个数字(这k个数字不一定是排序的)。时间复杂度为:O(N)。

第一次写不通过,因为快排边界条件写错了,没有考虑等号的情况。(错误同方法一)

第二次写不通过,因为牛客网要求输出的结果必须也是排好序的,因此最终结果又用sorted()函数排一下序。

第三次,终于通过啦!

已经AC的Python代码:

class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        # 空列表
        if tinput == None or len(tinput) == 0 or len(tinput) < k:
            return []

        index = self.partition(tinput, 0, len(tinput) - 1)
        while index != k - 1:
            if index > k - 1:
                index = self.partition(tinput, 0, index - 1)
            else:
                index = self.partition(tinput, index + 1, len(tinput) - 1)

        return sorted(tinput[:k])

    def partition(self, numList, low, high):
        key = numList[low]
        while low < high:
            while low < high and numList[high] >= key:
                high -= 1
            numList[low] = numList[high]

            while low < high and numList[low] <= key:
                low += 1
            numList[high] = numList[low]

        numList[high] = key
        return high

if __name__ == '__main__':
    list = [4, 5, 1, 6, 2, 7, 3, 8]
    solution = Solution()
    print(solution.GetLeastNumbers_Solution(list, 4))

已经AC的C++代码:

class Solution {
public:
    void swap(int &fir,int &sec)
    {
        int temp = fir;
        fir = sec;
        sec = temp;
    }
     
    int getPartition(vector<int> &input,int start,int end)
    {
        if(input.empty() || start>end) return -1;
        int temp = input[end];
        int j = start - 1;
        for(int i=start;i<end;++i)
        {
            if(input[i]<=temp)
            {
                ++j;
                if(i!=j) swap(input[i],input[j]);                   
            }
        }
        swap(input[j+1],input[end]);
        return (j+1);
    }
         
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k)
    {
        vector<int> result;       
        if(input.empty() || k>input.size() || k<=0) return result;
         
        int start = 0;
        int end = input.size()-1;
        int index = getPartition(input,start,end);
         
        while(index != (k-1))
        {
            if(index > (k-1))
            {
                end = index - 1;
                index = getPartition(input,start,end);
            }
            else
            {
                start = index + 1;
                index = getPartition(input,start,end);
            }
        }
         
        for(int i=0;i<k;++i)
        {
            result.push_back(input[i]);
        }
         
        return result;
    }
};

总结:这种思路虽时间复杂度不错,但会修改输入数组,且一般也不易想到。更容易想到的是利用堆排序。

(3)方法三:堆排序,O(N logK),适合处理海量数据。

用最大堆保存这k个数,每次只和堆顶比,如果比堆顶小,删除堆顶,新数入堆。

1) 遍历输入数组,将前k个数插入到堆中;

2) 继续从输入数组中读入元素做为待插入整数,并将它与堆中最大值比较:如果待插入的值比当前已有的最大值小,则用这个数替换当前已有的最大值;如果待插入的值比当前已有的最大值还大,则抛弃这个数,继续读下一个数。

这样动态维护堆中这k个数,以保证它只储存输入数组中的前k个最小的数,最后输出堆即可。

Python通过代码:

class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        def siftup(lst, temp, begin, end):
            if lst == []:
                return []
            i, j = begin, begin * 2 + 1
            while j < end:
                if j + 1 < end and lst[j + 1] > lst[j]:
                    j += 1
                elif temp > lst[j]:
                    break
                else:
                    lst[i] = lst[j]
                    i, j = j, 2 * j + 1
            lst[i] = temp
 
        def heap_sort(lst):
            if lst == []:
                return []
            end = len(lst)
            for i in range((end // 2) - 1, -1, -1):
                siftup(lst, lst[i], i, end)
            for i in range(end - 1, 0, -1):
                temp = lst[i]
                lst[i] = lst[0]
                siftup(lst, temp, 0, i)
            return lst
 
        if tinput == [] or k > len(tinput):
            return []
        tinput = heap_sort(tinput)
        return tinput[: k]

Java通过代码:

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list=new ArrayList<Integer>();
        //检查输入的特殊情况
        if(input==null || input.length<=0 || input.length<k){
            return list;
        }
        //构建最大堆
        for(int len=k/2-1; len>=0; len--){
            adjustMaxHeapSort(input,len,k-1);
        }
        //从第k个元素开始分别与最大堆的最大值做比较,如果比最大值小,则替换并调整堆。
        //最终堆里的就是最小的K个数。
        int tmp;
        for(int i=k; i<input.length; i++){
            if(input[i]<input[0]){
                tmp=input[0];
                input[0]=input[i];
                input[i]=tmp;
                adjustMaxHeapSort(input,0,k-1);
            }
        }
        for(int j=0; j<k; j++){
            list.add(input[j]);
        }
        return list;
    }
     
    public void adjustMaxHeapSort(int[] input, int pos, int length){
        int temp;
        int child;
        for(temp=input[pos]; 2*pos+1<=length; pos=child){
            child=2*pos+1;
            if(child<length && input[child]<input[child+1]){
                child++;
            }
            if(input[child]>temp){
                input[pos]=input[child];
            }else{
                break;
            }
        }
        input[pos]=temp;
    }
}

我们自己从头实现一个最大堆需要一定的代码量,这在面试短短的几十分钟内很难完成。我们可以借助于Python中实现了最小堆的heapq库。因为在一个数组中,每个数取反,则最大数变成了最小数,整个数字的顺序发生了变化,所以可以给数组的每个数字取反,然后借助最小堆,最后返回结果的时候再取反就可以了。

  • heapq.heappush(res, -i) 意为:向堆res中添加一个元素-i
  • heapq.heappushpop(res, -i)意为:将元素-i与堆顶的元素比较。如果该元素值大于堆顶元素,则将该元素与堆顶元素替换。否则不改变堆元素。

已经AC的代码:

# -*- coding:utf-8 -*-
import heapq

class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        if len(tinput) < k:
            return []
        res = []
        for i in tinput:
            heapq.heappush(res, -i) if len(res) < k else heapq.heappushpop(res, -i)
        return sorted(list(map(lambda x: -x, res)))

再来一种更简便的写法:

  • heapq.nlargest(n, iterable, key=None) 返回最大的n个元素(Top-K问题)
  • heapq.nsmallest(n, iterable, key=None) 返回最小的n个元素(Top-K问题)

已经AC的代码:

# -*- coding:utf-8 -*-

import heapq

class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        if len(tinput) < k:
            return []
        return heapq.nsmallest(k, tinput)

总结:

1.如果用快速排序,时间复杂度为 O(nlogn)。

2.基于Partition函数的时间复杂度为O(n)。

3.基于堆排序算法,构建最大堆的时间复杂度为 O(nlogk)。

4.如果用冒泡排序,时间复杂度为 O(n*k)。

Reference

【1】《剑指offer》,何海涛著。

【2】剑指offer:最小的K个数(Python),地址:https://blog.csdn.net/u010005281/article/details/80084994

【3】【剑指Offer】最小的K个数 解题报告(Python),地址:https://blog.csdn.net/fuxuemingzhu/article/details/79637795

【4】窥探算法之美妙——寻找数组中最小的K个数&python中巧用最大堆,地址:https://www.cnblogs.com/cotyb/p/5205123.html

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页