当前位置: 首页 > news >正文

k个最大的数及变种小结

k个最大的数及变种小结

声明

文章均为本人技术笔记,转载请注明出处:
[1] https://segmentfault.com/u/yzwall
[2] blog.csdn.net/j_dark/

0 堆实现

堆逻辑上是完全二叉树,物理上表现为数组,节点编号默认从0开始;
构造函数public Heap(boolean flag),flag为真建立最小堆,否则建立最大堆;向外提供接口如下:

  • offer(int x):将元素x加入堆中;

  • poll():弹出堆顶,空堆弹出Integer.MAX_VALUE

  • top():返回堆顶值但不弹出,空堆返回Integer.MAX_VALUE

  • size():返回堆中元素总数;

/**
 * 堆实现
 * @author yzwall
 */
public class Heap {
    private ArrayList<Integer> array;
    
    // true代表最小堆, false代表最大堆
    private boolean flag;
    
    public Heap(boolean flag) {
        this.array = new ArrayList<Integer>();
        this.flag = flag;
    }
    
    // 检查父节点a与其孩子节点b是否满足堆序
    private boolean isOrder(int a, int b) {
        if (flag) {
            return a < b ? true : false;
        }
        return a > b ? true : false;
    }
    
    /**
     * 添加元素,时间复杂度O(logn)
     * 1. 将新元素添加到完全二叉树尾部,物理上为数组尾部
     * 2. 新元素向上检查是否满足堆序
     */
    public void offer(int newNum) {
        array.add(newNum);
        int index = array.size() - 1;
        shiftUp(index);
    }
    // 上滤操作,检查index与父节点是否满足堆序
    private void shiftUp(int index) {
        while (index != 0) {
            int parent = (index - 1) / 2;
            if (isOrder(array.get(parent), array.get(index))) {
                break;
            }
            // 不满足堆序,互换位置
            int temp = array.get(index);
            array.set(index, array.get(parent));
            array.set(parent, temp);
            index = parent;
        }
    }
    
    /**
     * 删除堆顶,时间复杂度O(logn)
     * 1. 将末元素覆盖堆顶(删除堆顶)
     * 2. 删除末元素原来位置(保证堆节点总数-1)
     * 3. 向下检查新堆顶与孩子节点的堆序性
     */
    public int poll() {
        int top = top();
        if (top == Integer.MAX_VALUE) {
            return top;
        }
        array.set(0, array.get(array.size() - 1));
        array.remove(array.size() - 1);
        shiftDown(0);
        return top;
    }
    // 下滤操作,检查index与孩子节点是否满足堆序
    private void shiftDown(int index) {
        int bigIndex, leftIndex, rightIndex;
        boolean hasLChild = false, hasRChild = false;
        while (true) {
            leftIndex = 2 * index + 1;
            rightIndex  = 2 * index + 2;
            if (leftIndex < array.size() && array.get(leftIndex) != Integer.MAX_VALUE) {
                hasLChild = true;
            }
            if (rightIndex < array.size() && array.get(rightIndex) != Integer.MAX_VALUE) {
                hasRChild = true;
            }
            // 叶子节点默认满足堆序
            if (!hasLChild && !hasRChild) {
                break;
            } else if(hasLChild && hasRChild) {
                bigIndex = isOrder(array.get(leftIndex), array.get(rightIndex)) ? leftIndex : rightIndex;
            } else if(hasLChild) {
                bigIndex = leftIndex;
            } else {
                bigIndex = rightIndex;
            }
            
            if (isOrder(array.get(index), array.get(bigIndex))) {
                break;
            }
            // 不满足堆序,index与较大孩子节点互换位置
            int temp = array.get(index);
            array.set(index, array.get(bigIndex));
            array.set(bigIndex, temp);
            index = bigIndex;
            hasLChild = false;
            hasRChild = false;
        }
    }
    // 获取堆顶
    public int top() {
        return array.size() == 0 ? Integer.MAX_VALUE : array.get(0);
    }
    // 返回堆大小
    public int size() {
        return array.size();
    }
}

1 求k个最大的数

  • lintcode 544 Top K Largest Numbers(解法1~解法3,解法4无法保证结果有序)

1.1 解法1 最大堆实现$O(nlog n)$时间复杂度

  1. 将所有元素加入最大堆中(当元素总数n特别大时,创建堆时间开销大,花费$O(nlog n)$);

  2. 弹出堆顶k次,保证结果降序(花费时间$O(nlog k)$);

/**
 * 求给定数组前k大数,结果要求以降序给出
 * http://www.lintcode.com/zh-cn/problem/top-k-largest-numbers/
 * 解法2:最大堆解决前k大数:将所有元素入堆,最后弹出k次,时间复杂度O(nlogn),大数据量时速度低于最小堆解法
 * @author yzwall
 */
class Solution11 {
    public int[] topk(int[] nums, int k) {
        Heap heap = new Heap(false);
        for (int i = 0; i < nums.length; i++) {
            heap.offer(nums[i]);
        }
        int[] topK = new int[k];
        for (int i = 0; i < k; i++) {
            topK[i] = heap.poll();
        }
        return topK;
    }
}

