在处理组合问题时,我们通常做乘法。但有时加法也会出现——我们如何区分何时用哪种?
这里有几个我用来区分它们的思维模型。
心智模型:不同维度
让我们看一个简单的场景:你有4件衬衫和8条裤子,你能搭配出多少套衣服?
本质上,你是在这个网格上选择一个点:

衬衫和裤子存在于不同的维度,其面积代表不同的解决方案。我们可以选择任意一点 在网格中 并且我们有4 × 8 = 32种选择。
现在,假设我们有4件衬衫和8条裤子,但必须挑选一件物品出售。在这里,它们处于同一个“衣物”维度:

我们可以随机选择任意一点 沿着直线 并且有4 + 8 = 12种选择。
可以理解为“不同维度 vs. 相同维度”或“网格 vs. 直线”。
心智模型:与(AND) vs 或(OR)
另一种解释是“与(AND)” (乘法) vs. “或(OR)” (加法)。
假设我们必须选择一件衬衫与(AND)一条裤子。我们需要两者才能避免麻烦。场景如下:
pick among 4 shirts AND among 8 pants = 4 * 8 = 32 choices
如果麦当劳放宽规定,允许一件衬衫或(OR)一条裤子呢?(但不能两者都选——哎呀。)那么我们有:
pick among 4 shirts OR among 8 pants = 4 + 8 = 12 choices
写出场景通常更容易思考,特别是当有多个维度时(衬衫、裤子、帽子、鞋子)。
当你内化了这些类比,你会很快识别出是需要乘法还是加法。
示例:组合与排列公式
让我们深入思考一下。 排列公式 是:
![]()
我们该如何思考这个问题?
分子($n!$)是假设每个$n$个选择都有其自身维度时的最大体积。8个人的排列数是8 * 7 * 6 * 5 * 4 * 3 * 2 * 1。
但假设我们只关心前3个决策——在8名参赛者中挑选金牌、银牌和铜牌。在这种情况下,我们通过除以未使用的5个维度(它们本身有5!种选择)来缩小解空间。我们得到8! / 5! = 8 * 7 * 6 = 336种选择,一般公式为$\frac{n!}{(n-k)!}$。
(如果乘法创造维度,那么除法应该移除它们。)
现在,假设奖牌是相同的:我们给8人中的3人颁发一个罐头。我们需要进一步移除维度,因为我们的解空间中每个排列有3! = 3 * 2 * 1 = 6种冗余。我们再次缩小解空间:
![]()
(我想象解空间体积变得更密集。)
啊!这就是组合与排列公式的运作方式。我们创建最大体积,然后按未使用的维度收缩。在脑海中将场景翻译成对你有意义的版本。
示例:抛硬币
以下是我思考几个示例问题的方式。
你抛硬币10次。有多少种方式可以得到至少7次正面?
首先,总可能性数量是2^10 = 1024。直觉上,我把每次抛掷看作沿不同维度的一次决策,而不是同一个数轴。这意味着我们有2 * 2 * 2 *...种可能性,而不是2 + 2 + 2 +...种可能性。
几何上,这将是一个10维的“选择空间”,或者写成:
(Heads OR tails) AND (Heads OR tails) AND (Heads OR tails) AND ...
好的。现在,我们如何得到至少7次正面?这意味着有0次反面[10次正面]、1次反面[9次正面]、2次反面[8次正面]或3次反面[7次正面]。
转换为文字描述,这变成:
choices we want = (0 tails OR 1 tail OR 2 tails OR 3 tails)
给定我们的10次抛掷,结果的数量是:
- 0个反面 = 1种选择(全部正面)
- 1个反面 = 10种选择(恰好一次为反面)
- 2个反面 = C(10,2) =
10*9/(2*1) = 45 choices基于组合(Combination)公式 - 3个反面 = C(10,3) =
10 * 9 * 8 / (3 * 2 * 1) = 720 / 6 = 120 choices
所以,总数是
choices we want = (1 + 10 + 45 + 120) = 176
为了好玩,看到这种情况发生的概率是:
176 / 1024 = 17.2%
乘法超越了“重复加法”。它是一种通用的组合概念,我仍在发现其解释。不要局限于单一含义。
数学愉快。
附录:计算机编程
将AND/OR语句转化为算术运算,能很好地映射到布尔逻辑。
如果A和B是值为1或0的变量,我们可以写成:
A AND B = A * BA OR B = A + B
在大多数语言中,正数会被评估为“真”,所以A + B = 2为真。注意这个OR是“包含性OR”,允许两个值都为真。要强制实现排他性OR,我们可以取除以2后的余数:
A XOR B = (A + B) % 2
大多数编程语言有独立的运算符用于AND(&&)、OR(||)和XOR(^),但看到逻辑如何用常规算术工作还是不错的。
此外,“if/then/else”语句可以转换为算术运算。
如果 y 是一个变量(1或0),它决定了一个结果,而不是:
if (y) {
result = ResultIfTrue;
}
else {
result = ResultIfFalse;
}
我们可以使用单一语句:
result = y * ResultIfTrue + (1 - y) * ResultIfFalse
这个版本避免了分支的需求(分支对CPU来说代价高昂),并且是一个我们可以用微积分优化的公式(用于机器学习算法)。