算法练习 - 堆

算法练习 - 堆


练习1 LeetCode - 451. Sort Characters By Frequency

本题中,主要是基于heap的高级用法,其中统计元素出现次数,还借助了哈希实现的Counter

class Solution(object):
    # 输入:一个随机字符串
    # 输出:一个按照字符出现的次数,由大到小重新组合的字符串,区分大小写

    # 错误处理:当字符串为空的时候,直接输出空

    # 这里我们使用堆的一个常见应用,优先队列(每一个元素都由一个key,此处元素是字符,key是出现次数)
    # 有限队列支持的操作:Insert,Maximum,Extract-max,increase-Key
    # 简直就是天生的为这道题准备的数据结构
    # python中的优先队列的已有实现 - heapq模块

    # heapq模块中一些方法的简单介绍:
    # 1. heappush(heap,item)
    # 将 item push 进堆,并且保持堆不变
    # 2. heappop(heap)
    # 返回并popheap中最小的item,并保持堆不变,如果堆为空,那么就会返回一个IndexError,如果不想pop出去,就用heap[0]这样的方式
    # 3. heappushpop(heap,item)
    # 比使用那两个,更高效一些
    # 4. heapify(x)
    # 将list x 转化为 堆,空间原址,线性时间
    # 5. heapreplace(heap,item)
    # 更高效的先pop在push
    # 6. nlargest()
    # 可以获得一个集合中最大的n个元素的列表,在对字典取最大值时,默认key在前,value在后 key-vlue的形式

    # 同时我们还要借助 collections 模块,中的Counter类,可以让我们跟踪值出现的次数。

    def frequencySort(self, s):
        # 设h是一个键值对的列表
        h = []
        # 将s中字符串的字符的个数作为key,字符作为value,以键值对的形式存入h中
        c_dict = collections.Counter(s).items()
        for v,k in c_dict:
            h.append((k,v))
        # 以key的大小输出一个新的列表
        hh = heapq.nlargest(len(h),h)
        # 将列表转化为字符串
        new_s = ''.join(value*key for value,key in hh)
        return new_s

练习2 373. Find K Pairs with Smallest Sums

本题中同样要用到一点动态规划,好在不难,很容易思考出来

class Solution(object):
    # 输入:两组数组,nums1(长度为m) 和 nums2(长度为n);k
    # 输出:从小到大排列的前k个pair

    # pair是由nums1和nums2中的元素组成的一对数

    # nums1 和 nums2 都是有序的
    # 暴力直接的做法:求出所有的组合方式,然后找出前k小的,输出 时间复杂度大于O(mn)

    # 同样要用到动态规划的思想:

    # 把nums1和nums2的所有组合分类,可以分为m类,分为是 nums1[0]-nums2所有数,nums1[1]-nums2所有数,...nums1[m-1]-nums2所有数
    # 第一个最小组合一定是nums1[0]-nums2[0]的这个组合
    # 那么第二个最小组合就出在 nums1[0]-nums2[1],nums1[1]-nums2[0],....nums1[m-1]-nums2[0]这m个数之间出一个
    # 第三个最小组合同理,每次我们只要对比去掉的那一类中的新的数,以及之前的那些数就可以,以后的也事如此

    # 我们可以用一个优先队列来实现:

    # 这个优先队列的key是组合sum,而value则是该组合的List对象

    def kSmallestPairs(self, nums1, nums2, k):
        # 设minPairs为一个优先队列
        minPairs = []
        # 设res为最后输出的List
        res = []
        # 处理错误输入
        if len(nums1) == 0 or len(nums2) == 0:
            return res
        # 向minPairs中push(u0+v0,[u0,v0]),(u0+v0,[u0,v0]),(u0+v0,[u0,v0]),...
        for i in range(len(nums1)):
            heapq.heappush(minPairs,(nums1[i]+nums2[0],[nums1[i],nums2[0],i,0])) # 最后一个参数代表当前的v到了第几个
        # 循环 k 遍:
        all = len(nums1)*len(nums2)
        if all < k:
            k = all
        while len(res) < k:        
        #     mi 为 minPairs的pop出的元素
            mi = heapq.heappop(minPairs)
            u_min = mi[1][2]
            v_index = mi[1][3]
            pair = mi[1][:2]
        #     res中添加mi中的value部分
            res.append(pair)
        #     u_min 为min的组合中u的值
        #     如果v(下一个)存在,在 minPais中push进 value = [u_min,v(下一个)],key=u_min+v(下一个)
            if v_index+1 < len(nums2):
                heapq.heappush(minPairs,(nums1[u_min]+nums2[v_index+1],[nums1[u_min],nums2[v_index+1],u_min,v_index+1]))
        # 返回res
        return res

