腾讯编程题

本文记录了一次腾讯编程题,介绍了摘要搜索的规则,包括名词定义、搜索规则等。给出了待实现接口和测试用例,还展示了个人编写的程序,涉及摘要统计、LRU缓存、摘要对象等内容,要求考虑并发场景保证数据安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

记一次腾讯编程题,写了3个半小时,感觉还挺好。代表我现在的水平了。希望以后每次看都会有新的想法。

笔试题目说明

名词说明

单词
由大小写英文字母组成,不含其它字符。

摘要
由多个单词和语句结束符组成。
一条语句内的单词间用一个空格分隔;
摘要中若语句结束,以英文逗号或句号结尾。

搜索次数
标识该摘要被搜索次数。
搜索次数大于等于0。

搜索关键词
由一个关键单词组成,不包含2个及其以上的单词.

全词匹配
搜索的关键词与摘要中的单词完全一致,不存在被包含关系。

包含匹配
关键词与摘要中的单词为包含关系,如关键单词operation,被cooperation包含。

搜索规则
1、搜索出所有包含关键单词的摘要。
2、每次搜索时,若该摘要被搜索到关键词,则其搜索次数自动加1。
3、输出的摘要间以#符号间隔。
4、收索次数最高的摘要排在最前面。
5、若几份摘要的搜索次数相同,则输出规则按以下方式进行排序:
     A、比较全词匹配的关键字次数,按次数由大到小输出摘要。
     B、若全词匹配的关键字次数相同,则比较 包含匹配次数,次数多的优先输出。
     C、若全词匹配与包含匹配次数相同,则对摘要按由小到大进行排序输出。
6、进行搜索时,不区分大小写。

待实现接口
1、boolean addAbstract(String strAbstract,integer iCount)
    strAbstract: 摘要输入入参,同一份摘要不会重复输入。
     iCount:摘要的搜索次数。
    若入参为摘要空,直接返回false。


2、String searchAbstract(String strKeyWord)
     strKeyWord:搜索的关键单词。
    返回值为搜索符合关键字的摘要,每份摘要间以”#”号间隔。

举例
输入内容:
addAbstract(“These are good books .You can choose one book from them.”,9);
addAbstarct(“You can search books from Google.”,8);
addAbstarct(“Search and preview millions of books from libraries and publishers worldwide using Google Book Search.”,8);
addAbstarct(“Go to Google Books Home. Advanced Book Search, About Google ... All books.”,6)
addAbstarct(“Bookshelf provides free access to books and documents in life science and healthcare.”,7)

第一次搜索 关键词为BOOK

输出:
These are good books .You can choose one book from them.# Search and preview millions of books from libraries and publishers worldwide using Google Book Search.# You can search books from Google.# Bookshelf provides free access to books and documents in life science and healthcare.# Go to Google Books Home. Advanced Book Search, About Google ... All books.
 
第二次搜索 关键词为Google 

输出:
You can search books from Google .# Search and preview millions of books from libraries and publishers worldwide using Google Book Search .#Go to Google Books Home. Advanced Book Search, About Google ... All books.


规格
0<=摘要个数范围<=200
1<=摘要所含单词个数<=50
1<=单词所含字母数<=50
超出如上约束的输入认为是错误的,对应接口返回失败


其他要求:

已经提供初步的代码框架及测试用例,请在此框架上继续完成代码,并保证基本用例通过。

可以根据实际需要自由增加类等数据结构定义

要求考虑并发场景保证数据安全。

不得修改原有的接口定义。

不限制其他类库的使用。

注意遵从编程规范,圈复杂度不超过10。

尽善尽美地发挥,将工程师的思想发挥出来

实现接口:

package com.tencent.ied.bk;

public class CSearchAbstract {

    /**
     * @param strAbstract
     *            标识输入的摘要。
     * @param iCount
     *            标识该摘要搜索的次数。
     * @return 成功返回true,异常返回FALSE。
     */
    public boolean addAbstract(String strAbstract, int iCount) {
        return true;
    }

    /**
     * @param strKeyWord
     *            搜索关键词。
     * @return 返回搜索到的摘要。
     */
    public String searchAbstract(String strKeyWord) {
        return  "";
    }
}
 

 

测试用例:

package com.tencent.ied.bk.unittest;

import static org.junit.Assert.assertEquals;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.tencent.ied.bk.CSearchAbstract;