1.2 解法2 最小堆实现$O(nlog n)$时间复杂度

针对最大堆解法创建堆开销时间过高,维护一个元素总数最多为k的最小堆;每次淘汰待进堆元素和堆顶的较小者,保证堆中元素始终为已扫描数据中的最大的k个数;
解法:

  1. 建立最小堆(花费时间$O(nlog k)$);

  2. 弹出堆顶k次(花费时间$O(klog k)$);

  3. 根据题意将结果逆序(花费时间$O(k)$);;

/**
 * 求给定数组前k大数,结果要求以降序给出
 * http://www.lintcode.com/zh-cn/problem/top-k-largest-numbers/
 * 解法2:最小堆解决前k大数,时间复杂度O(nlogn)
 * @author yzwall
 */
class Solution10 {
    public int[] topk(int[] nums, int k) {
        Heap heap = new Heap(true);
        for (int i = 0; i < nums.length; i++) {
            if (heap.size() < k) {
                heap.offer(nums[i]);
            } else {
                // 更新前k大数中最小值
                if (heap.top() < nums[i]) {
                    heap.poll();
                    heap.offer(nums[i]);
                }
            }
        }
        int[] topK = new int[k];
        for (int i = 0; i < k; i++) {
            topK[i] = heap.poll();
        }
        for (int i = 0, j = k - 1; i < j; i++, j--) {
            int temp = topK[i];
            topK[i] = topK[j];
            topK[j] = temp;
        }
        return topK;
    }
}

1.3 解法3 优先队列实现$O(nlog n)$时间复杂度

优先队列可用来实现最大堆(解法1)和最小堆(解法2),根据需要重写构造方法中的比较器;
给出优先队列实现最大堆解法:

/**
 * 求给定数组前k大数,要求给出降序结果
 * http://www.lintcode.com/zh-cn/problem/top-k-largest-numbers/
 * 解法3:重写优先队列比较器实现最大堆
 * @author yzwall
 */
class Solution {
    public int[] topk(int[] nums, int k) {
        int[] topK = new int[k];
        PriorityQueue<Integer> pq = new PriorityQueue<>(k, new Comparator<Integer>() {
            public int compare(Integer o1, Integer o2) {
                return o1 > o2 ? -1 : 1;
            }
        });
        for (int i = 0; i < nums.length; i++) {
            pq.offer(nums[i]);
        }
        for (int i = 0; i < k; i++) {
            topK[i] = pq.poll();
        }
        return topK;
    }
}

1.4 解法4 Partiton $O(n)$时间复杂度

“求k个最大数”转变为:

  1. Partition切分思想求出数组第k小数

  2. 切分操作完成后,此时数组前k个数必然是最大的k个数

注意:切分操作完成后,结果乱序;

解法对比

---是否修改输入时间复杂度空间复杂度是否按序输出结果
解法1$O(nlog n)$$O(n)$
解法2$O(nlog k)$$O(n)$
解法3$O(nlog n)$或者$O(nlog k)$$O(n)$
解法4$O(n)$$O(1)$
  • 解法1:最大堆时间复杂度最高,空间复杂度最高;

  • 解法2:最小堆在解法1基础上在创建堆时控制入堆元素数量,降低创建堆时间开销;

  • 解法3:优先队列是堆的库函数实现,只需要根据题意重写比较器java.util.Comparator<T>,无需手写堆;

  • 解法4:时间复杂度和空间复杂度均最低,但是Partition切分算法无法保证最终结果有序;

2 求出现次数最多的k个单词

  • lintcode 471 Top K Frequent Words

使用优先队列实现,根据题意:

你需要按照单词的词频排序后输出,越高频的词排在越前面。如果两个单词出现的次数相同,则词典序小的排在前面。

解法如下:

  1. 使用HashMap统计单词出现次数;

  2. 优先队列队首维护出现次数最多单词;

  3. 优先队列出队k次;

创建优先队列时候需重写比较器,单词字典序比较使用java.util.String类自带的compareTo方法(String类实现Comparable<T>接口);

/**
 * 优先队列实现,字典序比较使用String类自带CompareTo方法
 * 求出现次数最多的K个单词,要求结果按序输出(出现次数升序输出,相同按照单词字典序升序输出);
 * http://www.lintcode.com/en/problem/top-k-frequent-words/
 * @author yzwall
 */
class Solution {
    private class Word {
        String word;
        int count;
        public Word(String word, int count) {
            this.word = word;
            this.count = count;
        }
    }
    