练习3 LeetCode - 264. Ugly Number II

超级丑数的简单版

    # 超级丑数的简单版

    # 这个版本是可以通过的,超级丑数同样可以利用相同的方法实现

    # 输入:n
    # 输出:第n个丑数
    def nthUglyNumber(self, n):
        # 设minPq为一个优先队列 (qian)
        minPq = [(2,[0,2]),(3,[0,3]),(5,[0,5])]
        # 设res是一个结果集,res一开始只含有1
        res = [1]
        # 当res的长度小于n时循环:
        while len(res) < n:
        #     mi 为 minPq pop的结果,当前的最小值
            mi = heapq.heappop(minPq)
        #     如果mi和res的最后一位不相同,则填入结果列表
            if mi[0] != res[-1]:
                res.append(mi[0])
        #    当前mi的计算位加1,代表的是当前是和res中的第几个数相乘
            min_idx =  mi[1][0] + 1
        #  向minPq中填入一个当前对应最小的组合
            if mi[1][1] == 2:
                heapq.heappush(minPq,(res[min_idx]*2,[min_idx,2]))                
            elif mi[1][1] == 3:
                heapq.heappush(minPq,(res[min_idx]*3,[min_idx,3]))
            elif mi[1][1] == 5:
                heapq.heappush(minPq,(res[min_idx]*5,[min_idx,5]))
        return res[-1]

练习4 LeetCode - 313. Super Ugly Number
    # 输入:前k个数,primes一个数组
    # 输出:满足超级丑数定义的第k个数

    # 首先要明白如何计算获得超级丑数:下一个超级丑数 = (当前的丑数列表中的某一个数 * primes中的某一个数)
    # 并且这个数要尽可能的小,这也就是为什么我们要维护一个minlist的原因了

    # 思路:

    # 设 res 为一个列表
    # while res.length < k 则
    #     设 minlist 为一个列表
    #     遍历 i 为 0 to primes.length:
    #         遍历 r in res:
    #             如果 primes[i]*r 小于 minlist[i] 则:
    #                 minlist[i] = primesp[i]*r
    #     设 min 为minlist中的最小值
    #     在res的尾部添加min
    # 返回res

    # 使用用于 heap 对算法进行优化:

    # 主要开销在生成 minlist 的步骤,但是其中每次计算minlist有大量的计算是重复的
    # 因为每次计算后,res长度加1,记新填入的元素为item。则minlist = minlist + (item* p in primes)
    # 我们使用一个小顶堆或者抽象程度更高的优先队列,minheap 来维护这个最小值范围
    # 用优先队列的思路,我们可以每次计算 primes.length 个新元素,并推进小顶堆中,调整小顶堆,并输入pop出当前的最小值
    # 这样就大大的减少了算法的复杂度

    # 优化:

    # 我们假设优先队列是由堆实现的,那么其所有操作都可以在O(lgn)内完成


    def nthSuperUglyNumber(self, k, primes):
        # 当 primes 为空的时候,返回1
        if len(primes) == 0:
            return 1
        # 设 res 为一个列表
        res = [1]
        # 设 reslast 为 res 中新添加的那设 
        reslast = 1
        # minPq 为一个优先队列,按照从小到大的优先排列
        minPq = []
        # while res.length < k 则
        while len(res) < k:
        #     遍历 i 为 0 to primes.length:
            for i in range(len(primes)):
        #         向 minPq 中 push reslast*primes[i]   向堆中push元素,时间复杂度为O(lgn)
                heapq.heappush(minPq,reslast*primes[i])
        #     设 mi 为 minPq 的最小值,minPq pop 出最小值  在此步,堆移走了根节点,需要进行一次调整堆,时间复杂度为O(lgn)
            mi = heapq.heappop(minPq)
        #      防止出现防止本次的最小值重复,这种情况是可能发生的,2*7 和 7*2                          
        #     在res的尾部添加mi
            if mi != reslast:
                res.append(mi)
        #     更新 reslast的值
            reslast = mi      
        # 返回res
        return reslast

