LeetCode高频题:设计一个股票推荐系统,自动根据注册用户的关注情况进行推荐,询问时,会推荐那个人多少只他还没有关注的股票

LeetCode高频题:设计一个股票推荐系统,自动根据注册用户的关注情况进行推荐,询问时,会推荐那个人多少只他还没有关注的股票?

提示:本题是系列LeetCode的150道高频题,你未来遇到的互联网大厂的笔试和面试考题,基本都是从这上面改编而来的题目
互联网大厂们在公司养了一大批ACM竞赛的大佬们,吃完饭就是设计考题,然后去考应聘人员,你要做的就是学基础树结构与算法,然后打通任督二脉,以应对波云诡谲的大厂笔试面试题!
你要是不扎实学习数据结构与算法,好好动手手撕代码,锻炼解题能力,你可能会在笔试面试过程中,连题目都看不懂!比如华为,字节啥的,足够让你读不懂题
在这里插入图片描述
基础知识:并查集
【1】并查集:解决连通性问题的利器:并查集
我也拿并查集解决过相关连通性的问题,类似本题:
【2】图的最小生成树:Kruskal算法–并查集的经典应用,解决连通性问题
【3】岛问题:1是陆地,0是水域,请问矩阵arr中有几个岛,并查集并行加速


文章目录

题目

完成设计一个股票推荐系统,该系统会自动根据注册用户的关注情况进行推荐。

例如,对于通知关注Zoom和Warmart的人而言,推荐算法会根据它的信息认为,关注了Zoom的人,可能对Warmart也感兴趣,反之亦然,关注了Warmart的人,可能对Zoom也感兴趣。

因此,对于另一个关注了Warmart,但是没有关注Zoom的人来说,该系统就会推荐他关注Zoom。
请注意,该系统会计算连锁的信息,例如,假设在刚刚的前提下(存在同时关注Warmart和Zoom的人),存在一个同时关注Zoom和Apple的人,那么对于另一个只关注了Warmart的人,该系统会同时推荐他Zoom和Apple。

现在给出一些人的注册信息和询问,你需要回答每次询问时,推荐系统会推荐那个人多少只他还没有关注的股票?

输入描述:
第一行输入一个整数q,代表操作次数
接下来输入q次操作,
共有2种操作:
1,注册,格式为:
第一行输入1 name n,代表1个名字为anme的人注册了该系统,它关注了n只股票
第二行输入n个仅仅包含英文名字母字符的str,代表这个人关注的股票名字
保证不会重复注册,这n个字符串互不相同。

2,查询,格式为:
仅有一行,输入为2 name,代表询问该推荐系统,会给这个人推荐多少只股票。

1<=q<=10000
1<=n<=5
所有字符串均只包含引文字母,且长度不会超过10.
保证至少有1次询问。

输出描述:
对于每次询问:
若查询的人不存在则输“error”。


一、审题

示例:
q=5
拢共要干5次操作

2 Bob
第一次询问时,系统内还没有名字为Bob的用户,输出error

1 Alice 2
Apple Zoom

2 Alice
第2次询问时,系统不会给Alice推荐任何股票,输出0
因为没别的用户,并查集查了集合,合并然后也不行

1 Bob 2
Apple Microsoft

2 Bob
第三次查询,由于Bob也注册了,所以看看并查集中的俩集合,把代表们依次查一遍,看看需要合并的合并,不需要的不管,这样算前后差,得到了1只股票,输出1
这里因为Alice和Bob都关注了APPLE,所以必然要给Bob推荐ZOOM


理解本题的关键是知道推荐系统的连通性,解决连通性的利器:并查集

为啥呢???

题目说:
请注意,该系统会计算连锁的信息,例如,
假设在刚刚的前提下(存在同时关注Warmart和Zoom的人),咱叫这个人名字为A
存在一个同时关注Zoom和Apple的人,咱叫这个人名字为B
那么对于另一个只关注了Warmart的人,咱叫这个人名字为C

该系统会同时推荐他Zoom和Apple。

