看似一道很简单的题,在LeetCode中却给到了中等难度的定义,主要的原因是题目限制了使用条件:要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
我们先想想常规的解决思路有哪些:如果不限制乘除法的话,我们可以直接使用等差数列求和公式就解决了: ( n + 1 ) ∗ n / 2 (n+1)*n/2 (n+1)∗n/2。但这个方法最大的问题在于无法通过别的运算法转换乘以n的问题(尽管我们知道乘除可以通过位运算来间接实现,但运算只能限于2的倍数的乘法,如果是乘以3、乘以5或者其他的数字就没办法了)
如果这个题只限定不允许使用乘除的话,其实我们还有别的办法,就是通过循环来将乘法化为加法(利用了乘法的本质),但如果循环也不允许使用呢?我们还可以通过递归方法来实现,但递归到最后还需要条件语句来跳出递归,而这些都是题目所不允许使用的,那么到底应该怎么做呢?
其实这些方法各自都只有一点问题不被满足,我们可以通过别的方法来解决这一点问题。
递归方法
递归方法需要解决的就是转化条件判断语句,这里利用了逻辑运算的短路原理,即A&&B,如果A为Fale,则直接跳过B的判断;同理,A||B,如果A为True,也会直接跳过B的判断。因而,我们就可以利用这样的思想来实现,代码如下:
class Solution {
public:
int sumNums(int n) {
n && (n = n + sumNums(n-1));
return n;
}
};
由于C++语言中0代表false,非0代表true,因而巧妙的利用n解决了短路判断的条件,可谓十分巧妙!
求和公式法
基于之前的讨论,我们知道公式法需要解决的就是乘以n的问题,而我们目前仅会将2、4、8这样的乘数通过位运算来转换,但对于其他的乘数就无法实现了,所以我们需要将乘法计算从本质上转换为加法。
这里参考了一个叫做俄罗斯农民算法的思想,这个算法原本将乘法用另一种思想进行转换,从而快速得到最终答案,本质上是将被乘数的每一位“贡献值”分布得出,详细的算法不在多做介绍,我们之间进入正题。
有兴趣的同学可以看看这一篇博主的文章
对于 A ∗ B A*B A∗B这个问题,我们将A和B都转换成二进制,那么B中第 i i i 位对计算结果的贡献值为 A ∗ 2 i = A < < i A*2^i=A<<i A∗2i=A<<i,因而我们可以按位遍历B的每一位,从而将每一位的贡献值都分别得出,再求和即可(注意到虽然需要遍历,但题目中已经规定了 n n n的范围,即 1 < = n < = 10000 1 <= n <= 10000 1<=n<=10000,所以n最多不会超过14位,我们可以将循环直接转换成14次顺序执行的代码即可)
class Solution {
public:
int sumNums(int n) {
int ans = 0, A = n, B = n + 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
(B & 1) && (ans += A);
A <<= 1;
B >>= 1;
...
//上述代码段重复14次
return ans>>1;
//最后右移一位是因为求和公式最后还有一个除以2
}
};