很可惜,最小堆的实现虽然直观简单,但是并不能通过时限要求,需要结合动态规划来解题

    def nthSuperUglyNumber(self,n,primes):
        # 设minPq为一个优先队列 (qian)
        minPq = []
        for i in range(len(primes)):
            minPq.append((primes[i],[0,i]))
        # 设res是一个结果集,res一开始只含有1
        res = [1]
        # 当res的长度小于n时循环:
        while len(res) < n:
        #     mi 为 minPq pop的结果,当前的最小值
            mi = heapq.heappop(minPq)
        #     如果mi和res的最后一位不相同,则填入结果列表
            if mi[0] != res[-1]:
                res.append(mi[0])
        #    当前mi的计算位加1,代表的是当前是和res中的第几个数相乘
            min_idx =  mi[1][0] + 1
            p_idx = mi[1][1]
        #  向minPq中填入一个当前对应最小的组合
            heapq.heappush(minPq,(res[min_idx]*primes[p_idx],[min_idx,p_idx]))
        return res[-1]

练习5 LeetCode - 355. Design Twitter

这道题是一道程序设计类题目,主要体会优先队列在实际应用中的作用

import java.util.*;

/**
 * Created by 13407 on 2017/5/13.
 * 定义一个简单版本的推特,用户可以发布推特,关注或者取关某一个用户,在新反馈中,获取10条最新的tweet
 */
public class Twitter {

    /* 用户类 */
    public class User{
        // 用户id
        public Integer userId;
        // 用户关注的用户
        public HashMap<Integer,User> followees;
        // 该用户发布的tweets
        public List<Tweet> tweets;
        // 构造函数
        public User(Integer userId){
            this.userId = userId;

            this.followees = new HashMap<>();
            // 本人关注列表中要有本人,因为发布的内容中一定会有自己的
            this.followees.put(this.userId,this);

            this.tweets = new LinkedList<>();
        }
    }
    /* Tweet类 */
    public class Tweet{
        // 当前推文的Id
        public Integer tweetId;
        // 当前推文的所属用户
        public Integer ownerId;
        // 当前推文的时间
        public Integer time;
        // 当前tweet属于该用户的第几个推文
        public Integer index;
        // 构造函数
        public Tweet(Integer tweetId,Integer ownerId,Integer time,Integer index){
            this.tweetId = tweetId;
            this.ownerId = ownerId;
            this.time = time;
            this.index = index;
        }
    }