下面我解释:
在这里插入图片描述
显然C和A是有交集的,可以将AC连同,由于AB有交集,所以AB也需要连同
我们待会用哈希表表示这些股票的话,重复的自动去重,因此合集的有效股票就3只
Warmart,Zoom,Apple
合并之后的数量是3,合并之前C的数量是1
结果ans=3-1=2只
懂?


有了上面的理解,其实你应该明白,每一次查询,都需要把所有集合走一遍,看看没有合并的集合,是否有交集,有就需要合并连通

因此,解决这个题目的关键点在于用:并查集

我之前透彻讲过并查集,也自己多次手撕过并查集

这个玩意,是必须要会的

【1】并查集:解决连通性问题的利器:并查集
我也拿并查集解决过相关连通性的问题,类似本题:
【2】图的最小生成树:Kruskal算法–并查集的经典应用,解决连通性问题
【3】岛问题:1是陆地,0是水域,请问矩阵arr中有几个岛,并查集并行加速

并查集的集合中装的节点Node代表股票
它包裹的value就是股票名字name–字符串

        //并查集的集合中装的节点**Node**代表股票
        //它包裹的value就是股票名字name--字符串
        public static class Node{
            public String name;

            public Node(String s){
                name = s;
            }
        }

我们的并查集数据结构设计为UnionSet
内部有这么几个结构
(1)nodes(哈希表):key–股票名字字符串,value–股票包裹节点Node

(2)parentMap(哈希表):key–股票节点Node,value–它所在集合的代表——这里就是并查集最经典的地方,具体你看上面我写的并查集的基础文章

(3)sizeMap(哈希表):keya–股票代表节点Node,value:它股票节点代表所在集合的节点个数,也就是当前这个股票集合拢共的张数

(4)普通并查集所没有的属性,我为了本题设计的数据结构(哈希表),holder:key–股票主人,value:主人所持股票仓,一个哈希集,立里面放的是所持股票的名字字符串
只需要根据这些股票就能跟踪大集合的所有情况,
比如,我只看C用户的warmart就知道,其实ABC仨都是一个大集合,
第一次用户C进来的时候,C独立成集合,我们查C数量为1
当合并ABC之后,其实再查C的数量,已经是3了

我为啥要设计(4)这个玩意呢???

你看看下面这图,假如A已经注册了Warmart和zoom
在这里插入图片描述
现在又来注册B,形成一个集合(zoom和apple)

now,我需要查一下,B中所有元素,比如,目前zoom已经在哪些集合里面了???
查到zoom在集合A中,我们就需要立马把B和A集合union了

我怎么知道B中的元素是否在集合A中呢?????

很显然需要提前把A的股票仓库用holder保存好,B的股票仓库也是用holder保存
这样遍历holder中B的所有股票名字,看看holder的A股票仓库里面有没有这些名字

如果有,那AB只要合并一次,就可以

okay,有了(1)–(4)这几个属性,我们就可以玩各种并查集的操作了

