Summary of Leetcode and search problems

Table of contents

 

Summary of Leetcode and search problems

1. What is union check

1.1 The search of union search

1.2 Merge of union search

2.Leetcode and check set problem selection

2.1 547. Moments

2.2 399. Division evaluation

2.3 Interview question 17.07. Baby name


Summary of Leetcode and search problems

Today I decided to fully understand the union and search.

1. What is union check

In some set application problems with N elements, we usually let each element form a single-element set at the beginning, and then merge the sets of elements belonging to the same group in a certain order, and repeatedly search for an element in which collection. The characteristic of this kind of topic is that it seems not complicated, but the amount of data is huge. If it is described by a normal data structure, it is often too large in space and the computer cannot bear it; even if it barely passes in space, the time complexity of running It is also very high. It is impossible to calculate the results required by the test questions within the running time stipulated in the competition. We can only use a new abstract special data structure-and check.

And lookup is similar to a forest, each node has a father[x] to represent the father node of x.

Initialize the collection x, there is only one element of x in the collection, and set the root node of x to itself, so that the label of each collection = the label of the root node.

public void init(int x){
    father[x]=x;
}

Next, let's look at the two important operations of union search: search and merge.

1.1 The search of union search

Find, that is, find the set where the element x is located, that is, find the label of the root node of x.

As shown in the figure, if we want to know the set where element 3 is located, we can know that father[3]=2, father[2]=1, and we can get that the set where element 3 is located is 1. And we can find that the number of operations to find the set where the element x is located = the depth where the element x is located.

The search here has an optimization of path compression, that is, when the set of 3 is found to be 1, you can directly set father[3] to 1, and when searching for 3, it will search for 2, and there may be other elements. The father of the elements is all set to 1. In this way, when searching for the collection of the descendants of 3 next time, in the above example, the number of searches is shortened by 1. When searching for the collection of the ancestors of 3, the number of searches is only 1 up. After the path is compressed, the collection becomes:

We can write code like this:

//返回元素x所在的集合标号
public int find(int x){
    //如果x不为根节点
    if(x!=father[x]){
       father[x]=find(father[x]);//路径压缩
    }
    return father[x];
}

When backtracking, set the father[] of all recursive elements as the collection root node. This optimization is where the advantage of union lookup lies.

1.2 Merge of union search

union(x,y) is to merge the two collections where x and y are located, we only need to set the root node of one of the collections as the child of the root node of the other collection.

Assuming that there are three nodes 1, 2, and 3, their initialization is three sets that only contain themselves.

Combining 1 and 2, can be:

These two cases have the same effect, but if combined with 3, it can be:

 These two situations will lead to the fact that when searching for the set of element 3, the number of searches for the former is 2, the number of searches for the latter is 1, and the latter is more efficient. Therefore, there is a heuristic merging optimization when merging sets, that is, recording the depth of each set. When merging two sets, hang the set with a smaller depth under the set with a greater depth. (The effect of this optimization is not very obvious, and it has little effect. Sometimes it is troublesome to discuss in this way, and you can also directly hang y on x).

We can write code like this:

//合并x和y所在的集合
public void union(int x, int y)
{
    int fx = find(x);//找到x所在的集合,即x的根节点
    int fy = find(y);//找到y所在集合,即y的根节点
    if (fx == fy) return;
    if (rank[fx] > rank[fy]) //y 合并到 x
    {
        father[fy] = fx;
        if(rank[fy]+1>rank[fx])//如果挂上fy之后深度增加
        {
            rank[fx]=rank[fy]+1;//这里的秩为深度
        }
    }
    else
    {           
        father[fx] = fy;
        if(rank[fx]+1>rank[fy]) rank[fy]=rank[fx]+1;
    }
}

Union search is applicable to the merging and searching operations of all sets, and can be further extended to some operations in graph theory to determine whether two elements belong to the same connected block. Due to the use of heuristic merging and path compression techniques, the time complexity of union search can be regarded as approximately O(1), and the space complexity is O(N), thus transforming a large-scale problem into a minimal space , Extremely fast and simple operation.

2.Leetcode and check set problem selection

2.1  547. Moments

班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。

给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。

 

示例 1:

输入:
[[1,1,0],
 [1,1,0],
 [0,0,1]]
