寻找数组中第K频繁的元素
问题是:给你一个数组,求解出现次数第K多的元素。当然leetcode上的要求是算法复杂度不能大于O(N*logN)。
首先这个问题我先是在leetcode上看到,当时想了两种做法,做到一半都觉得不是很好,正在思考别的方法。然后在牛客网上看别人的面试经历,看到一个应聘者和用我几乎完全一样的思路尝试在面试中解决这个问题(HashMap-->TreeSet),但是都没解决出来。这个问题确实是一个乍看不难但是要实际解决又会不停发现自己思路有问题的问题,于是我索性记录一下这两种想法和解决之道。
拿到这个问题的时候我第一时间想到了用HashMap解决,键值对的键是数组中的元素的值,值是目前出现的次数,当第一次出现的时候值给1,以后发现已经存在那么就取出值加1再放进去。这个方法的思想是用类似于上一篇博文中的方法来做。核心是用hash算法形成一个元素与储存位置的映射来实现查找操作的常量时间复杂度。这么做的结果是你能在O(N)的算法复杂度情况下得到一个HashMap,它储存了每一种元素和其出现的个数。这个时候一个很具体的问题就出现了,下一步本来需要对出现次数进行一个排序,然后取出出现次数第K大的那个元素。但是问题是键值对的映射方式是从键映射到值,也就是说你用HashMap可以找出值的集合,但是无法找出与这个值对于的键。比如你通过排序知道了最频繁的次数是5,但是你不知道到底谁出现了5次。这个地方其实我要解释一下,不是说你就完全做不到实现反映射,而是第一,HashMap实现的是映射你强行根据值寻找键不符合其设计初衷,第二,实现的过程比较复杂我想需要很大的算法复杂度完全抵消了使用HashMap带来的好处。比如一个键只能对应一个值,但是一个值可能有多个键对应。(每一个数组中的元素都有确定的出现次数,但是出现次数为K的元素可能不止一个)。要解决这些问题等等。真的解决了也就不是这道题目的问题了。
第二时间我想到了用TreeSet,因为它既可以根据你的重写的equal()方法判断是否等于和去重,又可以对你输入数据用comparator进行排序。我就想,我干脆写一个类,里面即方数字本身用于比较是否相等(hashcode和equal),又放当前出现的次数,又实现comparator接口来对当前出现的次数进行排序。我的期望是好的,但是在一开始的思考的时候我忽视了(确实没想到)一个根本的问题,这个问题直接导致我的这个想法完全不能按照我设想的实现。随着你遍历的进行,你的某个数的出现的次数可能从1变到2变到3。所以你有一个不停动态修改TreeSet的内容对象的过程。但是你无法获得TreeSet中值为当前数组值的那个对象的引用。比如说我现在遍历到数组中的5这个元素,于是我去TreeSet中找值为5的对象,但是找不到(没有提供相应的方法)。当然确实也不应该有这样的方法,你想你都知道值是5了,可是你又不知道具体引用的值,TreeSet也没办法帮你。如果用遍历强行去找会导致一个很复杂的过程,算法复杂度高(红黑树的遍历)。同时,TreeSet是按照某种规律排序的,大量的修改其值的操作(通过先删除再add,或者这个问题中的用add覆盖)会影响其效率因为要维护某种顺序。所以这个问题中也不推荐这么做。
虽然以上两种做法都么能直接解决这个问题,但是给我带来了思考。
最后还是做出来了,大致就是第一种方法不过键值对的值是我写的类,最后再用堆排序。这个做法还是挺蠢得我觉得。貌似最佳做法使用桶排序O(N)?以后再研究研究这个。