【问题标题】:How to efficiently find which range a number falls into?如何有效地找到一个数字属于哪个范围?
【发布时间】:2019-03-06 02:12:52
【问题描述】:

我在一次求职面试中被问到以下问题:

给定如下的累进所得税制度:

  • 高达10000按10%征税

  • 10001到50000按20%征税

  • 50001及以上按30%征税

编写一个程序来计算给定收入的税收。

例子:

  • 对 15000 征税将是(5000 的 20% + 10000 的 10%)= 2000

  • 60000 的税收为(10000 的 30% + 40000 的 20% + 10000 的 10%)= 12000

我想出了以下伪代码:

list = (
    (1, 10000, 0.1)
    (10001, 50000, 0.2)
    (50001, infinity, 0.3)
)

taxableIncome = income
taxes = 0
while taxableIncome > 0
    find e in list such that taxableIncome is in range [e[1], e[2]]
    taxes += taxableIncome - e[1] + 1
    taxableIncome = e[1] - 1

return taxes

上述方法有效,但在最坏的情况下,列表中的项目数需要二次时间。考虑收入 = 60000 的情况;代码循环 3 次,每次都可能扫描整个列表。

有没有更快的方法来确定收入属于哪个范围? This question 有一些 Python 解决方案,但我对通用算法解决方案感兴趣,而不是库。

【问题讨论】:

  • 您应该先预先计算括号开头以下金额的税金,因为它是恒定的。
  • @MarkRansom 我不明白你指的是什么括号。一旦找到合适的支架,剩下的问题就几乎是微不足道的了。问题是,如何有效地找到括号?
  • 他们的这种伪代码语言中没有if 语句吗?只有 3 个括号,其他任何东西都将是矫枉过正。
  • :) 当然任何高级编程语言都支持if,但我被要求使代码易于扩展。这是有道理的,因为没有人会根据他们编写两个if 和一个else 语句的能力来雇用开发人员。
  • 这取决于目标是可读性、可扩展性还是效率。对于您给出的案例,if 语句在可读性和效率方面都胜出。除非您事先不知道括号的数量,否则另一种方法会更好。

标签: algorithm data-structures range


【解决方案1】:

预先计算每个范围开始的税值并将此值包含在列表中。

我还删除了 Dillon Davis 在 cmets 中注意到的过高上限,并将下限值更改为先前范围的末尾以使公式更准确

 list = (
        (0, 0, 0.1)
        (10000, 1000, 0.2)
        (50000, 9000, 0.3)
    )

对于给定的收入,使用线性搜索(如果范围数量很少)或使用二分搜索(如果有很多范围 - 几十个、几百个等)找到合适的范围

然后用简单的公式计算税收

  tax  = e[2] + (income - e[1]) * e[3]

收入15000我们可以找到

range = 2nd interval (10000, 1000, 0.2)
tax = 1000 + (15000 - 10000) * 0.2 = 
      1000 + 5000 * 0.2 = 2000

或者(使用狄龙戴维斯的建议)

  tax  = income * e[3] + (e[2] -  e[1]) * e[3])
  tax  = income * e[3] + e[2]'

为每个范围预先计算 e2' = (e[2] - e[1]) * e[3])

总体复杂度为线性或对数(带 BS)

【讨论】:

  • 但要计算总税额,您仍然需要遍历所有括号,那么使用 BS 定位有什么意义呢?
  • @Lucas Kot-Zaniewski 范围开始的总税款(在我的示例中为 0,1000,9000)仅在填写列表时计算一次。然后对于许多 income 查询,我们可以使用它们。
  • 您可以排除每个括号的上限,因为它从未使用过(并且无论如何都比下一个括号的开头少一个),并且还可以简化您的方程式。 e[3] + (income - e[1]) * e[4] == income * e[4] + (e[3] - e[1] * e[4]) 最后一位是常数,因此您可以将其替换为不同的常数 e[3]income * e[4] + e[3]
  • @MBo 啊好的我现在看到你已经修改了输入列表。旧空间与计算的权衡。您的解决方案对于固定数量的税收系统上的大量查询很有用,这可能是他们想要的。酷
  • @Lucas Kot-Zaniewski 据我了解,list 无论如何都是存储的,所以没有新的空间要求。
