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

从二维数组寻找最短元素集合的算法优化问询

高效求解满足条件的最短集合问题

问题描述

给定二维数组(示例如下),需找到最短元素集合,确保集合包含数组每一行的至少一个元素:

A  B  C  D  E
   --------------
1 | 0  2  3  4  5
2 | 1  2  4  5  6
3 | 1  3  4  5  6
4 | 2  3  4  5  6
5 | 1  2  3  4  5

示例情况:

  • 选取每行首元素得到集合 {0,1,1,2,1},去重后长度为3;
  • 选取 {1B,2A,3A,4A,5A} 得到集合 {2,1,1,2,1},去重后长度为2;
  • 最优解如选取 {1D,2C,3C,4C,5D},对应元素均为4,集合长度为1。

对于大规模数组(如495行×28列),暴力遍历所有列组合(共28^495种)完全不可行,必须采用高效算法。

问题本质与高效解法

该问题属于**击中集(Hitting Set)**问题,是NP-hard问题,不存在多项式时间精确解法,但针对每行元素数量少(仅28个)的场景,可采用以下实用方案:

1. 贪心算法(快速近似最优解)

核心逻辑:每次选择覆盖最多未覆盖行的元素,重复至所有行被覆盖。时间复杂度为O(R×E)(R为行数,E为不同元素数量),解长度不超过最优解的log(R)倍,适合大规模数据快速求解。

2. 二分查找+回溯(精确最优解)

核心逻辑:通过二分查找确定最小集合大小k,再用回溯法验证是否存在大小为k的集合覆盖所有行。针对较小的k(如1、2)验证效率极高;k较大时,可通过剪枝策略(如剩余元素不足以覆盖未覆盖行时终止分支)提升速度。

示例代码

贪心算法实现(C#)

using System;
using System.Collections.Generic;
using System.Linq;

public class HittingSetSolver
{
    public static HashSet<long> FindGreedyHittingSet(List<List<long>> matrix)
    {
        var uncoveredRows = new HashSet<int>(Enumerable.Range(0, matrix.Count));
        var solution = new HashSet<long>();

        while (uncoveredRows.Any())
        {
            // 统计每个元素覆盖的未被覆盖行数
            var elementCoverage = new Dictionary<long, int>();
            foreach (var rowIndex in uncoveredRows)
            {
                foreach (var element in matrix[rowIndex])
                {
                    elementCoverage.TryAdd(element, 0);
                    elementCoverage[element]++;
                }
            }

            // 选择覆盖行数最多的元素
            var bestElement = elementCoverage.OrderByDescending(kv => kv.Value).First().Key;
            solution.Add(bestElement);

            // 标记包含该元素的行为已覆盖
            var rowsToRemove = uncoveredRows.Where(rowIndex => matrix[rowIndex].Contains(bestElement)).ToList();
            foreach (var rowIndex in rowsToRemove)
                uncoveredRows.Remove(rowIndex);
        }

        return solution;
    }

    // 测试示例
    public static void Main()
    {
        var matrix = new List<List<long>>
        {
            new List<long> {0,2,3,4,5},
            new List<long> {1,2,4,5,6},
            new List<long> {1,3,4,5,6},
            new List<long> {2,3,4,5,6},
            new List<long> {1,2,3,4,5}
        };

        var result = FindGreedyHittingSet(matrix);
        Console.WriteLine("贪心算法得到的最短集合:");
        foreach (var elem in result)
            Console.Write(elem + " ");
        // 输出:4(符合最优解)
    }
}

精确解法(二分查找+回溯)片段

using System;
using System.Collections.Generic;
using System.Linq;

public class ExactHittingSetSolver
{
    private static HashSet<long> _bestSolution;
    private static List<List<long>> _matrix;
    private static int _targetSize;

    public static HashSet<long> FindExactHittingSet(List<List<long>> matrix)
    {
        _matrix = matrix;
        var allElements = matrix.SelectMany(row => row).Distinct().ToList();
        _bestSolution = new HashSet<long>(allElements); // 初始最优解为所有元素

        // 二分查找最小k
        int left = 1, right = allElements.Count;
        while (left <= right)
        {
            int mid = (left + right) / 2;
            _targetSize = mid;
            Backtrack(0, new HashSet<long>(), new HashSet<int>(Enumerable.Range(0, matrix.Count)));
            
            if (_bestSolution.Count <= mid)
                right = mid - 1;
            else
                left = mid + 1;
        }

        return _bestSolution;
    }

    private static void Backtrack(int elementIndex, HashSet<long> currentSet, HashSet<int> uncoveredRows)
    {
        // 剪枝:当前集合已不优于最优解,或剩余元素不足以凑够目标大小
        if (currentSet.Count >= _bestSolution.Count || 
            currentSet.Count + (_matrix.SelectMany(row => row).Distinct().Count() - elementIndex) < _targetSize)
            return;

        // 所有行已覆盖,更新最优解
        if (!uncoveredRows.Any())
        {
            if (currentSet.Count < _bestSolution.Count)
                _bestSolution = new HashSet<long>(currentSet);
            return;
        }

        var allElements = _matrix.SelectMany(row => row).Distinct().ToList();
        if (elementIndex >= allElements.Count)
            return;

        // 选择当前元素
        long element = allElements[elementIndex];
        var newUncovered = new HashSet<int>(uncoveredRows.Where(row => !_matrix[row].Contains(element)));
        currentSet.Add(element);
        Backtrack(elementIndex + 1, currentSet, newUncovered);
        currentSet.Remove(element);

        // 不选择当前元素
        Backtrack(elementIndex + 1, currentSet, uncoveredRows);
    }
}

说明

  • 贪心算法适合大规模数据快速求解,结果接近最优;
  • 精确解法能找到最优解,但k较大时耗时较长,可根据需求选择;
  • 可结合两者:先用贪心算法得到初始最优解,再用回溯法尝试寻找更小解,减少剪枝负担。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 11:45:30