    public String[] topKFrequentWords(String[] words, int k) {
        PriorityQueue<Word> pq = new PriorityQueue<>(words.length, new Comparator<Word>() {
            public int compare(Word o1, Word o2) {
                if (o1.count != o2.count) {
                    return o1.count > o2.count ? -1 : 1;
                } else {
                    return o1.word.compareTo(o2.word);
                }
            }
        });
        
        HashMap<String, Integer> map = new HashMap<>();
        for (String word : words) {
            if (map.containsKey(word)) {
                map.put(word, map.get(word) + 1);
            } else {
                map.put(word, 0);
            }
        }
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            pq.offer(new Word(entry.getKey(), entry.getValue()));
        }
        String[] topK = new String[k];
        for (int i = 0; i < k; i++) {
            if (!pq.isEmpty()){
                topK[i] = pq.poll().word;
            } else {
                break;
            }
        }
  
        return topK;
    }
}

3 求距离最近的k个点

  • lintcode 612 K Closest Points

求距离origit点最近的k个点,结果要求以距离升序输出,距离相同按x坐标升序,x坐标相同按照y坐标升序
解法:根据题意使用优先队列java.util.PriorityQueue<T>,按照题意构造比较器Compator<T>;

/**
 * 求距离origit点最近的k个点,距离相同按x坐标升序,x坐标相同按照y坐标升序
 * http://www.lintcode.com/en/problem/k-closest-points/
 * 使用优先队列java.util.PriorityQueue,按照题意构造比较器Compator;
 * @author yzwall
 */
class Solution {
    private class Point {
        int x, y;
        Point() { x = 0; y = 0; };
        Point(int a, int b) { x = a; y = b; }
    }
    
    private class PPoint {
        int x, y, dist;
        PPoint(Point p, int dist) { 
            this.x = p.x; 
            this.y = p.y;
            this.dist = dist;
        }
    }
    
    private int distance(Point src, Point dest) {
        int xx = src.x - dest.x;
        int yy = src.y - dest.y;
        return xx * xx + yy * yy;
    }
    
    public Point[] kClosest(Point[] points, Point origin, int k) {
        // 使用new PriorityQueue<>(new Comparatpr<T>()) lintcode编译会报错
        PriorityQueue<PPoint> pq = new PriorityQueue<>(k, new Comparator<PPoint>(){
            @Override
            public int compare(PPoint o1, PPoint o2) {
                if (o1.dist != o2.dist) {
                    return o1.dist - o2.dist;
                } else {
                    if (o1.x != o2.x) {
                        return o1.x - o2.x;
                    }
                    return o1.y - o2.y;
                }
            }
        });
        if (points == null || points.length < k) {
            return null;
        }
        Point[] array = new Point[k];
        for (Point point : points) {
            pq.offer(new PPoint(point, distance(origin, point)));
        }
        for (int i = 0; i < k; i++) {
            PPoint temp = pq.poll();
            array[i] = new Point(temp.x, temp.y);
        }
        
        return array;
    }
}

相关文章:

  • [Prism]Composite Application Guidance for WPF(9)——命令
  • 最受 Web 开发者欢迎的 NoSQL 和关系数据库
  • ASP.NET MVC Preview 5 and Form Posting Scenarios
  • 洛谷P2726 阶乘 Factorials 数学
  • COM+中怎么公用一个数据层接口
  • JAVA常见算法题(十七)
  • Node 版本管理
  • XNA Game Stdio 3.0 发布了
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • 可能出现的问题
  • Mysql远程登陆错误:ERROR 2003
  • 在Word里实现禁止复制和选定
  • RAC维护手记08-ASM磁盘组信息查看常用命令
  • 什么是TELNET协议
  • [转]一种革命性的自绘菜单实现
  • 【个人向】《HTTP图解》阅后小结
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  • iOS | NSProxy
  • IOS评论框不贴底(ios12新bug)
  • JavaWeb(学习笔记二)
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • Node + FFmpeg 实现Canvas动画导出视频
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • PHP面试之三:MySQL数据库
  • Python打包系统简单入门
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • vuex 学习笔记 01
  • 读懂package.json -- 依赖管理
  • 前端技术周刊 2019-01-14:客户端存储
  • 深度学习入门:10门免费线上课程推荐
  • 用element的upload组件实现多图片上传和压缩
  • const的用法,特别是用在函数前面与后面的区别
  • kubernetes资源对象--ingress
  • Spring Batch JSON 支持
  • (14)学习笔记:动手深度学习(Pytorch神经网络基础)
  • (function(){})()的分步解析
  • (初研) Sentence-embedding fine-tune notebook
  • (力扣)循环队列的实现与详解(C语言)
  • (转)JAVA中的堆栈
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .NET 使用配置文件
  • .NET国产化改造探索(一)、VMware安装银河麒麟
  • .php文件都打不开,打不开php文件怎么办
  • 。Net下Windows服务程序开发疑惑
  • /bin/rm: 参数列表过长"的解决办法
  • ??eclipse的安装配置问题!??
  • []C/C++读取串口接收到的数据程序
  • [Android 数据通信] android cmwap接入点
  • [android]-如何在向服务器发送request时附加已保存的cookie数据
  • [ASP]青辰网络考试管理系统NES X3.5
  • [BUUCTF]-Reverse:reverse3解析
  • [C#]DataTable常用操作总结【转】