public class CTestSearchAbstract {

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void testFindFlushPattern() {
        CSearchAbstract objSA = new CSearchAbstract();
        objSA.addAbstract("These are good books .You can choose one book from them.", 9);
        objSA.addAbstract("You can search books from Google.", 8);
        objSA.addAbstract("Search and preview millions of books from libraries and publishers worldwide using Google Book Search.", 8);
        objSA.addAbstract("Go to Google Books Home. Advanced Book Search, About Google ... All books.", 6);
        objSA.addAbstract("Bookshelf provides free access to books and documents in life science and healthcare.", 7);

        String strRst = objSA.searchAbstract("BOOK");
        String rstExpt = "These are good books .You can choose one book from them.# Search and preview millions " +
        "of books from libraries and publishers worldwide using Google Book Search.# You can search books from Google.# Bookshelf provides" +
        " free access to books and documents in life science and healthcare.# Go to Google Books Home. Advanced Book Search, About " +
        "Google ... All books.";
        assertEquals(strRst,rstExpt);
        strRst = objSA.searchAbstract("google");
        rstExpt = "You can search books from Google.# Search and preview millions of books from libraries and publishers worldwide using "
       + "Google Book Search.# Go to Google Books Home. Advanced Book Search, About Google ... All books.";

       assertEquals(strRst,rstExpt);
    }
}
 

 个人编写的程序:

package com.insightfullogic.java8;


