题目
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
解析
预备知识
首先观察题目要求,不能使用乘除法,循环和显示的判断语句的等。而求1+2+3+...+n
的做法通常是需要用到循环的或者递归的,另一种则是直接用公式求和公式:n(n + 1) / 2
,但是这种是需要乘除法的。所以这道题其实很考察思维能力。
结合上述约束,我们发现逻辑运算符和位运算符是可以使用的,所以主要从这两方面入手。
思路一
思路一从逻辑运算符出发,逻辑运算符通常与逻辑表达式结合使用的,既然不能显示的使用if语句,那就是如何使用逻辑表达式来代替if的效果。我们可以利用逻辑运算符的&&, ||
的短路特性,使得代码可以在某个逻辑表示式为真或者假时发生短路,这样就可以结束循环或者递归。
对于1+2+3+...+n
的问题既然不能显示使用循环,那可以使用与之对应的递归啊。这里就可以利用上面分析的逻辑运算符的短路特性作为递归中止条件。
短路的逻辑与:
/**
* 利用短路的逻辑与
* @param n
* @return
*/
public static int Sum_Solution(int n) {
int sum = n;
boolean flag = (n != 0 && (sum += Sum_Solution(n - 1)) >= 0);
return sum;
}
短路的逻辑或
/**
* 短路的逻辑或
* @param n
* @return
*/
public static int Sum_Solution2(int n) {
int sum = n;
boolean flag = (n == 0 || (sum += Sum_Solution2(n - 1)) >= 0);
return sum;
}
思路二
思路二从允许的位运算出发,主要如何利用位算法来模拟乘除法,这样就可以使用n(n + 1) / 2
公式进行求解。其中对于除2操作可以右移1位达到,所以问题转化为如何利用位运算求出n(n + 1)
的解。
首先看一下位运算乘法推导过程:
a * b = a * (b_{n - 1}2^{n - 1} + b_{n - 2}2^{n - 2} +...+ b_{1}2^{1} + b_{0}2^{0}) = a << (n - 1) + a << (n - 2) +...+ a << 0)
通过以上分析,可以得出乘法确实可以使用位运算模拟。我们把b写成二进制形式,其中b_{n - 1}
表示二进制项第n - 1
的系数,取值0或者1,表示这一位是0还是1。又因为一个数乘以2的幂次方,都等同左移幂次数。利用位运算的思路为:对于二进制乘数,若该位为0,则什么都不做,若该位为1,则把被乘数左移对应的位所在的位置即可 。
我们在遍历乘数的每一位的时候可能是需要用到循环判断是否遍历,所以这里还是利用递归和短路逻辑运算符,不过复杂度已经由之前的O(n)
变成了O(logn)
。
static int count;
public static int Sum_Solution4(int n) {
int a = n, b = n + 1;
count = 0;
return sum(a, b) >> 1;
}
static int sum(int a, int b) {
int sum = 0;
boolean flag = ((b & 1) != 0) && (sum += (a << count)) > 0;
count++;
boolean flag1 = (b != 0) && (sum += sum(a, b >> 1)) > 0;
return sum;
}
可不可以不利用count呢,还有我们能不能利用上次已经移位的a呢,可以。
public static int Sum_Solution5(int n) {
int a = n, b = n + 1;
return sumAToB(a, b) >> 1;
}
static int sumAToB(int a, int b) {
int sum = 0;
boolean flag1 = ((b & 1) != 0) && (sum += a) > 0;
boolean flag2 = ((b != 0)) && (sum += sumAToB(a << 1, b >> 1)) > 0;
return sum;
}
总结
传统的做法不再适用时,就可以利用位运算了。