由于我们是先建并查集,后面根据ACM格式输入注册用户,所以默认构造函数就很简单

        //我们的并查集数据结构设计为**UnionSet**
        public static class UnionSet{
            //所有参数类型统统实在写类型
            //内部有这么几个结构
            //(1)nodes(哈希表):key--股票名字字符串,value--股票包裹节点Node
            public HashMap<String, Node> nodes;
            //(2)parentMap(哈希表):key--股票节点Node,value--它所在集合的**代表**——这里就是并查集最经典的地方,具体你看上面我写的并查集的基础文章
            public HashMap<Node, Node> parentMap;
            //(3)sizeMap(哈希表):keya--股票代表节点Node,value:它股票节点代表所在集合的节点个数,也就是当前这个股票集合拢共的张数
            public HashMap<Node, Integer> sizeMap;
            //(4)**普通并查集所没有的属性**,我为了本题设计的数据结构(哈希表),**holder**:key--股票主人,value:主人所持股票仓,
            // 一个哈希集,立里面放的是所持股票的名字字符串
            public HashMap<String, HashSet<String>> holder;

            //构造函数
            public UnionSet(){
                nodes = new HashMap<>();
                parentMap = new HashMap<>();
                sizeMap = new HashMap<>();
                holder = new HashMap<>();//为了方便ACM格式,输入,暂时就先置空
            }

代码接着下面的放

一切都是为了o(1)速度查a和b是否同属一个集合?isSameSet(a,b)?中间必然要用一个函数过度,查单个股票它所在集合的代表是谁?findFather(a)

findFather(a)查节点Node a的所在集合的代表是谁?将沿途所有的节点扁平化,全挂代表上

这个思想基础文章里面我说过了哦,很巧妙的,为了加速查询o(1)速度,需要扁平化所有节点,直接他们挂在集合的代表上面

            //## 一切都是为了o(1)速度查a和b是否同属一个集合?isSameSet(a,b)?中间必然要用一个函数过度,
            // 查单个股票它所在集合的代表是谁?findFather(a)
            //### findFather(a)是谁?将沿途所有的节点扁平化,全挂代表上
            public Node findFather(Node cur){
                //扁平化所有节点,挂到代表cur上
                Stack<Node> path = new Stack<>();//沿途收集

                while (parentMap.get(cur) != cur){
                    path.push(cur);//沿途挂接的加入path
                    cur = parentMap.get(cur);//没找到代表就继续往上
                }
                //结果就是cur作为代表

                while (!path.isEmpty()){
                    parentMap.put(path.pop(), cur);//改挂代表,下一次就一次找到了
                }

                return cur;
            }

isSameSet(a,b)问,ab在同一个集合吗?

            //### isSameSet(a,b)问,股票ab在同一个集合吗?
            public boolean isSameSet(String a, String b){
                if (!nodes.containsKey(a) || !nodes.containsKey(b)) return false;

                //存在了俩
                return findFather(nodes.get(a)) == findFather(nodes.get(b));//俩地址相等就是同一个代表,在同一个集合
            }

然后看看是否需要合并ab所在的集合?union(a,b)

            //## 然后看看是否需要合并ab所在的集合?union(a,b)
            public void union(String a, String b){
                if (!nodes.containsKey(a) || !nodes.containsKey(b)) return ;

                //存在了俩,才看看是否同属一个集合?
                Node aHead = findFather(nodes.get(a));
                Node bHead = findFather(nodes.get(b));

                //在同一个集合就算了
                if (aHead != bHead){
                    Integer aSize = sizeMap.get(aHead);
                    Integer bSize = sizeMap.get(bHead);
                    //合并策略:小集合挂大集合
                    Node big = aSize >= bSize ? aHead : bHead;
                    Node small = big == aHead ? bHead : aHead;

                    parentMap.put(small, big);//小集合的代表换为big,挂好了,合并好了也
                    sizeMap.put(big, aSize + bSize);//大集合数量变了
                    sizeMap.remove(bSize);//小集合消失
                }
            }

可以查当前并查集的集合数量:getSetNum()–本题中好像这个不重要

            //## 可以查当前并查集的集合数量:getSetNum()--本题中好像这个不重要
            public int getStockSetNum(){
                return sizeMap.size();//拢共多少个集合?本题不知道用还是不用
            }

对于本题,咱们还需要对应ACM输入格式,注册一个用户,会来一串股票,我们一次性构建独立集合,并union他们所有

这个功能好比咱们普通并查集的构造函数一样,不过呢,本题的构造函数就随意了
每次注册,咱们直接把用户user的所有股票,放列表里面,然后一鼓作气加入并查集

所以并查集需要准备add函数,拿着user名字,和它的所有股票
这样的话我们的哈希表holder就能保存user–股票们

这是本题的重头戏

后面再查询阶段,我们需要先合并可能的集合,那就要更新user,合并之后,它手里有哪些股票了,这样才知道推荐系统应该推荐多少个user手里没有的股票

add手撕代码,非常非常关键:

            //## 对于本题,咱们还需要对应ACM输入格式,注册一个用户,会来一串股票,我们一次性构建独立集合,并union他们所有
            //这个功能好比咱们普通并查集的构造函数一样,不过呢,本题的构造函数就随意了
            public void add(String user, List<String> arr){
                //送进来的是用户,还有它全部股票:一个字符串数组
                //有了咱就不需要建了,直接合并即可
                //同时,把用户,和股票所属关系表填好
                HashSet<String> set = new HashSet<>(arr);
                holder.put(user, set);

                for(String str : arr){
                    if (!nodes.containsKey(str)) {
                        Node cur = new Node(str);
                        nodes.put(str, cur);//没有咱再考虑加
                        //新来的都是独立成集合的
                        parentMap.put(cur, cur);//自己代表自己
                        sizeMap.put(cur, 1);
                    }
                }
                //加完之后,立马把该用户的所有股票合并成一个集合,其实这时候,几乎相同的用户都合并在一起了
                for (int i = 1; i < arr.size(); i++) {
                    union(arr.get(0), arr.get(i));//这用户的集合得聚到一起
                }
            }

对于本题,我们需要查询用户user,系统会给它推荐多少股票?

我们需要看看目前用户独立情况下,股票仓中的股票数量是多少?
holder里面要是压根没有用户user,那就不必查了,返回error

上面的holder保存的就是这个数据,好说,不妨设为x

然后咱们就要考虑合user所有集合与别的集合,一连串合并之后,新集合的股票仓数量是y
——这个很关键
中途,我们可能需要查一下,user的每个股票,它在不在其他的集合中?合并,并返回合并后股票的总量【看下面单独模块】

则应该给user推荐的股票数量是y-x,这就是本题要的结果

            //## 对于本题,我们需要查询用户user,系统会给它推荐多少股票?
            //我们需要看看目前用户独立情况下,股票仓中的股票数量是多少?
            public int query(String user){
                if (!holder.containsKey(user)) return 0;//没这个用户,返回0;

                //上面的holder保存的就是这个数据,好说不妨设为x
                int x = holder.get(user).size();
                //然后咱们就要考虑合user所有集合与别的集合,一连串合并之后,新集合的股票仓数量是y——这个过程其实加入的时候已经搞定了
                //我们需要把这个用户原来没有的股票,互相加入自己的阵营
                //也就是挨个查用户user的那些股票,是否与其他用户的股票是同一集合,是就让两者相互加
                checkAndUnionTowUsers(user);

                int y = holder.get(user).size();//合并后user用户的最新数量为y

                //则合并之后给user推荐的股票数量是**y-x,这就是本题要的结果**
                return y - x;
            }

中途,我们可能需要查一下,user的每个股票,它在不在其他的集合中?合并,并返回合并后股票的总量

怎么搞呢?

就是拿着用户user的每一个股票stock
然后跟别的用户(所有的其他用户)u的股票otherStock对比

(1)如果stock和otherStock确实已经在一个集合,说明这俩用户赢合并过了

这时候,就只需要把user和u相互补充一波
补充怎么做?

就是拿着用户user的每一个股票stock
然后跟别的用户(所有的其他用户)u的股票otherStock对比
如果u手里的股票没有stock,需要补充stock
如果user手里的股票没有otherStock,需要补充otherStock

(2)接着(1)
如果stock和otherStock确实不再在一个集合,需要查一波u和user他们手里的股票有没有交集,如果有交集那这俩用户集合就得合并

OK,逻辑屡清楚,那就要手撕代码了

            //### 中途,我们可能需要查一下,user的每个股票,它在不在其他的集合中?
            // 在就合并,既然合并,那就要考虑更新holder,保证用户和股票是对应好的
            public void checkAndUnionTowUsers(String user){//目前user它有的股票
                HashSet<String> set = holder.get(user);//user的所有股票们

                for(String u:holder.keySet()){
                    if (!u.equals(user)){//自己跟自己就不用查了
                        boolean in = false;
                        for(String otherStock:holder.get(u)){
                            //跟用户user挨个对比,看看在同一个集合就好,不在需要合并的
                            for(String stock:set){
                                if (isSameSet(otherStock, stock)){
                                    in = true;//它背后俩集合一定在一起的
                                    break;
                                }
                                //in是false,即不在一个集合,需要查,是否user,可能和现在的u有交集stock,有就要合并的
                                if (set.contains(otherStock)) union(otherStock, stock);
                            }
                            if (in) break;//我们需要互相补充然后直接退出不管
                        }

                        if (in){//我们需要互相补充然后直接退出不管
                            List<String> uNeed = new ArrayList<>();//加给当前user
                            List<String> userNeed = new ArrayList<>();//加给user的
                            for(String otherStock:holder.get(u)) {
                                //跟用户user挨个对比,看看在同一个集合就好,不在需要合并的
                                for (String stock : set) {//我们需要互相补充
                                    if (!holder.get(user).contains(otherStock)) userNeed.add(otherStock);
                                    if (!holder.get(u).contains(stock)) uNeed.add(stock);
                                }
                            }

                            for(String str:uNeed) holder.get(u).add(str);
                            for(String str:userNeed) holder.get(user).add(str);//互相加
                        }
                    }
                }
            }
            
        }//并查集结束

