一题算法 | 子域名访问计数

268 阅读4分钟

题目

一个网站域名,如"discuss.leetcode.com",包含了多个子域名。作为顶级域名,常用的有"com",下一级则有"leetcode.com",最低的一级为"discuss.leetcode.com"。当我们访问域名"discuss.leetcode.com"时,也同时访问了其父域名"leetcode.com"以及顶级域名 "com"。 给定一个带访问次数和域名的组合,要求分别计算每个域名被访问的次数。其格式为访问次数+空格+地址,例如:"9001 discuss.leetcode.com"。

示例

输入

["9001 discuss.leetcode.com"]

输出:

["9001 discuss.leetcode.com", "9001 leetcode.com", "9001 com"]

说明:

例子中仅包含一个网站域名:"discuss.leetcode.com"。按照前文假设,子域名"leetcode.com"和"com"都会被访问,所以它们都被访问了9001次。

这道题目相对来说还是比较简单的,看到这种String、这种格式,平头哥首先想到的是Stringsubstring()split()两个方法将string转成数组或者新的字符串,事实证明平头哥的想法还阔以,居然居于这两个方法实现了子域名访问统计,真是瞎猫碰上死耗子。但是平头哥是一名具有专研精神的非专业搬砖员,经过平头哥不严格的测试得出,subString()方法比split()方法效率高出一大截。这种不知道有多少层的东西用递归或者循环总归是不会错的,为什么这么说呢?因为以平头哥的智商也想不出其他方式啦。平头哥基于递归或者循环实现了三种解决方式,那平头哥就来给小伙伴们介绍介绍这三种方式。

while 方式的实现

/**
     * for +while 方法
     * @param cpdomains
     * @return
     */
    public static List<String> whileSolution(String[] cpdomains){
        // 用来存返回结果
        List<String> list = new ArrayList<String>();
        // 用来计数统计
        Map<String, Integer> map = new HashMap<>();
        for (String cpdomain : cpdomains) {
//            // 先截取访问数和域名
//            String[] temp = cpdomain.split(" ");
//            // 访问数
//            int viewCount = Integer.valueOf(temp[0]);
//            // 域名
//            String domain = temp[1];
            int indexOf = cpdomain.indexOf(" ");
            // 访问数
            int viewCount = Integer.valueOf(cpdomain.substring(0, indexOf));
            // 域名
            String domain = cpdomain.substring(indexOf + 1);

            while (domain.indexOf(".") !=-1){
                mapPutOrAdd(map,domain,viewCount);
                domain = domain.substring(domain.indexOf(".")+1);
            }
            // 最后一个顶级域名
            mapPutOrAdd(map,domain,viewCount);
        }
        for (String key :map.keySet())
            list.add(map.get(key)+" "+key);
        return list;
    }

利用 while 循环来判断域名中是否包含.,如果包含.,则说明只要有一个子域名,因为indexOf()方法是从左往右查找的,只要找到就返回.所在字符串的位置,没有找到就返回-1。如果找到了,就利用substring()方法从返回位置的后一位开始截取到字符串的最后一位,作为新的字符串,继续走上面的流程,知道循环结束。

for 循环方式实现

/**
     * 三层for 循环
     * @param cpdomains
     * @return
     */
    public static List<String> forSolution(String[] cpdomains) {

        List<String> list = new ArrayList<String>();
        Map<String, Integer> map = new HashMap<>();
        // 遍历数组
        for (String cpdomain : cpdomains) {
//            // 先截取访问数和域名
//            String[] temp = cpdomain.split(" ");
//            // 访问数
//            int viewCount = Integer.valueOf(temp[0]);
//            // 域名
//            String domain = temp[1];
            int indexOf = cpdomain.indexOf(" ");
            // 访问数
            int viewCount = Integer.valueOf(cpdomain.substring(0, indexOf));
            // 域名
            String domain = cpdomain.substring(indexOf + 1);

            String[] domains = domain.split("\\.");
            if (domains.length == 0) continue;
            // 拼接出多级域名,都塞入map
            for (int i = 0; i < domains.length; i++) {
                StringBuilder key = new StringBuilder(domains[i]);
                for (int j = i + 1; j < domains.length; j++) {
                    key.append('.');
                    key.append(domains[j]);
                }
                mapPutOrAdd(map,key.toString(), viewCount);
            }
        }
        for (String key :map.keySet())
            list.add(map.get(key)+" "+key);

        return list;
    }

for循环的实现是平头哥带来的三种实现中,效率最低的。所以小伙伴们能不用双层for就坚决不用。for循环最要利用split()方法将域名分离出现,例如discuss.leetcode.com分成discussleetcodecom。然后双层遍历将域名组合起来,最外层第一次遍历完之后得到discuss.leetcode.com,第二次遍历完之后得到leetcode.com,第三次遍历完之后得到com。这样最后也得到了每个域名的访问次数。

递归方法实现

public static List<String> recurveSolution(String[] cpdomains){
        List<String> list = new ArrayList<String>();
        Map<String, Integer> map = new HashMap<>();
        // 遍历数组
        for (String cpdomain : cpdomains) {
            // 先截取访问数和域名
            String[] temp = cpdomain.split(" ");
            // 访问数
            int viewCount = Integer.valueOf(temp[0]);
            // 域名
            String domain = temp[1];
            // 递归方法
            recurve(map,domain,viewCount);
        }
        for (String key :map.keySet())
            list.add(map.get(key)+" "+key);

        return list;
    }

    /**
     * 递归方法
     * @param map map集合
     * @param domain 网站域名
     * @param viewCount 访问数
     * @return
     */
    public static Map<String,Integer> recurve(Map<String,Integer> map,String domain,Integer viewCount){
        if (domain.indexOf(".") != -1){
            mapPutOrAdd(map,domain,viewCount);
            return recurve(map, domain.substring(domain.indexOf(".")+1),viewCount);
        }else {
            mapPutOrAdd(map,domain,viewCount);
            return map;
        }

    }

    private static void mapPutOrAdd(Map<String,Integer> map, String key, Integer val) {
        if (map.containsKey(key)) {
            map.put(key,map.get(key) + val);
        } else {
            map.put(key,val);
        }
    }

递归方法跟while方式差不多,效率也不相上下。因为我们不知道域名有多少层,用递归来做是非常合适的,因为递归就是为这个而生。每次递归的时候我们现将域名添加到map中,跟while一样我们也用indexOf(".")来判断域名中是否含有子域名,如果存在,则截取新的域名继续调用自身,直到没有子域名为止。

以上就是平头哥给小伙伴们带来关于子域名访问计数的算法分享,不知道是否给小伙伴们讲明白?对于这道算法题,小伙伴们你是否还有其他思路呢?请在留言区与其他的小伙伴分享吧。

扫码关注公众号(搜索公众号:平头哥的技术博文)一起交流学习呗

扫码关注公众号(搜索公众号:平头哥的技术博文)一起交流学习呗