【问题标题】:Algorithm calculate the smallest sum that a number fits into算法计算一个数字适合的最小和
【发布时间】:2021-06-17 23:04:10
【问题描述】:

给定一组数字,找到任意数字适合的最小倍数和

  • 集合中的数字可以多次使用(或根本不使用)来实现“sum”
  • 这组数字可以是任何正小数(即1, 4, 4.5
  • 给定/任意数字阈值可以是任何小数(即5

替代措辞:

  • 找出给定数可以与最小余数相匹配的倍数的组合

  • 找到一个数字可以四舍五入的最小“总和”

  • 每个组合中使用的实际数字本身对这个特定挑战并不重要

    • 虽然锻炼起来也很有趣 ^

目标是找到最小的“总和”(精确的或四舍五入的总和);但是,我很乐意探索/了解其他因素

其他解决方案似乎从精确

Algorithm for calculating number of fitting boxes

  • 不完全是最小零钱问题

其他人说子集和问题的变体

目前解决问题的幼稚伪思维:

  1. 获取每个的倍数
  2. 逐步增加所有元素的组合总和(/多个元素的组合;不确定最好的方法)
  3. 当每个求和器超过给定数字(阈值)时停止
  4. mod 比较通过给定数字的最小余数 一世。 可能不需要此步骤,具体取决于组合是如何一起/单独构建的
  5. 已找到总和

认为对于这个或其他明显的 python 示例可能有数学解决方案

https://www.geeksforgeeks.org/find-smallest-range-containing-elements-from-k-lists/

Subset whose sum is the smallest sum over a specific threshold - 我认为这种动态规划非常类似于这里的挑战;但是,对于如何以务实的方式逐步组合/乘法(+记忆)所有潜在的元素组合,我遇到了一些障碍。

递归反向减法在这类问题中效果好吗?

示例测试用例

  1. [1, 4, 4.5]设置为任意数字5;最小总和 = 5 (4 + 1)
  2. 设置[1, 4, 4.5]为任意数5.1;最小总和 = 5.5 (4.5 + 1)
  3. [20, 40, 9001]设置为任意数88;最小总和 = 100(40 + 40 + 20 或 20 + 20 + 20 + 20 + 20,等等)
  4. [20, 40, 9001]设置为任意数字145;最小总和 = 160(40 + 40 + 40 + 40 或 20 + 20 + 20 + 20 + 20 + 20 + 20 + 20,等等)
  5. [20, 40, 9001]设置为任意数字9000;最小总和 = 9000(我认为是 40 * 225 等)
  6. [20, 40, 9001]设置为任意数字9001;最小总和 = 9001(9001,举例说明具有奇怪的不可分割分量的情况,给定任意集合)
  7. [20, 40, 9001]设置为任意数18002;最小总和 = 18002 (9001 + 9001)
  8. [100, 300, 420]设置为任意数字101;最小总和 = 200 (100 + 100)
  9. 设置[100, 300, 420]为任意数398.001;最小总和 = 400 (300 + 100)
  10. [100, 300, 420]设置为任意数字404;最小总和 = 420 (420)

感谢您提供任何其他上下文、指针或简单的伪代码解决方案,您可以提供帮助。

【问题讨论】:

  • 我将从简化它开始,将问题转换为整数问题:如果值是非整数,则将其表示为分数。然后将所有值(包括所需结果)乘以分母的 LCM。然后取所有值的 GCD。如果它不除所需的总和,则不存在精确的解决方案。如果它确实将它分开,那么可能存在精确解。如果允许负乘数,它肯定会存在。但既然它们似乎不是,它可能不存在。
  • 第 8 个测试用例中的最小总和应该是 200 而不是 300?
  • @hilberts_drinking_problem 不,因为任何值都可以使用 0 到多次,所以如果他可以在 1 值中使用它就可以了
  • @Ryan 我认为使用 100 两次导致的余数比使用 300 一次要小。
  • @hilberts_drinking_problem 你是对的,我解释了你是对的,然后继续说,哈哈

标签: javascript python algorithm subset-sum


【解决方案1】:

这本身可能不是一种算法,但您可以使用混合整数规划来解决这个问题。这是在 Python 中使用PuLP 的示例:

import pulp

def solve(lst, target):
  # define a minimization problem
  prob = pulp.LpProblem("CombProb", pulp.LpMinimize)
  # vars[i] counts the number of time lst[i] is used in an optimal solution
  vars = pulp.LpVariable.dicts("count",  range(len(lst)), lowBound=0, cat="Integer")
  # define the objective
  prob += sum(vars[i] * lst[i] for i in range(len(lst)))
  # define the constraint
  prob += sum(vars[i] * lst[i] for i in range(len(lst))) >= target
  # solve the problem and check termination status
  assert prob.solve() == 1
  # return the objective value and involved list elements
  return prob.objective.value(), {lst[i]: vars[i].varValue for i in range(len(lst))}

tests = [
#   (lst, target, solution)
    ([1, 4, 4.5], 5, 5),
    ([1, 4, 4.5], 5.1, 5.5),
    ([20, 40, 9001], 88, 100), 
    ([20, 40, 9001], 145, 100),
    ([20, 40, 9001], 9000, 9000),
    ([20, 40, 9001], 9001, 9001),
    ([20, 40, 9001], 18002, 18002),
    ([100, 300, 420], 101, 200),
    ([100, 300, 420], 398.001, 400),
    ([100, 300, 420], 404, 420),
]

for i, (lst, target, sol) in enumerate(tests, start=1):
  res = solve(lst, target)[0]
  if res == sol:
    continue
  else:
    print(f"Error in case {i} with expected solution {sol} and computed solution {res}")

测试用例4似乎有错误,但其他测试用例通过。

【讨论】:

  • 感谢您使用混合整数线性规划进行注释良好的求解。给了我很多新的概念来学习这项技术。 PuLP 库是开始深入研究的最便捷方式吗?感谢您也发现了测试用例 4 错字 - 已修改。查看文档—CombProb 文本命名是指组合概率条件?最小化。先枚举,然后向下优化,直到满足条件?它建立组合的详尽程度 - 在考虑类似问题时我应该实施软/硬限制吗?
  • @algorerhythm 如果您想学习如何使用求解器,PuLP 中的示例可能是一个好的开始。您也可以查看PyomoJuMP。现代求解器往往非常复杂,可能很难说出解是如何计算的; “CombProb”是我随便编的一个名字,松散地代表“组合问题”。
【解决方案2】:

我根本没有考虑过效率,但是一个幼稚的实现看起来很简单:

const {ceil, min} = Math;
const range = (lo, hi) => [... Array (hi - lo + 1)] .map ((_, i) => i + lo);

const minMult = ([n, ...ns], t) => 
   ns .length == 0 
    ? n * ceil (t / n)
    : min ( ...range (0, ceil (t / n)) .map (k => k * n + minMult (ns, t - k * n)));

[
  [[1, 4, 4.5], 5],
  [[1, 4, 4.5], 5.1],
  [[20, 40, 9001], 88],
  [[20, 40, 9001], 145],
  [[20, 40, 9001], 9000],
  [[20, 40, 9001], 9001],
  [[20, 40, 9001], 18002],
  [[100, 300, 420], 101],
  [[100, 300, 420], 398.001],
  [[100, 300, 420], 404]
] .forEach (
  ([ns, t]) => console .log (`minMult ([${ns.join(', ')}], ${t}) //=> ${minMult(ns, t)}`)
)
.as-console-wrapper {max-height: 100% !important; top: 0}

我们重复数组的长度。当我们只有一个数字时,答案就是该数字的最小倍数,不大于目标。否则,我们将第一个数字的每个倍数向上取到不大于目标的最小倍数,然后重复剩余的数字和原始数字的未覆盖部分,将结果加在一起。

JS 没有range 函数,所以我们必须包含我们自己的函数。这个版本比 Python 更简单,不提供 step 参数,并且包含最后一个值,而不是在缺少它时停止。

【讨论】:

    猜你喜欢
    • 2011-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多