[LeetCode] 1006. Clumsy Factorial 笨拙的阶乘


Normally, the factorial of a positive integer n is the product of all positive integers less than or equal to n.  For example, factorial(10) = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1.

We instead make a clumsy factorial: using the integers in decreasing order, we swap out the multiply operations for a fixed rotation of operations: multiply (*), divide (/), add (+) and subtract (-) in this order.

For example, clumsy(10) = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1.  However, these operations are still applied using the usual order of operations of arithmetic: we do all multiplication and division steps before any addition or subtraction steps, and multiplication and division steps are processed left to right.

Additionally, the division that we use is floor division such that 10 * 9 / 8 equals 11.  This guarantees the result is an integer.

Implement the clumsy function as defined above: given an integer N, it returns the clumsy factorial of N.

Example 1:

Input: 4
Output: 7
Explanation: 7 = 4 * 3 / 2 + 1

Example 2:

Input: 10 Output: 12 Explanation: 12 = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1

Note:

  1. 1 <= N <= 10000
  2. -2^31 <= answer <= 2^31 - 1  (The answer is guaranteed to fit within a 32-bit integer.)

这道题定义了一种笨拙的阶乘,与正常的连续相乘不同的是,这里按顺序使用乘除加减号来计算,这里要保持乘除的优先级,现在给了一个正整数N,让求这种笨拙的阶乘是多少。由于需要保持乘除的优先级,使得问题变的稍微复杂了一些,否则直接按顺序一个个的计算就好。根据题目中的例子2分析,刚开始的乘和除可以直接计算,紧跟其后的加法,也可以直接累加,但是之后的减号,就不能直接计算,而是要先计算后面的乘和除,所以遇到了减号,是需要特殊处理一下的。博主最初的做法就是用一个字符串数组来保存乘除加减号,然后用个变量j来循环遍历这个数组,从而知道当前该做什么操作。还需要一个变量 cur 来计算乘和除优先级的计算,初始化为N,此时从 N-1 遍历到1,若遇到乘号,则 cur 直接乘以当前数字,若遇到除号,cur 直接除以当前数字,若遇到加号,可以直接把当前数字加到结果 res 中,若遇到减号,此时需要判断一下,因为只有第一个乘和除后的结果是要加到 res 中的,后面的都是要减去的,所以要判断一下若当前数字等于 N-4 的时候,加上 cur,否者都是减去 cur,然后 cur 更新为当前数字,因为减号的优先级小于乘除,不能立马运算。之后j自增1并对4取余,最终返回的时候也需要做个判断,因为有可能数字比较小,减号还没有出来,且此时的最后面的乘除结果还保存在 cur 中,那么是加是减还需要看N的大小,若小于等于4,则加上 cur,反之则减去 cur,参见代码如下:


解法一:

class Solution {
public:
    int clumsy(int N) {
        int res = 0, cur = N, j = 0;
        vector<char> ops{'*', '/', '+', '-'};
        for (int i = N - 1; i >= 1; --i) {
            if (ops[j] == '*') {
                cur *= i;
            } else if (ops[j] == '/') {
                cur /= i;
            } else if (ops[j] == '+') {
                res += i;
            } else {
                res += (i == N - 4) ? cur : -cur;
                cur = i;
            }
            j = (j + 1) % 4;
        }
        return res + ((N <= 4) ? cur : -cur);
    }
};

再来看一种比较简洁的写法,由于每次遇到减号时,优先级会被改变,而前面的乘除加是可以提前计算的,所以可以每次处理四个数字,即首先处理 N, N-1, N-2, N-3 这四个数字,这里希望每次可以得到乘法计算时的第一个数字,可以通过 N - i*4 得到,这里需要满足 i*4 < N,知道了这个数字,然后可以立马算出乘除的结果,只要其大于等于3。然后需要将乘除之后的结果更新到 res 中,还是需要判断一下,若是第一个乘除的结果,需要加上,后面的都是减去。乘除后面跟的是加号,所以要加上 num-3 这个数字,前提是 num 大于3,参见代码如下:


解法二:

class Solution {
public:
    int clumsy(int N) {
		int res = 0;
		for (int i = 0; i * 4 < N; ++i) {
			int num = N - i * 4, t = num;
			if (num >= 3) t = num * (num - 1) / (num - 2);
			res += (i == 0) ? t : -t;
			if (num > 3) res += (num - 3);
		}
		return res;
    }
};

接下来这种方法基本上很难想到,完全是利用了数学知识,这里参见大神 Lisanaaa 和 lee215
的帖子来讲吧:

N * N - 3 * N = N * N - 3 * N + 2 - 2
N * (N - 3) = (N - 1) * (N - 2) - 2。(factorization)
N = (N - 1) * (N - 2) / (N - 3) - 2 / (N - 3) (Divide N - 3 on both side)
N - (N - 1) * (N - 2) / (N - 3) = - 2 / (N - 3) 
- 2 / (N - 3) = 0, If N - 3 > 2.
So when N > 5, N - (N - 1) * (N - 2) / (N - 3) = 0
N + 1 - N * (N - 1) / (N - 2) = 0  (Replace N with N + 1)
N + 1 = N * (N - 1) / (N - 2)

由上面的推导可以得到 i + 1 = i * (i - 1) / (i - 2) when i >= 5,则中间很多项可以相互抵消:

i * (i-1) / (i-2) + (i-3) - (i-4) * (i-5) / (i-6) + (i-7) - (i-8) * .... + rest elements
=   (i+1) + "(i-3)" - "(i-4) * (i-5) / (i-6)" + "(i-7)" - "(i-8) * " .... + rest elements
=   (i+1) + "(i-3) - (i-3)" + "(i-7) - (i-7)" +  ....  + rest elements
=   (i+1) + rest elements
when 0 element left: final result is (i+1) + ... + 5 - (4*3/2) + 1, which is i+1
when 1 element left: final result is (i+1) + ... + 6 - (5*4/3) + 2 - 1, which is i+2
when 2 element left: final result is (i+1) + ... + 7 - (6*5/4) + 3 - 2 * 1, which is i+2
when 3 element left: final result is (i+1) + ... + 8 - (7*6/5) + 4 - 3 * 2 / 1, which is i-1

所以可以看出,在N小于5一下,是没有规律的,不过可以直接计算出所有的结果,放到一个数组中,而N大于等于5之后,就按上述的规律重复着,则可以把 1,2,2,-1 放到一个数组中,通过对4取余数来得到正确的值,再加上N即可,参加代码如下:


解法三:

class Solution {
public:
    int clumsy(int N) {
        int a[5] = {-1, 1, 2, 6, 7};
        int b[4] = {1, 2, 2, -1};
        return N < 5 ? a[N] : N + b[N % 4];   
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/1006


参考资料:

https://leetcode.com/problems/clumsy-factorial/

https://leetcode.com/problems/clumsy-factorial/discuss/252257/C%2B%2B-3-lines.

https://leetcode.com/problems/clumsy-factorial/discuss/252247/C%2B%2BJava-Brute-Force

https://leetcode.com/problems/clumsy-factorial/discuss/252279/You-never-think-of-this-amazing-O(1)-solution


LeetCode All in One 题目讲解汇总(持续更新中...)

posted @ 2021-01-19 10:08  Grandyang  阅读(449)  评论(0编辑  收藏  举报
Fork me on GitHub