Skip to content

1 需求背景

双轨成色配置,可以按照适用范围配置。每个适用范围都由“品类”、“品牌”、“型号”组成,其中品类必填,品牌和型号非必填,当品牌不为空时型号非必填。

要求不同的适用范围互斥,不能有交集(从左至右)。

有交集
{1, 10}
true
{1, 10}
false
{1, 10, 101}
false

2 实现思路

2.1 适用范围实体定义

java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class PgScope {
    private Integer cateId;
    private Integer brandId;
    private Integer modelId;
}

2.2 简单明了求列表公共前缀

java
/**
 * 适用范围是否有重叠
 */
public static boolean checkIntersection(List<PgScope> scopeList) {
    List<List<Integer>> list = new ArrayList<>();
    for (PgScope pgScope : scopeList) {
        // 过滤 null 值
        List<Integer> path = Lists.newArrayList(pgScope.getCateId(), pgScope.getBrandId(), pgScope.getModelId())
                .stream().filter(Objects::nonNull).collect(Collectors.toList());
        for (List<Integer> tmp : list) {
            if (checkIntersection(tmp, path)) {
                return true;
            }
        }
        list.add(path);
    }
    return false;
}

public static boolean checkIntersection(List<Integer> path1, List<Integer> path2) {
    int minLength = Math.min(path1.size(), path2.size());
    for (int i = 0; i < minLength; i++) {
        if (!Objects.equals(path1.get(i), path2.get(i))) {
            // 发现同一位置两个列表元素不一样,即返回没有交集
            return false;
        }
    }
    // 存在公共前缀,返回有交集
    return true;
}

设定范围数量为 n,路径长度为 k,该算法时间复杂度为 O(kn²)

该场景 k 最大为 3,时间复杂度简化为 O(n²)

2.3 来点操作 Trie树

参考:DFA敏感词过滤算法

下面图示下建立 Trie 树的过程

添加

添加 {1, 10},因为 Trie 数中已存在 {1, 10} 路径,所以无法添加成功

添加 

java
@Data
public static class TrieNode {
    /**
     * 路径结束标识
     */
    private Boolean end = false;
    /**
     * 子节点
     */
    private Map<Integer, TrieNode> children = new HashMap<>();
}

/**
 * 范围是否有重叠
 */
public static boolean checkIntersection(List<PgScope> scopeList) {
    TrieNode root = new TrieNode();
    for (PgScope pgScope : scopeList) {
        List<Integer> path = Lists.newArrayList(pgScope.getCateId(), pgScope.getBrandId(), pgScope.getModelId())
                .stream().filter(Objects::nonNull).collect(Collectors.toList());
        if (hit(root, path, 0)) {
            return true;
        }
        add(root, path, 0);
    }
    return false;
}

/**
 * 递归匹配当前节点,是否存在子节点与之有交集
 *
 * @param node 前缀树节点
 * @param path 待匹配路径
 * @param index 路径位置下标
 * @return 是否有交集
 */
public static boolean hit(TrieNode node, List<Integer> path, int index) {
    if (index >= path.size()) {
        return false;
    }
    Integer key = path.get(index);
    TrieNode child = node.getChildren().get(key);
    if (Objects.isNull(child)) {
        return false;
    }
    // 当前路径终点,或者是前缀树结束标识,表明与之有交集
    if (index == path.size() - 1 || child.getEnd()) {
        return true;
    }
    return hit(child, path, index + 1);
}

/**
 * 递归添加到前缀树中
 */
public static void add(TrieNode node, List<Integer> path, int index) {
    if (index >= path.size()) {
        return;
    }
    Integer key = path.get(index);
    TrieNode child = node.getChildren().get(key);
    if (Objects.isNull(child)) {
        // 如果不存在需要新建节点挂上去
        child = new TrieNode();
        node.getChildren().put(key, child);
    }
    if (index == path.size() - 1) {
        child.end = true;
    }
    add(child, path, index + 1);
}

设定范围数量为 n,路径长度为 k,该算法时间复杂度为 O(kn)

该场景 k 最大为 3,时间复杂度简化为 O(n)

验证代码

java
public static void main(String[] args) {
    PgScope scope1 = PgScope.builder()
            .cateId(1)
            .brandId(10)
            .build();
    PgScope scope2 = PgScope.builder()
            .cateId(1)
            .brandId(10)
            .modelId(101)
            .build();
    PgScope scope3 = PgScope.builder()
            .cateId(1)
            .brandId(11)
            .modelId(110)
            .build();
    // {1, 10}
    // {1, 10, 101}
    // true
    System.out.println(checkIntersection(Lists.newArrayList(scope1, scope2)));
    // {1, 10}
    // {1, 11, 110}
    // false
    System.out.println(checkIntersection(Lists.newArrayList(scope1, scope3)));
    // {1, 10, 101}
    // {1, 11, 110}
    // false
    System.out.println(checkIntersection(Lists.newArrayList(scope2, scope3)));
}

基于 MIT 许可发布