输出:2 
解释:已知学生 0 和学生 1 互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回 2 。
示例 2:

输入:
[[1,1,0],
 [1,1,1],
 [0,1,1]]
输出:1
解释:已知学生 0 和学生 1 互为朋友,学生 1 和学生 2 互为朋友,所以学生 0 和学生 2 也是朋友,所以他们三个在一个朋友圈,返回 1 。
 

提示:

1 <= N <= 200
M[i][i] == 1
M[i][j] == M[j][i]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/friend-circles
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

This is the most typical and simplest union search problem, which can be solved directly by union search.

//典型并查集问题
//我们使用father[N*N]数组来记录父节点,father[i]表示i的父节点
//一开始肯定有N个朋友圈即每个人他们自己,每合并一次,朋友圈数就会减少一次
class Solution {
    public int[] father;
    public int res;
    public int findCircleNum(int[][] M) {
        int N=M.length;
        if(N<=0) return 0;
        res=N;
        father=new int[N*N];
        //先给father数组全赋值-1
        for(int i=0;i<N*N;i++)
            father[i]=-1;
        for(int i=0;i<N;i++)
            for(int j=0;j<N;j++)
                if(M[i][j]==1) union(i,j);//合并集合
        return res;
    }
    //查找x的父结点
    public int find(int x){
        if(father[x]==-1 || father[x]==x){
            father[x]=x;//初始父结点是自己
            return x;
        }
        else{
            father[x]=find(father[x]);//回溯根节点,进行路径压缩
        }
        return father[x];
    }
    //合并集合
    public void union(int x,int y){
        int fx=find(x);
        int fy=find(y);
        if(fx==fy) return ;
        else{
            res--;//朋友圈减少一个
            father[fx]=fy;
        }
    }
}

2.2  399. Division evaluation

给出方程式 A / B = k, 其中 A 和 B 均为用字符串表示的变量, k 是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0。

输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果。

 

示例 1:

输入:equations = [["a","b"],["b","c"]], values = [2.0,3.0], queries = [["a","c"],["b","a"],["a","e"],["a","a"],["x","x"]]
输出:[6.00000,0.50000,-1.00000,1.00000,-1.00000]
解释:
给定:a / b = 2.0, b / c = 3.0
问题:a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
返回:[6.0, 0.5, -1.0, 1.0, -1.0 ]
示例 2:

输入:equations = [["a","b"],["b","c"],["bc","cd"]], values = [1.5,2.5,5.0], queries = [["a","c"],["c","b"],["bc","cd"],["cd","bc"]]
输出:[3.75000,0.40000,5.00000,0.20000]
示例 3:

输入:equations = [["a","b"]], values = [0.5], queries = [["a","b"],["b","a"],["a","c"],["x","y"]]
输出:[0.50000,2.00000,-1.00000,-1.00000]
 

提示:

1 <= equations.length <= 20
equations[i].length == 2
1 <= equations[i][0].length, equations[i][1].length <= 5
values.length == equations.length
0.0 < values[i] <= 20.0
1 <= queries.length <= 20
queries[i].length == 2
1 <= queries[i][0].length, queries[i][1].length <= 5
equations[i][0], equations[i][1], queries[i][0], queries[i][1] 由小写英文字母与数字组成

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/evaluate-division
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

answer:

//本质上是一个带权并查集问题
//我们可以用一个哈希表father记录每个结点的父结点
//再用另一个哈希表values记录该结点到父节点的距离
//然后x,y方程式的解,也就是用x到根节点的距离除以y到根节点的距离
class Solution {
    public HashMap<String,String> father=new HashMap();
    public HashMap<String,Double> values=new HashMap();
    public double[] calcEquation(List<List<String>> e, double[] v, List<List<String>> q) {
        //首先遍历一遍等式,顺便合并集合
        for(int i=0;i<e.size();i++){
            List<String> temp=e.get(i);
            String x=temp.get(0);
            String y=temp.get(1);
            //因为x,y肯定在同一个集合里,所以合并两个集合
            union(x,y,v[i]);
        }
        //求结果
        double[] res=new double[q.size()];
        for(int i=0;i<q.size();i++){
            List<String> temp=q.get(i);
            String x=temp.get(0);
            String y=temp.get(1);
            if(!father.containsKey(x) || !father.containsKey(y)){//要求解的方程元素没有
                res[i]=-1;
                continue;
            }
            String fx=find(x);//找x的父节点
            String fy=find(y);//找y的父节点
            if(!fx.equals(fy)) res[i]=-1;//如果x,y不在一个集合里,方程没解
            else res[i]=values.get(x)/values.get(y);//否则解x到根节点的距离除以y到根节点的距离
        }
        return res;
    }
    //查找x的父节点
    public String find(String x){
        String fx=father.getOrDefault(x,x);//如果x没有父节点,那就是它自己,此时它自己就是根节点
        String fx2=new String(fx);//记住它上一级的父节点
        double vx=values.getOrDefault(x,1.0);
        if(!fx.equals(x)){//如果它不是根节点,就继续回溯
            fx=find(fx);//路径压缩
            vx=vx*values.get(fx2);//x到根结点的距离
        } 
        father.put(x,fx);
        values.put(x,vx);
        return fx;
    }
    //合并两个结点
    public void union(String x,String y,double vi){
        String fx=find(x);//找x的父节点
        String fy=find(y);//找y的父节点
        double vx=values.getOrDefault(x,1.0);
        double vy=values.getOrDefault(y,1.0);
        if(fx.equals(fy)) return ;//两个结点已经同集合了
        father.put(fx,fy);//设置fy为fx的父节点
        values.put(fx,vy*vi/vx);
    }
}

2.3  Interview questions 17.07. Baby names


每年,政府都会公布一万个最常见的婴儿名字和它们出现的频率,也就是同名婴儿的数量。有些名字有多种拼法,例如,John 和 Jon 本质上是相同的名字,但被当成了两个名字公布出来。
给定两个列表,一个是名字及对应的频率,另一个是本质相同的名字对。设计一个算法打印出每个真实名字的实际频率。注意,如果 John 和 Jon 是相同的,并且 Jon 和 Johnny 相同,则 John 与 Johnny 也相同,即它们有传递和对称性。

在结果列表中,选择字典序最小的名字作为真实名字。

示例:

输入:names = ["John(15)","Jon(12)","Chris(13)","Kris(4)","Christopher(19)"], synonyms = ["(Jon,John)","(John,Johnny)","(Chris,Kris)","(Chris,Christopher)"]
输出:["John(27)","Chris(36)"]
提示:

names.length <= 100000
class Solution {
    public String[] trulyMostPopular(String[] names, String[] synonyms) {
        Map<String, Integer> map = new HashMap<>();
        Map<String, String> father = new HashMap<>();     //并查集, key(子孙)->value(祖宗)
        for (String name : names) {     //统计频率
            int idx1 = name.indexOf('(');
            int idx2 = name.indexOf(')');
            int frequency = Integer.valueOf(name.substring(idx1 + 1, idx2));
            map.put(name.substring(0, idx1), frequency);
        }
        for (String pair : synonyms) {  //union同义词
            int idx = pair.indexOf(',');
            String name1 = pair.substring(1, idx);
            String name2 = pair.substring(idx + 1, pair.length() - 1);
            while (father.containsKey(name1)) {   //找name1祖宗
                name1 = father.get(name1);
            }
            while (father.containsKey(name2)) {   //找name2祖宗
                name2 = father.get(name2);
            }
            if(!name1.equals(name2)){   //祖宗不同,要合并
                int frequency = map.getOrDefault(name1, 0) + map.getOrDefault(name2, 0);    //出现次数是两者之和
                String trulyName = name1.compareTo(name2) < 0 ? name1 : name2;
                String nickName = name1.compareTo(name2) < 0 ? name2 : name1;
                father.put(nickName, trulyName);      //小名作为大名的分支,即大名是小名的祖宗
                map.remove(nickName);       //更新一下数据
                map.put(trulyName, frequency);
            }
        }
        String[] res = new String[map.size()];
        int index = 0;
        for (String name : map.keySet()) {
            StringBuilder sb = new StringBuilder(name);
            sb.append('(');
            sb.append(map.get(name));
            sb.append(')');
            res[index++] = sb.toString();
        }
        return res;
    }
}

Guess you like

Origin blog.csdn.net/qq_35590459/article/details/109683475