import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class CSearchAbstract{
    //0<=摘要个数范围<=200, 缓存摘要ID对应摘要对象
    private static LRUCache ABSTRACT = new LRUCache(200);
    //全局摘要ID
    private static AtomicInteger INC = new AtomicInteger(1);
    //静态分隔符
    private static String SEPA = "# ";

    //关键词map,内部记录包含摘要ID的集合
    private static ConcurrentHashMap<String, Set<Integer>> WORD_MAP = new ConcurrentHashMap<>();

    /**
     * @param strAbstract 标识输入的摘要。
     * @param iCount      标识该摘要搜索的次数。
     * @return 成功返回true, 异常返回FALSE。
     */
    public boolean addAbstract(String strAbstract, int iCount){
        if (strAbstract == null || strAbstract.length() < 1){
            return false;
        }
        return statWordConut(strAbstract, iCount);
    }

    /**
     * @param strKeyWord 搜索关键词。
     * @return 返回搜索到的摘要。
     */
    public String searchAbstract(String strKeyWord){
        if (strKeyWord == null || strKeyWord.length() < 1){
            return null;
        }
        //获取摘要ID集合,setFull全词匹配集合,setInclude包含匹配集合
        Set<Integer> setAll = Sets.newHashSet();
        strKeyWord = strKeyWord.toLowerCase();
        for (Map.Entry<String, Set<Integer>> entry : WORD_MAP.entrySet()){
             if (entry.getKey().contains(strKeyWord)){
                 setAll.addAll(entry.getValue());
            }
        }

        //获取摘要包含匹配的list
        LinkedList<AbstractBean> listAll = Lists.newLinkedList();
        setAll.forEach(id -> {
            if (ABSTRACT.get(id) != null){
                listAll.add(ABSTRACT.get(id));
                //增加摘要搜索次数
                ABSTRACT.get(id).incr();
            }
        });

        //返回包含匹配的结果排序
        // TODO LRUCache会把最新操作数据放到前面,如果搜索规则结果排序规则都未命中,最后写入的会优先输出。如果要求先入优先输出,这里可以倒转下
        // Collections.reverse(listAll);
        listAll.sort(getAbstractBeanComparator(strKeyWord));

        //构建返回内容
        StringBuffer sb = new StringBuffer();
        listAll.stream().forEach(o -> sb.append(o.getContent()).append(SEPA));
        sb.delete(sb.length() - SEPA.length(), sb.length());
        return sb.toString();
    }

    /**
     * 搜索规则结果排序规则
     * <li>1.搜索次数最高的摘要排在最前面。</li>
     * <li>2.比较全词匹配的关键字次数,按次数由大到小输出摘要。</li>
     * <li>3.若全词匹配的关键字次数相同,则比较 包含匹配次数,次数多的优先输出。</li>
     * <li>4.若全词匹配与包含匹配次数相同,则对摘要按由小到大进行排序输出。</li>
     * @param strKeyWord
     * @return
     */
    private Comparator<AbstractBean> getAbstractBeanComparator(String strKeyWord){
        return (o1, o2) -> {
            AbstractBean abstractBean1 = ABSTRACT.get(o1.getId());
            AbstractBean abstractBean2 = ABSTRACT.get(o2.getId());

            //1.搜索次数最高的摘要排在最前面
            int count1 = abstractBean1.getCount().intValue();
            int count2 = abstractBean2.getCount().intValue();
            if (count1 != count2){
                return count2 - count1;
            }

            //2.比较全词匹配的关键字次数,按次数由大到小输出摘要。
            int fullCount1 = abstractBean1.wordCountMap.entrySet()
                    .stream()
                    .filter(o -> o.getKey().equals(strKeyWord))
                    .mapToInt(Map.Entry :: getValue)
                    .sum();
            int fullCount2 = abstractBean2.wordCountMap.entrySet()
                    .stream()
                    .filter(o -> o.getKey().equals(strKeyWord))
                    .mapToInt(Map.Entry :: getValue)
                    .sum();
            if (fullCount1 != fullCount2){
                return fullCount2 - fullCount1;
            }

            //3.若全词匹配的关键字次数相同,则比较 包含匹配次数,次数多的优先输出。
            int includeCount1 = abstractBean1.wordCountMap.entrySet()
                    .stream()
                    .filter(o -> o.getKey().contains(strKeyWord) && !o.getKey().equals(strKeyWord))
                    .mapToInt(Map.Entry :: getValue)
                    .sum();
            int includeCount2 = abstractBean2.wordCountMap.entrySet()
                    .stream()
                    .filter(o -> o.getKey().contains(strKeyWord) && !o.getKey().equals(strKeyWord))
                    .mapToInt(Map.Entry :: getValue)
                    .sum();
            if (includeCount1 != includeCount2){
                return includeCount2 - includeCount1;
            }

            //4.若全词匹配与包含匹配次数相同,则对摘要按由小到大进行排序输出。
            return abstractBean1.getContent().length() - abstractBean2.getContent().length();
        };
    }

    /**
     * 统计输入摘要中关键单词,按单词出现次数从前到后统计
     *
     * @param str
     */
    public static boolean statWordConut(String str, int iCount){
        //统计摘要关键词和出现次数map
        HashMap<String, Integer> map = new HashMap<>(str.length(), 0.75f);
        //删除标点字符,\W意思是非单词字符;
        String[] array = str.split("\\W+");

        Integer tmp;
        for (String s : array){
            //1<=单词所含字母数<=50
            if (s.length() > 50){
                continue;
            }
            s = s.toLowerCase();
            tmp = map.get(s);
            map.put(s, tmp == null ? 1 : tmp + 1);
        }

        //1<=摘要所含单词个数<=50 TODO 这里也可以只保留摘要按数量统计的前50个单词
        if (map.isEmpty() || map.size() > 50){
            return false;
        }

        List<Map.Entry<String, Integer>> list = map.entrySet()
                .stream()
                .collect(Collectors.toList());
        list.sort((o1, o2) -> o2.getValue() - o1.getValue());

        //分配摘要ID,摘要记录到LRU缓存中
        Integer id = INC.getAndIncrement();
        ABSTRACT.put(id, new AbstractBean(id, new AtomicInteger(iCount), str, map));

        list.stream().forEach(obj -> {
            String key = obj.getKey().toLowerCase();
            if (WORD_MAP.containsKey(key)){
                String finalKey = key;
                //TODO 这里要直接操作value对象,时间有限没有找到底层api
                synchronized (WORD_MAP){
                    WORD_MAP.put(key, new HashSet<Integer>(){{
                        add(id);
                        addAll(WORD_MAP.get(finalKey));
                    }});
                }

            }else{
                WORD_MAP.put(key, Sets.newHashSet(id));
            }
        });

        return true;
    }

    /**
     * LRU(Least recently used最近最少使用)缓存,支持get和put操作,并且两者的时间复杂度为O(1)
     * 实现:参考LinkedHashMap
     */
    public static class LRUCache{
        private LinkedHashMap<Integer, AbstractBean> map;
        private final int capacity;

        public LRUCache(int capacity){
            this.capacity = capacity;
            //accessOrder = true,按访问顺序排序,访问后会移到链尾(tail)
            map = new LinkedHashMap<Integer, AbstractBean>(capacity, 0.75f, true){
                //LinkedHashMap封装的淘汰方法,当put新值方法返回true时,就移除该map中最老的键和值
                protected boolean removeEldestEntry(Map.Entry eldest){
                    //如果Map的size大于设定的最大长度,返回true,再新加入对象时删除最少使用对象(head)
                    return size() > capacity;
                }
            };
        }

        public AbstractBean get(int key){
            return map.getOrDefault(key, null);
        }

        public void put(int key, AbstractBean value){
            map.put(key, value);
        }

    }

    /**
     * 摘要对象
     */
    static class AbstractBean{
        /**
         * 摘要ID
         */
        private Integer id;

        /**
         * 摘要搜索次数
         */
        private AtomicInteger count;

        /**
         * 摘要内容
         */
        private String content;

        /**
         * 单词在摘要中出现次数,key为小写字母
         */
        private Map<String, Integer> wordCountMap;

        public AbstractBean(Integer id, AtomicInteger count, String content, Map<String, Integer> wordCountMap){
            this.id = id;
            this.count = count;
            this.content = content;
            this.wordCountMap = wordCountMap;
        }

        /**
         * 自旋锁增加摘要搜索次数
         */
        public void incr(){
            for(;;){
                int i = count.get();
                boolean flag = count.compareAndSet(i, ++i);
                if(flag){
                    break;
                }
            }
        }

        public Integer getId(){
            return id;
        }

        public void setId(Integer id){
            this.id = id;
        }

        public AtomicInteger getCount(){
            return count;
        }

        public void setCount(AtomicInteger count){
            this.count = count;
        }

        public String getContent(){
            return content;
        }

        public void setContent(String content){
            this.content = content;
        }

        public Map<String, Integer> getWordCountMap(){
            return wordCountMap;
        }

        public void setWordCountMap(Map<String, Integer> wordCountMap){
            this.wordCountMap = wordCountMap;
        }
    }

}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值