【解决方案2】:

这里的操作:@MBo 的回答让我思考(为此向他投了赞成票),但不幸的是,他没有以对我来说足够清晰的方式解释它,所以我们开始吧。

N 为括号数。

NAIVE 方法:线性搜索适当的税级,计算括号内超额收入的税,然后递归计算括号下的应税收入的税。例如,收入15000 属于以10001 开头的括号;这个范围的税是(15000 - 10000) * 0.2 = 1000 + 税10000。 这行得通,但线性搜索在最坏的情况下可能会采用O(N),如果初始收入落在最高 括号,代码将循环N次。我们最终得到了一个 O(N^2) 算法。

更好的方法: 对括号进行二进制搜索,然后按照幼稚方法进行。 O(N log(N)).

动态编程方法:这个问题展示了应用动态编程的两个标准, 最优子结构和重叠子问题。为什么?每个等级的总税款是当前等级的税款和剩余应税收入的税款之和。对于每个括号,递归解决方案一遍又一遍地计算较低括号的税。

因此,我们以自下而上的方式预先计算并记忆上一桶的应税收入税款。 这需要O(N) 时间。对括号进行二分搜索需要log(N) 时间。现在计算税收需要O(1) 次,总体上为我们提供了一个线性时间算法。

Scala 代码:随意适应您喜欢的编程语言。

def taxes(income: Int, brackets: IndexedSeq[(Int, Double)]): Double = {
    val dp = brackets
      .zipWithIndex
      .foldLeft((0d, IndexedSeq.empty[(Int, Double, Double)])) { case ((sum, acc), (cur, i)) =>
        val taxesOnPrevBracket = if (i > 0) {
          val prev = brackets(i - 1)
          (cur._1 - prev._1) * prev._2
        } else 0d
        val cumulativeTaxes = sum + taxesOnPrevBracket

        (cumulativeTaxes, acc :+ (cur._1, cur._2, cumulativeTaxes))
      }
      ._2

    @tailrec
    def findBracket(start: Int, end: Int): Int = {
      if (end - start <= 1) start
      else {
        val mid = start + (end - start) / 2
        if (income > brackets(mid)._1) findBracket(mid, end)
        else findBracket(start, mid)
      }
    }

    val br = dp(findBracket(0, brackets.size - 1))
    val inc = income - br._1 + 1
    val tx = inc * br._2 + br._3
    println(s"Taxable income: $income, bracket: $br, taxes: $tx")
    tx
}

brackets 这里是元组序列、起始值(感谢@Dillon Davis 的想法)和税率。

2021 年 7 月编辑:

关键的一点是,最高级别的税收会根据收入而变化,但较低级别的税收是不变的。例如,对于属于第二类的收入 15000,我们提前不知道要根据第二类征税的超额金额。一旦我们对它征税,剩下的 10000 就属于第一个桶,并且可以预先计算对它的征税。这是一个 Python 解决方案。

import bisect

brackets = [
    (1, 0.1),
    (10001, 0.2),
    (50001, 0.3)
]
starts = [x for x, _ in brackets]
precomputed = []
cumulative_sum = 0
for i in range(len(brackets) - 1):
    cumulative_sum += (starts[i + 1] - starts[i]) * brackets[i][1]
    precomputed.append(cumulative_sum)

for income in [9000, 15000, 60000]:
    i = bisect.bisect_right(starts, income)
    i -= 1
    
    excess = income - brackets[i][0] + 1
    taxes = excess * brackets[i][1]
    if i > 0:
        taxes += precomputed[i - 1]

    print(f"Taxes on income {income} is: {taxes}")

【讨论】:

    猜你喜欢
    • 2022-09-28
    • 1970-01-01
    • 2014-12-12
    • 2011-09-21
    • 1970-01-01
    • 2020-11-22
    • 1970-01-01
    • 2013-07-18
    • 1970-01-01
    相关资源
    最近更新 更多