# 笨阶乘(1006)
# 题目
通常,正整数 n 的阶乘是所有小于或等于 n 的正整数的乘积。例如,factorial(10) = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1
。
相反,我们设计了一个笨阶乘 clumsy:在整数的递减序列中,我们以一个固定顺序的操作符序列来依次替换原有的乘法操作符:乘法(*),除法(/),加法(+)和减法(-)。
例如,clumsy(10) = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1
。然而,这些运算仍然使用通常的算术运算顺序:我们在任何加、减步骤之前执行所有的乘法和除法步骤,并且按从左到右处理乘法和除法步骤。
另外,我们使用的除法是地板除法(floor division),所以 10 * 9 / 8 等于 11。这保证结果是一个整数。
实现上面定义的笨函数:给定一个整数 N,它返回 N 的笨阶乘。
# 示例
输入:4
输出:7
解释:
7 = 4 * 3 / 2 + 1
输入:10
输出:12
解释:
12 = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1
# 提示
- 1 <= N <= 10000
- -2^31 <= answer <= 2^31 - 1 (答案保证符合 32 位整数。)
# 算法
# 栈
本题涉及到运算顺序——“先乘除,后加减”,设计这类题目的时候一般考虑使用栈
来实现
- 出现乘除法时,我们把栈顶元素取出并且和当前数字
i
进行运算 - 出现加法时,我们直接推入栈
- 出现减法时,我们当作加上一个负数,将他的相反数推入栈
最后我们将栈中数字累加得到结果
const clumsy = N => {
const stack = [N--];
for (let i = N, j = 0; i > 0; i--, j++) {
if (j % 4 === 0) {
// 乘
stack.push(stack.pop() * i);
} else if (j % 4 === 1) {
// 除
const cur = stack.pop();
stack.push(cur >= 0 ? Math.floor(cur / i) : Math.ceil(cur / i));
} else if (j % 4 === 2) {
// 加
stack.push(i);
} else if (j % 4 === 3) {
// 减
stack.push(-i);
}
}
return stack.reduce((acc, cur) => (acc += cur));
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 归纳公式
笨阶乘按乘除加减
的运算顺序依次重复,我们将其看作一轮,每一轮的前三项可一般化为N(N-1)/(N-2)
,化简得到N+1+2/(N-2)
,最后一项2/(N-2)
当N>4
且在地板除法
的规则下为0
,所以每一轮前三项可化简为N+1
当我们重复多写几项时发现可以消去,除了最后可能余出的不完整的轮次,前面部分通过消去可化简得N+1
然后我们通过N的值,分析余出的项的情况:
N%4 === 0
时,有N(N-1)/(N-2)+...+5-4*3/2+1
,即N+1+5-6+1
,得到N+1
N&4 === 1
时,有N(N-1)/(N-2)+...+6-5*4/3+2-1
,即N+1+6-6+2-1
,得到N+2
N%4 === 2
时,有N(N-1)/(N-2)+...+7-6*5/4+3-2*1
,即N+1+7-7+3-2
,得到N+2
N%4 === 3
时,有N(N-1)/(N-2)+...+8-7*6/5+4-3*2/1
,即N+1+8-8+4-6
,得到N-1
然后我们单读判断N<5
的情况,然后结合上面的规律,得到公式:
const clumsy = N => {
if (N === 1) return 1;
if (N === 2) return 2;
if (N === 3) return 6;
if (N === 4) return 7;
if (N % 4 === 0) return N + 1;
if (N % 4 <= 2) return N + 2;
if (N % 4 === 3) return N - 1;
};
2
3
4
5
6
7
8
9
当然我们也可以先循环打印出不同N递增时的结果,然后归纳总结规律