leetcode-堆

 小根堆里堆顶元素是最小的,其他元素都是比堆顶元素要大的

新来的元素想要进入小根堆里面,只有当这个元素比小根堆堆顶元素大的时候才能进入小根堆

所以小根堆可以用来求出前k个最大的元素

同理:大根堆里堆顶元素是最大的,其他元素都是比堆顶元素要小的

新来的元素想要进入大根堆里面,只有当这个元素比小根堆堆顶小的时候才能进入小根堆

所以大根堆可以用来求出前k个最小的元素

助记:小根堆,前k大

           大根堆,前k小

java中堆,不管是大根堆还是小根堆,都是优先队列PriorityQueue

1.创建大根堆和小根堆(默认创建的就是小根堆)

(1)创建小根堆(堆顶元素最小):

PriorityQueue queue=new PriorityQueue();

//向堆里面添加元素
queue.add(0);
queue.add(1);
queue.add(2);
queue.add(3);
queue.add(4);
queue.add(5);
queue.add(6);

//输出堆中全部元素
while(queue.isEmpty()==false)
{
  System.out.println(queue.remove());
}
//输出:0,1,2,3,4,5,6,即小根堆每次弹出当前堆里面最小的元素

(2)创建大根堆

PriorityQueue queue=new PriorityQueue(
                                        { 
                                           public int compare(Integer a,Integer b)
                                           {
                  //这里如果是return a-b就是生成一个小根堆,return b-a就是生成一个大根堆
                                              return b-a;
                                           }                                      
                                        }
                                     );

//向堆里面添加元素
queue.add(0);
queue.add(1);
queue.add(2);
queue.add(3);
queue.add(4);
queue.add(5);
queue.add(6);

//输出堆中全部元素
while(queue.isEmpty()==false)
{
  System.out.println(queue.remove());
}
//输出6,5,4,3,2,1,0即大根堆每次弹出当前堆里面最大的元素

事实上还有一种更简洁的创建大根堆的方式:

PriorityQueue<Integer> queue=new PriorityQueue( (a,b)->(b-a) )

(3)人为设置堆内元素的比较依据:

比较依据:堆内元素对应的哈希值

  PriorityQueue<Integer> queue=new PriorityQueue<Integer>(  (a,b)->(  map.get(a)-map.get(b) ) );

 比较依据:堆内元素(是结点)对应的结点值

  PriorityQueue<ListNode> queue=new PriorityQueue<ListNode>( (a,b)->(a.val-b.val) );

2.堆的常用方法:

(1)通过索引获取堆里面的元素

 queue[0]=3, queue[1]=5,queue[2]=10........

(2)向堆内添加元素:add

弹出堆顶元素:remove

返回堆顶元素(不删除):peek

3.例题:用最大堆,最小堆来解决TopK问题:

(1)小根堆:力扣703,数据流中第k大的数

暴力法的话是通过不了所有测试用例的


class KthLargest 
{
    int k;
    ArrayList<Integer> a=new ArrayList();
    public KthLargest(int k, int[] nums)
    {
       this.k=k;
       for(int i:nums)
       {
           a.add(i);
       }
    }
    
    public int add(int val) 
    {
        a.add(val);
        int[] result=new int[a.size()];
        int j=0;
        for(int temp:a)
        {
            result[j]=temp;
            j++;
        }
        Arrays.sort(result);
        return result[a.size()-k];
    }
}

 小根堆法:

class KthLargest 
{
    int k; 
   // 创建一个小根堆(即堆顶元素最小)
    PriorityQueue<Integer> queue=new PriorityQueue<Integer>();
   //构造函数:传入k值和数组(后面调用add函数会往这个数组里面添加元素)
    public KthLargest(int k, int[] nums) 
    {
        this.k=k;
        
        //这道题要维护一个size为k的堆 
        //在堆的长度小于k之前,随便添加
        //当堆的长度等于k的时候,此时堆顶元素是这k个数中最小的
        //由于我们是想要找出数组中第k大的,所以要添加新的元素进堆中时,如果新的数比堆顶元素大,就可以进堆里面(先把堆顶元素弹出,然后再把这个新的元素添加进堆中),如果新的数不比堆顶元素大,就不添加进堆里
        for(int i=0;i<nums.length;i++)
        {
            if(queue.size()<k)
            {
                queue.add(nums[i]);
            }
            else
            {
                if(nums[i]>queue.peek())
                {
                    queue.remove();
                    queue.add(nums[i]);

                }
            }
        }
    }
  
    public int add(int val)
    {
       if(queue.size()<k)  queue.add(val);
       else
       {
             if(val>queue.peek())
             {
               queue.remove();
               queue.add(val);
            }
       }
       return queue.peek();
    }
}

(2)大根堆的题:

力扣1046 最后一块石头的重量

class Solution 
{
    public int lastStoneWeight(int[] stones)
    {
        //构造一个大根堆
        PriorityQueue<Integer> queue=new PriorityQueue<Integer>((a,b)->b-a);
        //将数组里的元素放进大根堆
        for(int stone:stones)
        {
            queue.add(stone);
        }
        while(queue.size()>1)//只要堆里面的元素有两个以上
        {
           int a=queue.remove();//取出堆顶元素,即最大的元素
           int b=queue.remove();//继续取出堆顶元素,第二大的元素
           int c=Math.abs(a-b);
           queue.add(c);
        }
        //跳出循环,则堆里面只有一个元素了
        return  queue.remove(); 
    }
}