ACM格式输入,主函数操作q次,每次可能是1或者2,是1就注册,是2就查询

咱们算是一气呵成!!

终于把我们要用的并查集UnionSet数据结构设计好了
该有的属性我们有了
该有的函数,我们有了

现在就是操作q次
根据操作类型:注册,或者是查询
来完成任务即可

        //主函数,ACM输入格式
        public static void main(String[] args) {
            Scanner in = new Scanner(System.in);

            int q = in.nextInt();//q个操作
            in.nextLine();
            //提前准备好并查集
            UnionSet unionSet = new UnionSet();

            while (q-- > 0){
                //每操作一次q--
                String s = in.nextLine();//一行字符串
                String [] str = s.split(" ");
                String user = str[1];//用户名字
                if (str[0].equals("1")){
                    //1,注册,格式为:
                    //第一行输入1 name n,代表1个名字为anme的人注册了该系统,它关注了n只股票
                    //第二行输入n个仅仅包含英文名字母字符的str,代表这个人关注的股票名字

                    int n = str[2].toCharArray()[0] - '0';//转化为n个
                    //输入n个股票
                    List<String> stocks = new ArrayList<>();
                    String[] stk = in.nextLine().split(" ");
                    for (int i = 0; i < n; i++) {
                        stocks.add(stk[i]);//加入之后放入并查集
                    }
                    unionSet.add(user, stocks);
                }else {//str[0].equals("2")
                    //2,查询,格式为:
                    //仅有一行,输入为2 name,代表询问该推荐系统,会给这个人推荐多少只股票。
                    int ans = unionSet.query(user);
                    System.out.println(ans == 0 ? "error" : ans);
                }
            }//q次操作结束

        }//main结束

