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

Python实现带最小命中数与百分比条件的组合生成问题

彩票组合覆盖问题的正确实现与优化

现有代码的错误分析

你的代码逻辑完全偏离需求:

  • 现有代码遍历每个t元子集,找到第一个能包含它的k元组合并加入结果,最后去重。这会把所有能覆盖至少一个t元子集的k元组合都纳入结果,但你需要的是最小(或满足条件)的组合集合,确保所有t元子集都被集合中的至少一个k元组合覆盖,而非所有能覆盖单个t元子集的组合。
  • 以n=5, k=3, t=2为例,现有代码会把(1,2,4)这类多余的组合加入,但实际上(1,2)已被(1,2,3)覆盖,(2,4)已被(2,3,4)覆盖,(1,4)被(1,4,5)覆盖,(1,2,4)完全不需要。

正确实现思路

这本质是集合覆盖问题:全集是所有t元号码子集,每个k元组合对应它能包含的所有t元子集,我们需要选最少的k元组合,使得它们的子集并集等于全集。该问题是NP-hard问题,小规模场景可用精确枚举,大规模场景需用贪心算法近似求解。

精确实现(适用于小规模n)

from itertools import combinations

def find_min_cover(n, k, t):
    # 生成所有t元子集作为全集
    all_t_subsets = set(combinations(range(1, n+1), t))
    # 生成所有k元组合作为候选集合
    all_k_combos = list(combinations(range(1, n+1), k))
    # 初始最优解设为所有组合(最坏情况)
    best_cover = all_k_combos.copy()
    
    # 从最小组合数开始枚举,找到第一个全覆盖的集合
    for cover_size in range(1, len(all_k_combos)+1):
        for candidate_cover in combinations(all_k_combos, cover_size):
            covered = set()
            for combo in candidate_cover:
                # 统计当前候选组合能覆盖的所有t元子集
                covered.update(combinations(combo, t))
            if covered == all_t_subsets:
                best_cover = candidate_cover
                return sorted(best_cover)
    
    return sorted(best_cover)

# 测试n=5, k=3, t=2
for combo in find_min_cover(5, 3, 2):
    print(', '.join(map(str, combo)))

运行结果与你给出的正确输出一致:

1, 2, 3
1, 4, 5
2, 3, 4
2, 3, 5

贪心实现(适用于较大n,如n=49)

精确枚举在n=49时完全不可行,贪心算法每次选择能覆盖最多未被覆盖t元子集的k元组合,效率更高且结果接近最优:

from itertools import combinations
from collections import defaultdict

def greedy_set_cover(n, k, t):
    all_t_subsets = set(combinations(range(1, n+1), t))
    remaining = all_t_subsets.copy()
    cover = []
    all_k_combos = list(combinations(range(1, n+1), k))
    
    # 预计算每个k元组合能覆盖的t元子集
    combo_to_covered = {}
    for combo in all_k_combos:
        combo_to_covered[combo] = set(combinations(combo, t))
    
    while remaining:
        # 找到覆盖最多剩余子集的组合
        best_combo = None
        max_covered = 0
        for combo, covered in combo_to_covered.items():
            current_covered = len(remaining & covered)
            if current_covered > max_covered:
                max_covered = current_covered
                best_combo = combo
        # 更新覆盖集合与剩余子集
        cover.append(best_combo)
        remaining -= combo_to_covered[best_combo]
        del combo_to_covered[best_combo]
    
    return sorted(cover)

# 测试n=5, k=3, t=2
for combo in greedy_set_cover(5, 3, 2):
    print(', '.join(map(str, combo)))

处理非100%覆盖的情况

若只需覆盖指定百分比的t元子集,可修改贪心算法,达到目标覆盖比例后提前停止:

from itertools import combinations
from collections import defaultdict

def greedy_set_cover_with_percent(n, k, t, target_percent=0.95):
    all_t_subsets = set(combinations(range(1, n+1), t))
    total = len(all_t_subsets)
    target_covered = int(total * target_percent)
    remaining = all_t_subsets.copy()
    cover = []
    all_k_combos = list(combinations(range(1, n+1), k))
    
    combo_to_covered = {}
    for combo in all_k_combos:
        combo_to_covered[combo] = set(combinations(combo, t))
    
    covered_count = 0
    while covered_count < target_covered and remaining:
        best_combo = None
        max_additional = 0
        for combo, covered in combo_to_covered.items():
            additional = len(remaining & covered)
            if additional > max_additional:
                max_additional = additional
                best_combo = combo
        # 更新覆盖状态
        cover.append(best_combo)
        newly_covered = remaining & combo_to_covered[best_combo]
        covered_count += len(newly_covered)
        remaining -= newly_covered
        del combo_to_covered[best_combo]
    
    return sorted(cover)

# 示例:覆盖n=5, k=3, t=2的80%子集
for combo in greedy_set_cover_with_percent(5, 3, 2, 0.8):
    print(', '.join(map(str, combo)))

原彩票场景的适配

针对你提到的6/49玩法:从49个号码中选27个重点号码,要求任意6个重点号码的子集(开奖命中),所选彩票中至少有一张包含这6个中的至少3个。可调整为击中集逻辑实现:

from itertools import combinations

def lottery_hit_cover(pool_size=27, ticket_size=6, hit_min=3):
    # 生成27个重点号码的所有6元子集(目标命中集合)
    focus_numbers = list(range(1, pool_size+1))
    target_subsets = set(combinations(focus_numbers, 6))
    remaining = target_subsets.copy()
    cover = []
    # 仅从重点号码中生成候选彩票(减少计算量)
    candidate_tickets = list(combinations(focus_numbers, ticket_size))
    
    while remaining:
        best_ticket = None
        max_hit = 0
        for ticket in candidate_tickets:
            # 统计该彩票能击中的剩余目标子集数(交集>=3)
            hit_count = 0
            for subset in remaining:
                if len(set(ticket) & set(subset)) >= hit_min:
                    hit_count += 1
            if hit_count > max_hit:
                max_hit = hit_count
                best_ticket = ticket
        # 更新覆盖集合与剩余目标子集
        cover.append(best_ticket)
        new_remaining = set()
        for subset in remaining:
            if len(set(best_ticket) & set(subset)) < hit_min:
                new_remaining.add(subset)
        remaining = new_remaining
        candidate_tickets.remove(best_ticket)
    
    return sorted(cover)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 07:44:57