(3)不止找一个数,而是找出若干个数(那就queue.remove()若干次,弹出若干次堆顶元素就行)

力扣 剑指offer40  最小的k个数

暴力法:

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

小根堆的方法:

class Solution 
{
    public int[] getLeastNumbers(int[] arr, int k) 
    {
       PriorityQueue<Integer> queue=new PriorityQueue();
       for(int i:arr)
       {
           queue.add(i);
       }
       int[] result=new int[k];
       for(int j=0;j<k;j++)
       {
           result[j]=queue.remove();
       }
       return result;
    }
}

(4)前k个高频元素

力扣347

这里创建小根堆可以采用更简化的方式:

PriorityQueue pq=new Priority( (a,b)->( map.get(a)-map.get(b) )  );

 小根堆里堆顶元素是最小的,其他元素都是比堆顶元素要大的

新来的元素想要进入小根堆里面,只有当这个元素比小根堆大的时候才能进入小根堆

class Solution
{
   public int[] topKFrequent(int[] nums, int k)
    {
        HashMap<Integer,Integer> map=new HashMap();
        for(int num:nums)
        {
            map.put(num,map.getOrDefault(num,0)+1);
        }
       //构建一个特殊的小根堆,小根堆里面存的是hashmap的key,比较的依据是这这个key对应的value值,也就是说堆顶元素是这个堆中所有key中,对应的value最小的那个key
        PriorityQueue<Integer> queue=new PriorityQueue(
                                                        new Comparator<Integer>() 
                                                        {
                                                          @Override
                                                          public int compare(Integer a, Integer b)
                                                          {
                                                            return map.get(a)-map.get(b);
                                                          }
                                                         }
                                                       );//构建一个小根堆
        int size=0;
  //往小根堆里面添加key,在堆的容量没有达到k之前,随便加
  //一旦容量达到k,想要将key再加入堆,就必须这个key对应的value值要大于堆顶元素对应的value值
        for(int num:map.keySet())
        {
            if(size<k)   queue.add(num);
            else//当小根堆的容量超过k
            {
                if(map.get(num)>map.get(queue.peek()))
                {
                    queue.remove();
                    queue.add(num);
                }
            }
            size++;
        }
        int[] result=new int[queue.size()];
        int temp=queue.size();
        for(int i=0;i<temp;i++)
        {
            result[i]=queue.remove();
        }
        return result;
    }
}

这里重写了compare方法,表示堆里面比较的是元素的hashmap值来排堆里元素的位置

当堆里存的是链表节点的时候,要根据节点的值来排堆里的元素位置可以这样写:

 PriorityQueue<ListNode> queue=new PriorityQueue(
                                                         new Comparator<ListNode>()
                                                         {
                                                             public int compare(ListNode a,ListNode b)
                                                             {
                                                                 return a.val-b.val;
                                                             }
                                                         }
                                                       );

(5)力扣剑指offer41  数据流中的中位数

建一个大根堆,小根堆

第一个数字,直接进大根堆

后面所有的:.

后面的每个数进堆,如果小于等于大根堆的堆顶,入大根堆,如果不是,入小根堆

每加完一个数字后,要比较大根堆和小根堆的大小

如果其中一个size比另一个size多了2,那就size大的那个堆堆顶元素弹出,进size更小的那个堆

最后的效果就是:较小的n/2个数在大根堆里,较大的n/2个数在小根堆里

大根堆堆顶元素和小根堆顶元素一定可以求出中位数

class MedianFinder 
{

    PriorityQueue<Integer>  pq1,pq2;
    public MedianFinder() 
    { 
        pq1=new PriorityQueue();//小根堆

        pq2=new PriorityQueue<Integer>( (a,b)->(b-a) );//大根堆
    }
    
    public void addNum(int num) 
    {
       //第一个数直接进大根堆
       if(pq1.size()==0&&pq2.size()==0)
       {
          pq2.add(num);
       }
       else
       {
         //小于等于大根堆堆顶的元素,就入大根堆
          if(num<=pq2.peek())
          {
              pq2.add(num);
              if(Math.abs(pq1.size()-pq2.size())>=2)    //这里写大于等于还是等于都可以
              {
                  if(pq1.size()>pq2.size())
                  {
                      pq2.add(pq1.remove());
                  }
                  else
                  {
                      pq1.add(pq2.remove());
                  }
              }
          }
          else//大于大根堆堆顶就入小根堆
          {
              pq1.add(num);
              if(Math.abs(pq1.size()-pq2.size())>=2)
              {
                  if(pq1.size()>pq2.size())
                  {
                      pq2.add(pq1.remove());
                  }
                  else
                  {
                      pq1.add(pq2.remove());
                  }
              }
          }
       }
    }
    
    public double findMedian() 
    {
        if(pq1.size()==pq2.size())   return  (pq1.peek()+pq2.peek())/2.0;
        else
        {
            if(pq1.size()>pq2.size())
            {
                return  (double)pq1.peek();          
            }
            else
            {
                return  (double)pq2.peek();
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值