测试一把:

5
1 Bob 2
apple mirco
1 Alice 2
apple zoom
2 Bob
1
1 A 2
zoom jjj
2 A
2

连着注册俩用户Bob和Alice
查Bob,系统应该给它推荐1个micro

再注册一个用户A
查A,系统应该给A推荐两个,apple和micro

本题十足不容易,必须熟透了并查集,才能拿下
难点在于你需要造一个holder来存放每个用户user手里的所有股票
输入的时候,合并user所有的集合,但是同时可能合并了别的集合

查询的时候,先检查user可能和其他的用户是否需要合并,合并就要更新user和其他用户u手里的股票

查询之后再看holder的user手里是否多了股票,多出来的就是系统应该给它推荐的

不容易,这是zoom的秋招笔试题,难到爆炸,非常难,所以平时好好练习,否则上场了题目都看不懂的,更别说写出来了。

最后,甩出整体的代码
可能还能继续优化,但是为了理解并查集的本质,我这个过程很清楚

    public static class Main {

        //并查集的集合中装的节点**Node**代表股票
        //它包裹的value就是股票名字name--字符串
        public static class Node{
            public String name;

            public Node(String s){
                name = s;
            }
        }

        //我们的并查集数据结构设计为**UnionSet**
        public static class UnionSet{
            //所有参数类型统统实在写类型
            //内部有这么几个结构
            //(1)nodes(哈希表):key--股票名字字符串,value--股票包裹节点Node
            public HashMap<String, Node> nodes;
            //(2)parentMap(哈希表):key--股票节点Node,value--它所在集合的**代表**——这里就是并查集最经典的地方,具体你看上面我写的并查集的基础文章
            public HashMap<Node, Node> parentMap;
            //(3)sizeMap(哈希表):keya--股票代表节点Node,value:它股票节点代表所在集合的节点个数,也就是当前这个股票集合拢共的张数
            public HashMap<Node, Integer> sizeMap;
            //(4)**普通并查集所没有的属性**,我为了本题设计的数据结构(哈希表),**holder**:key--股票主人,value:主人所持股票仓,
            // 一个哈希集,立里面放的是所持股票的名字字符串
            public HashMap<String, HashSet<String>> holder;

            //构造函数
            public UnionSet(){
                nodes = new HashMap<>();
                parentMap = new HashMap<>();
                sizeMap = new HashMap<>();
                holder = new HashMap<>();//为了方便ACM格式,输入,暂时就先置空
            }

            //## 一切都是为了o(1)速度查a和b是否同属一个集合?isSameSet(a,b)?中间必然要用一个函数过度,
            // 查单个股票它所在集合的代表是谁?findFather(a)
            //### findFather(a)是谁?将沿途所有的节点扁平化,全挂代表上
            public Node findFather(Node cur){
                //扁平化所有节点,挂到代表cur上
                Stack<Node> path = new Stack<>();//沿途收集

                while (parentMap.get(cur) != cur){
                    path.push(cur);//沿途挂接的加入path
                    cur = parentMap.get(cur);//没找到代表就继续往上
                }
                //结果就是cur作为代表

                while (!path.isEmpty()){
                    parentMap.put(path.pop(), cur);//改挂代表,下一次就一次找到了
                }

                return cur;
            }

            //### isSameSet(a,b)问,股票ab在同一个集合吗?
            public boolean isSameSet(String a, String b){
                if (!nodes.containsKey(a) || !nodes.containsKey(b)) return false;

                //存在了俩
                return findFather(nodes.get(a)) == findFather(nodes.get(b));//俩地址相等就是同一个代表,在同一个集合
            }

            //## 然后看看是否需要合并ab所在的集合?union(a,b)
            public void union(String a, String b){
                if (!nodes.containsKey(a) || !nodes.containsKey(b)) return ;

                //存在了俩,才看看是否同属一个集合?
                Node aHead = findFather(nodes.get(a));
                Node bHead = findFather(nodes.get(b));

                //在同一个集合就算了
                if (aHead != bHead){
                    Integer aSize = sizeMap.get(aHead);
                    Integer bSize = sizeMap.get(bHead);
                    //合并策略:小集合挂大集合
                    Node big = aSize >= bSize ? aHead : bHead;
                    Node small = big == aHead ? bHead : aHead;

                    parentMap.put(small, big);//小集合的代表换为big,挂好了,合并好了也
                    sizeMap.put(big, aSize + bSize);//大集合数量变了
                    sizeMap.remove(bSize);//小集合消失
                }
            }

            //## 可以查当前并查集的集合数量:getSetNum()--本题中好像这个不重要
            public int getStockSetNum(){
                return sizeMap.size();//拢共多少个集合?本题不知道用还是不用
            }

            //## 对于本题,咱们还需要对应ACM输入格式,注册一个用户,会来一串股票,我们一次性构建独立集合,并union他们所有
            //这个功能好比咱们普通并查集的构造函数一样,不过呢,本题的构造函数就随意了
            public void add(String user, List<String> arr){
                //送进来的是用户,还有它全部股票:一个字符串数组
                //有了咱就不需要建了,直接合并即可
                //同时,把用户,和股票所属关系表填好
                HashSet<String> set = new HashSet<>(arr);
                holder.put(user, set);

                for(String str : arr){
                    if (!nodes.containsKey(str)) {
                        Node cur = new Node(str);
                        nodes.put(str, cur);//没有咱再考虑加
                        //新来的都是独立成集合的
                        parentMap.put(cur, cur);//自己代表自己
                        sizeMap.put(cur, 1);
                    }
                }
                //加完之后,立马把该用户的所有股票合并成一个集合,其实这时候,几乎相同的用户都合并在一起了
                for (int i = 1; i < arr.size(); i++) {
                    union(arr.get(0), arr.get(i));//这用户的集合得聚到一起
                }
            }

            //## 对于本题,我们需要查询用户user,系统会给它推荐多少股票?
            //我们需要看看目前用户独立情况下,股票仓中的股票数量是多少?
            public int query(String user){
                if (!holder.containsKey(user)) return 0;//没这个用户,返回0;

                //上面的holder保存的就是这个数据,好说不妨设为x
                int x = holder.get(user).size();
                //然后咱们就要考虑合user所有集合与别的集合,一连串合并之后,新集合的股票仓数量是y——这个过程其实加入的时候已经搞定了
                //我们需要把这个用户原来没有的股票,互相加入自己的阵营
                //也就是挨个查用户user的那些股票,是否与其他用户的股票是同一集合,是就让两者相互加
                checkAndUnionTowUsers(user);

                int y = holder.get(user).size();//合并后user用户的最新数量为y

                //则合并之后给user推荐的股票数量是**y-x,这就是本题要的结果**
                return y - x;
            }

            //### 中途,我们可能需要查一下,user的每个股票,它在不在其他的集合中?
            // 在就合并,既然合并,那就要考虑更新holder,保证用户和股票是对应好的
            public void checkAndUnionTowUsers(String user){//目前user它有的股票
                HashSet<String> set = holder.get(user);//user的所有股票们

                for(String u:holder.keySet()){
                    if (!u.equals(user)){//自己跟自己就不用查了
                        boolean in = false;
                        for(String otherStock:holder.get(u)){
                            //跟用户user挨个对比,看看在同一个集合就好,不在需要合并的
                            for(String stock:set){
                                if (isSameSet(otherStock, stock)){
                                    in = true;//它背后俩集合一定在一起的
                                    break;
                                }
                                //in是false,即不在一个集合,需要查,是否user,可能和现在的u有交集stock,有就要合并的
                                if (set.contains(otherStock)) union(otherStock, stock);
                            }
                            if (in) break;//我们需要互相补充然后直接退出不管
                        }

                        if (in){//我们需要互相补充然后直接退出不管
                            List<String> uNeed = new ArrayList<>();//加给当前user
                            List<String> userNeed = new ArrayList<>();//加给user的
                            for(String otherStock:holder.get(u)) {
                                //跟用户user挨个对比,看看在同一个集合就好,不在需要合并的
                                for (String stock : set) {//我们需要互相补充
                                    if (!holder.get(user).contains(otherStock)) userNeed.add(otherStock);
                                    if (!holder.get(u).contains(stock)) uNeed.add(stock);
                                }
                            }

                            for(String str:uNeed) holder.get(u).add(str);
                            for(String str:userNeed) holder.get(user).add(str);//互相加
                        }
                    }
                }
            }

        }//并查集结束

        //主函数,ACM输入格式
        public static void main(String[] args) {
            Scanner in = new Scanner(System.in);

            int q = in.nextInt();//q个操作
            in.nextLine();
            //提前准备好并查集
            UnionSet unionSet = new UnionSet();

            while (q-- > 0){
                //每操作一次q--
                String s = in.nextLine();//一行字符串
                String [] str = s.split(" ");
                String user = str[1];//用户名字
                if (str[0].equals("1")){
                    //1,注册,格式为:
                    //第一行输入1 name n,代表1个名字为anme的人注册了该系统,它关注了n只股票
                    //第二行输入n个仅仅包含英文名字母字符的str,代表这个人关注的股票名字

                    int n = str[2].toCharArray()[0] - '0';//转化为n个
                    //输入n个股票
                    List<String> stocks = new ArrayList<>();
                    String[] stk = in.nextLine().split(" ");
                    for (int i = 0; i < n; i++) {
                        stocks.add(stk[i]);//加入之后放入并查集
                    }
                    unionSet.add(user, stocks);
                }else {//str[0].equals("2")
                    //2,查询,格式为:
                    //仅有一行,输入为2 name,代表询问该推荐系统,会给这个人推荐多少只股票。
                    int ans = unionSet.query(user);
                    System.out.println(ans == 0 ? "error" : ans);
                }
            }//q次操作结束

        }//main结束


    }

