Bootstrap

再说连续性问题

连续问题的一个总结

连续问题是判断在某个期间,某些动作(状态)是否连续出现。在帖子连续性问题 中, 已经提到了用户连续登录、最大假期等。这些问题的解法是使用大小等差数列相减的办法。其实还有一种算法可以解决此问题,它就是合并集办法。合并集往往使用在计算二维数组的连通性问题,例如,leetcode 的 friend-circle 和 island-size 两个问题。并查集能解决二维数组的问题,那么一维数组的问题一定也能完美解决,在之前文章中提到的连续性问题就是一维数组上的连通性问题。

啥叫并查集合

并查集是将多个集合做并集的方法,它里面保存了一个 key-value 关系,key 表示集合中的某个节点,value 代表节点。如下图所示,集合中的元素,除了代表节点之外,其中节点都指向了代表节点。

并查集
这种结构,可以使用对象引用的方式来表示,如下的 Node 类的设计。

public class Node<V>{
    public V value ;
    public Node rep ;
    public boolean equals(Node n){
      // 判断两个 Node 是否相等
    }
}

也可以使用 Map 的方式保存这种关系。最好是使用数组下标的方式了表示,例如,parents[children_index] = parent_index 这样的方式。

还有一个合并两个 Node 所在集合的操作,他是实现并集操作的关键。下面是一个使用 Map 结构实现的一种方式:

    public static class Node<V>{
        public V value ;
        public Node(V v){
            value = v ;
        }

        @Override
        public boolean equals(Object obj) {
            return this.value.equals(obj) ;
        }
    }
    public static class UnionSet<V>{
        // 设置 V 和 Node 的对应关系。
        private Map<V,Node<V>> nodes = new HashMap<>();
        // 保存 Node 和 Node 直接的父子关系
        private Map<Node<V> , Node<V>> parents = new HashMap<>();
        // 保存每个大集合的元素大小
        private Map<Node<V> , Integer> sizeMap = new HashMap<>();
        public UnionSet(List<V> values){
            for (V value : values) {
                Node node = new Node(value);
                nodes.put(value , node);
                parents.put(node , node);
                sizeMap.put(node , 1);
            }
        }
        public Node<V> getFather(V v){
            Stack<Node<V>> stack = new Stack<>();
            Node<V> curr =  nodes.get(v);
            while(!parents.get(curr).equals(curr)){
                curr = nodes.get(v);
            }
            // 这里是做了优化,举个例子,本来是  A -> B -> C -> D
            // 这样的结果,下面的优化后,就变成了 A -> D , B -> D , C -> D 这样的结构
            // 这样优化后,再去查询的时候,就能做到一步到位了。gi
            Node<V> head = stack.pop();
            while(!stack.empty()){
                parents.put(stack.pop() , head);
            }
            return curr ;
        }
        public boolean isSameSet(V a , V b){
            return getFather(a).equals(getFather(b));
        }
        public void union(V a , V b){
            Node<V> A = nodes.get(a);
            Node<V> B = nodes.get(b);
            if(!A.equals(B)){
                int aSize = sizeMap.get(A);
                int bSize = sizeMap.get(B);
                Node<V> big = (aSize > bSize ? A : B);
                Node<V> small = (aSize <= bSize ? A : B);
                parents.put(small , big);
                sizeMap.put(big , aSize + bSize);
            }
        }
    }

采用并查集的方式,可以计算一段阶段中,有多少个连续的集合以及集合的大小,但是集合具体的元素不能方便的取出了 ,其实在 UnionSet 类中加入一个 Map ,其中 key 是代表节点,value 是一个 List 里面保存了集合中的其他节点。这样就能方便的取出集合中的元素了。

