# 笨阶乘(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));
};
1
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;
};
1
2
3
4
5
6
7
8
9

当然我们也可以先循环打印出不同N递增时的结果,然后归纳总结规律