总结

提示:重要经验:

1)并查集核心参数nodes表,打包节点的,parentMap,并查集集合的代表节点,sizeMap表,每个结合有几个节点
2)本题外加一个哈希表holder,用来保存用户user手里有多少只股票的,然后并查集内部的加入函数变了,同时多了查询函数,查询的关键在于合并user和u之后,需要更新user和u双方手里的股票
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: leetcode高频100java是指LeetCode网站上的100道经典算法目,使用Java语言进行解答。这些目涵盖了各种算法和数据结构,包括数组、链表、树、图、排序、查找、动态规划等等。这些目是程序员面试和算法学习的重要参考资料,也是提高编程能力和解决实际问的有效途径。 ### 回答2: LeetCode一个著名的程序员面试库,其高频100是指在面试中经常被问到的100道目。这些目涵盖了算法、数据结构、数学等各个领域,是程序员求职和成长过程中不可或缺的练习材料。本文将介绍这100的Java解法。 这100覆盖了许多经典算法,如动态规划、贪心算法、双指针等。对于Java程序员来说,最重要的是要掌握这些算法的核心思想及其实现方法。此外,Java核心类库中的一些常用类和函数,如String、Math、Arrays等也是解过程中常用的工具。 对于高频100,需要掌握的核心算法包括二分查找、数组和链表的操作、栈和队列、哈希表、递归等。此外,还需要掌握字符串操作、动态规划、贪心算法、回溯算法、深度/广度优先搜索等经典算法。 以数组和链表为例,需要掌握的操作包括数组的查找、排序和去重,以及链表的遍历、反转和合并等。使用Java语言来实现这些操作,可以使用Java核心库中的Arrays和Collections类,它们提供了便捷的方法来处理数组和集合。 另外,Java语言还提供了众多数据结构,如栈、队列、双端队列、优先队列、堆等。这些数据结构可以进行复杂的算法操作,如优先队列可以实现贪心算法、堆可以实现堆排序等。Java还提供了Map和Set这两个关键字来实现哈希表的操作,使得开发人员可以轻松地实现哈希表。 最后,Java还提供了众多的工具类和常用函数,例如字符串操作函数、正则表达式、数学函数等。使用这些函数可以为算法提供更加便捷和高效的实现方法。 总之,掌握LeetCode高频100的Java解法需要深入理解算法核心思想,灵活使用Java语言的各种工具和函数。在实际练习中,需要注重代码的可读性、复杂度和鲁棒性。 ### 回答3: LeetCode是全球最大的在线练习平台之一,通过解决LeetCode的问,可以帮助人们提高他们的编程能力。在LeetCode上,有许多高频,这些目被认为是最重要的,因为这些目经常在面试中出现。Java作为一种流行的编程语言,被大多数程序员所熟悉和使用。因此,LeetCode高频100java是程序员们需要掌握的重要知识点。接下来,我将详细介绍LeetCode高频100Java。 LeetCode高频100java包括许多经典的算法和数据结构问,例如:动态规划、回溯、DFS、BFS、二分查找、排序、链表、栈、队列、树、图等。每个问都有一份完整的Java代码实现,以帮助程序员理解算法思路。不仅如此,Java代码还包括了详细的注释和解释,以帮助程序员更好地掌握算法。 LeetCode高频100java对于Java程序员来说很重要,因为这些目是在日常编码工作和面试中经常出现的问。通过解决这些问,程序员们能够提高他们的编码能力和思维能力。此外,这些问也能帮助程序员们更好地了解Java语言的使用和优化,加深对Java语言特性的理解。 总结来说,对于Java程序员来说,掌握LeetCode高频100java非常重要。这将帮助他们提高他们的编程水平,了解更多的算法和数据结构。通过解决这些问,他们将更容易通过面试,获得更好的编程工作。因此,Java程序员们应该花费足够的间和精力去学习和掌握LeetCode高频100java。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值