下面是实现的代码:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.RandomUtil;
import java.util.*;
import java.util.stream.Collectors;
public class ConsistanceLogin {
    public static void main(String[] args) {
        /**
         * 找出每周连续登录超过 n (n < 8) 的用户
         * */
        List<String> row = new ArrayList<>();
        for(int i = 0 ; i < 1000 ; i++){
            int userId = RandomUtil.randIntRange(2 , 20 ,99);
            for (int j = 0; j < 30; j++) {
               row.add(String.format("%s %s %s",userId , j , RandomUtil.randIntRange(2 , 20 ,99) >= 30 ? "1" : "0"));
            }
        }
        List<String> rs = getMaxLoginUser(row);
        for(String userId ; rs){
          System.out.println(userId);
        }
    }
    public static List<String> getMaxLoginUser(List<String> rows , int days){
        List<String> rs = new ArrayList<>();
        List<LoginInfo> loginInfos = new ArrayList<>();
        for (String row : rows) {
            String[] split = row.split("\\s+");
            String userId = split[0];
            String date = split[1];
            String isLogin = split[2];
            loginInfos.add(new LoginInfo(userId , date , isLogin));
        }
        List<LoginInfo> ordered = loginInfos.stream().sorted(new Comparator<LoginInfo>() {
            @Override
            public int compare(LoginInfo o1, LoginInfo o2) {
                if(Integer.parseInt(o1.userId) == Integer.parseInt(o2.userId)){
                    return Integer.parseInt(o1.date) - Integer.parseInt(o2.date);
                }else{
                    return Integer.parseInt(o1.userId) - Integer.parseInt(o2.userId) ;
                }
            }
        }).collect(Collectors.toList());
        Map<String , List<Integer>> userLoginInfo = new HashMap<>();
        for (LoginInfo loginInfo : ordered) {
            userLoginInfo.computeIfPresent(loginInfo.userId , (key , value)->{
                value.add(Integer.parseInt(loginInfo.isLogin));
                return value ;
            });
            userLoginInfo.computeIfAbsent(loginInfo.userId , (key) -> {
                List<Integer> list = new ArrayList<>();
                list.add(Integer.parseInt(loginInfo.isLogin));
                return list;
            });
        }
        UnionSet us = new UnionSet();
        userLoginInfo.forEach((key , value)->{
            us.parents = new int[value.size()];
            us.setElement = new int[value.size()][value.size()];
            us.setIndex = new int[value.size()];
            us.sizeSet = new int[value.size()];
            for (int i = 0; i < value.size() ; i++) {
                if(value.get(i).equals(1)){
                    us.parents[i] = i ;
                    us.sizeSet[i] = 1 ;
                    us.setIndex[i] = 1 ;
                    us.setElement[i] = new int[]{i};
                }

            }
            for(int i = 1 ; i < value.size() ; i++){
                if(value.get(i-1).equals(value.get(i)) && value.get(i).equals(1)){
                    us.union(i -1 , i);
                }
            }
            int maxNode = 0 ;
            for(int i : us.sizeSet){
                maxNode = Math.max(i , maxNode);
            }
            if(maxNode > days){
                rs.add(key);
            }
        });
        return rs ;

    }
    public static class LoginInfo{

        public String userId ;
        public String date ;
        public String isLogin ;
        public LoginInfo(String s , String d , String i){
            userId = s ;
            date = d ;
            isLogin = i ;
        }
    }
    public static class UnionSet{
        public int[] parents ;
        public int[] sizeSet ;
        public int[] help ;
        public int[][] setElement ;
        public int[] setIndex;
        public void union(int i , int j){
            int iFather = getFather(i);
            int jFather = getFather(i);
            if(iFather != jFather){
                int big = (iFather > jFather ? iFather : jFather);
                int small = (iFather <= jFather ? iFather : jFather);
                parents[small] = small ;
                for(int a = 0 ; a < setElement[small].length ; a++){
                    setElement[big][setIndex[big]++] = setElement[small][a] ;
                }
                setElement[small] = null ;
                sizeSet[big] += sizeSet[small];
                sizeSet[small] = 0 ;
            }

        }
        public int getFather(int i){
            int idx = 0 ;
            int cur = 0 ;
            while(cur != parents[cur]){
                help[idx++] = cur ;
                cur = parents[cur];
            }
            idx--;
            while(idx>=0){
                parents[help[idx]] = cur ;
                idx--;
            }
            return cur ;
        }
    }
}


;