    HashMap<Integer,User> userHashMap;
    Integer time;
    /** Initialize your data structure here. */
    public Twitter() {
        /* 初始化一个哈希列表来保存当前所有的用户,好处是哈希的存取时间为O(1) */
        userHashMap = new HashMap<>();
        /* 初始化一个时间戳,每次有用户创建Tweet,时间戳就+1 */
        time = 0;
    }
    /** Compose a new tweet. */
    /*  组成一个新的tweet */
    public void postTweet(int userId, int tweetId) {
        // 判断该用户是否存在,如果不存在,则创建该用户,并添加到用户列表里
        User user;
        if (!checkUserExit(userId)){
            user = new User(userId);
            userHashMap.put(userId,user);
        } else {
            user = userHashMap.get(userId);
        }
        // 创建一个Tweet,添加入该用户的Tweets列表里,然后累加时间戳
        int index;
        if (user.tweets.size() == 0){
            index = 0;
        }else{
            index = user.tweets.get(0).index+1;
        }
        Tweet tweet = new Tweet(tweetId,userId,time++,index);
        user.tweets.add(0,tweet);
        return;
    }
    /* 接受10条 最新最近的消息,每一条item必须是由user订阅的用户或者自身发布的,获取的顺序是从最近到最远*/
    /* 这样看就是一个求前n小数的问题了 */
    /** Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. */
    public List<Integer> getNewsFeed(int userId) {
        List<Integer> tweets = new ArrayList<>();
        // 处理错误输入
        // 如果说当前没有任何用户,或者不存在当前的userID
        if (userHashMap.isEmpty() || !userHashMap.containsKey(userId)) return tweets;

        // 获取当前的用户
        User user = userHashMap.get(userId);
        // 使用一个优先队列,大小为该用户的关注用户的大小,时间越早(小)越优先
        PriorityQueue<Tweet> queue = new PriorityQueue<>(user.followees.size(),(a,b)->(b.time-a.time));
        for(Integer followee:user.followees.keySet()){
            // 将每一个关注人的最新微博填入queue中,在此注意,有些用户还没有发布任何推文
            if(!user.followees.get(followee).tweets.isEmpty()) {
                Tweet t = user.followees.get(followee).tweets.get(0);
                queue.add(t);
            }
        }
        // 设n为计数,当tweets为返回的结果,如果queue为空或者 n == 10时,则返回res
        // 否则就将queue中时间最小的poll出,然后插入这个用户中新的最小的
        int n = 0;
        while (n<10 && !queue.isEmpty()){
            Tweet t = queue.poll();
            tweets.add(t.tweetId);
            n++;
            // 如果t不是最后一个,那么就取其之后的那个假如queue中
            User cur = userHashMap.get(t.ownerId);
            if (t.index != 0){
                int newIndex = t.index-1;
                newIndex = cur.tweets.size()-1 - newIndex;
                Tweet newTweet = cur.tweets.get(newIndex);
                queue.add(newTweet);
            }
        }
        return tweets;
    }

    /** Follower follows a followee. If the operation is invalid, it should be a no-op. */
    /* 关注一个用户 */
    public void follow(int followerId, int followeeId) {
        // follower和followee的Id,如果不存在,就创建
        User follower;
        User followee;
        if(!checkUserExit(followerId)){
            follower = new User(followerId);
            userHashMap.put(followerId,follower);
        } else {
            follower = userHashMap.get(followerId);
        }
        if (!checkUserExit(followeeId)){
            followee = new User(followeeId);
            userHashMap.put(followeeId,followee);
        } else {
            followee = userHashMap.get(followeeId);
        }
        // 给follower用户的followee列表添加该用户
        follower.followees.put(followeeId,followee);
    }

    /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */
    /* 取关一个用户 */
    public void unfollow(int followerId, int followeeId) {
        // 如果followerId不存在,就直接返回
        if (!checkUserExit(followerId)){
            return;
        } else {
            // 否则,搜索查看该用户的followees集合,对应删除该用户,同时注意,不可以unfollow自身
            User follwer = userHashMap.get(followerId);
            if (followerId != followeeId) follwer.followees.remove(followeeId);
        }
    }

    /* 用来保存当前用户的集合中检查看,是否存在此用户 */
    public boolean checkUserExit(Integer userId){
        if(userHashMap.containsKey(userId)) {
            return true;
        } else {
            return false;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值