You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

动态规划能否解决二维列表最小化distinct值扁平化问题?求最优解法

最小化扁平化数组的distinct元素数量解法

问题明确

给定二维列表,从每个子数组选一个元素组成一维数组,目标是让结果里的不同元素数量最少。示例如下:

  • 输入[[1,2],[1,2],[1,3],[5,4],[4,6]],最优输出[1,1,1,4,4](仅含2种不同元素)
  • 输入[[1,2,3],[1,3],[1,8],[5,6],[6,5],[4]],最优输出[1,1,1,5,5,4]或[1,1,1,6,6,4](仅含3种不同元素)
  • 反例输入[[1,2],[1,2],[1,2],[1,3],[3,4],[3,4],[4,5,9]],单纯统计元素频次的贪心策略会得到[1,1,1,1,3,3,4](含3种元素),但最优解是[1,1,1,1,4,4,4](仅含2种元素)——这说明只看频次的方法不可靠。

核心本质:最小击中集问题

这个问题等价于寻找最小元素集合,要求每个子数组中至少有一个元素属于该集合。找到这个集合后,从每个子数组中挑选集合内的元素,就能得到distinct数量最少的结果数组。

分场景解决

场景1:所有子数组长度≤2(多项式可解)

此时问题可转化为图的顶点覆盖问题,能通过多项式时间算法解决:

  1. 将每个元素视为图的顶点
  2. 若两个元素出现在同一个子数组中,则在它们之间连一条边
  3. 求解该图的最小顶点覆盖,这个覆盖集合就是目标最小元素集合
  4. 对每个子数组,选择集合中的任意一个元素即可

场景2:存在子数组长度>2(NP难问题)

当子数组长度超过2时,最小击中集是NP难问题,需根据数据规模选择解法:

回溯法(精确解,适合小规模数据)

按元素频次降序排序优化搜索效率,从小到大尝试不同大小的元素子集,找到第一个能覆盖所有子数组的最小子集:

def find_min_hitting_set(arrays):
    # 统计元素频次并降序排序,减少搜索次数
    freq = {}
    for sub in arrays:
        for num in sub:
            freq[num] = freq.get(num, 0) + 1
    sorted_elements = sorted(freq.keys(), key=lambda x: -freq[x])

    # 从最小子集大小开始尝试
    for size in range(1, len(sorted_elements)+1):
        from itertools import combinations
        for candidate in combinations(sorted_elements, size):
            # 检查当前子集是否覆盖所有子数组
            valid = True
            for sub in arrays:
                if not any(num in candidate for num in sub):
                    valid = False
                    break
            if valid:
                return set(candidate)
    return set()

def flatten_min_distinct(arrays):
    hitting_set = find_min_hitting_set(arrays)
    result = []
    for sub in arrays:
        # 取子数组中第一个属于击中集的元素
        for num in sub:
            if num in hitting_set:
                result.append(num)
                break
    return result

# 测试反例
test_case = [[1,2],[1,2],[1,2],[1,3],[3,4],[3,4],[4,5,9]]
print(flatten_min_distinct(test_case))  # 输出 [1,1,1,1,4,4,4]
贪心近似算法(适合大规模数据)

每次选择能覆盖最多未被覆盖子数组的元素,重复操作直到所有子数组被覆盖。该方法的近似比为O(log n),能快速得到接近最优的结果:

def greedy_hitting_set(arrays):
    # 复制子数组为集合形式,避免修改原数据
    remaining = [set(sub) for sub in arrays]
    hitting_set = set()
    
    while remaining:
        # 统计每个元素能覆盖的剩余子数组数量
        count = {}
        for sub in remaining:
            for num in sub:
                count[num] = count.get(num, 0) + 1
        # 选择覆盖数量最多的元素
        best_num = max(count, key=count.get)
        hitting_set.add(best_num)
        # 移除被该元素覆盖的子数组
        remaining = [sub for sub in remaining if best_num not in sub]
    return hitting_set

为什么频次统计法失效?

反例中,元素1可覆盖前4个子数组,元素4可覆盖后3个子数组,两者组合仅需2个元素;而元素3虽然频次高,但只能覆盖第4-6个子数组,和元素1组合需要3个元素。单纯选取高频元素会陷入局部最优,无法得到全局最优解。

注:若有比动态规划更优的解法,欢迎提出。

内容的提问来源于stack exchange,提问作者raspace

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 03:07:50