题目一
题目描述
求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
示例 1:
输入: n = 3
输出: 6
示例 2:
输入: n = 9
输出: 45
时间、空间超100%的解答
等差数列的前n项和,自然是用求和公式,时间空间都是O(1),但是根据题目,不允许使用乘除法,自然只能使用递归了
function multi(a, b){
return b && ((b & 1 && a) + multi(a << 1, b >> 1))
}
var sumNums = function(n) {
return multi(n, n+1)>>1
};
递归实现乘法
大致思路就是不断的把右边的2移动到左边去,因为2的变化可以通过<<、>>实现,遇到奇数就停下来进行+运算,否则一直进行乘除运算
- 判断b不能为零
- 判断b的奇偶性,b&1 == 0是偶数执行3,否则b&1 == 1是奇数执行4
- 直接b的一个2移动给a,进入下一次递归
- 因为b是奇数,multi(a << 1, b >> 1)) == multi(a << 1, b-1 >> 1)),所以多加一个a,进入下一次递归
- 重复1,知道b为0返回0,开始退出递归
6 * 7
= 6 + multi(12, 3)
= 6 + 12 + multi(24, 1)
= 6 + 12 + 24 + multi(48, 0)
= 42
8 * 8
= multi(16, 4)
= multi(32, 2)
= multi(64, 1)
= 64 + multi(128, 0)
小结
- x & 1返回x的奇偶性,为零则是偶数,否则为奇数
- a && b的返回值要么是0或flase,要么是b或true
题目二
题目描述
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
时间、空间超100%的解答
常规思路是储存一个map:键是数字,值是出现的次数。下列题解思路大致:
- num第一次出现时:a = nums; b =0;
- num第二次出现时:a = 0; b = nums;
- num第三次出现时:a = 0; b =0;
var singleNumber = function (nums) {
let a = 0;
let b = 0;
nums.forEach(num => {
a = (a ^ num) & ~b;
b = (b ^ num) & ~a;
});
return a;
};
小结
位运算的性质:a^a=0、a^0=a、a^b^c=a^c^b
题目三
题目描述
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
时间、空间超100%的解答
这题同上一题的思路一样,利用性质a^a=0,所以,7^2^3^4^4^3^2 = 7。
var singleNumbers = function(nums) {
let sum = 0;
let result = [0,0]
nums.forEach(element => {
sum^=element
});
let flag = (-sum)&sum
nums.forEach(element => {
if((flag&element)==0){
result[0]^=element
}else{
result[1]^=element
}
});
return result;
};
上述代码的时间空间复杂的都是超100%,说明位运算符才是这些题的正确解题姿势
x & -x的意义
我们都知道, -x 的值, 其实就是在x的值的基础上进行按位取反(~x)之后在增加1所得, 也就是说x & -x == x & (~x + 1)
x 为奇数
x 为奇数比较简单, 因为奇数取反后的值一定是偶数, 而偶数的值 + 1之后, 并不会影响进位。
如果是x是奇数, 那x & -x 的结果一定是1
x 为偶数
我们都知道, 当一个奇数 + 1时,表示的二进制数则会发生进位,这样的话, 会产生一个连锁反应,也就是最低位的那些连续的1都会被清0, 如:
0000 0000 0111 1111 + 1=0000 0000 1000 0000
如果一个偶数, 如 0000 0100 1110, 取反后的结果就变成了 1111 1011 0001,而当这个值 + 1之后由于发生了进位, 即
1111 1011 0001 + 1 = 1111 1011 0010
这个结果再与最初的值相与后, 只会有一位保留为
0000 0100 1110 & 1111 1011 0010 = 0000 0000 0010
这个结果与最初的值又有什么关系呢?很显然, 这个值的与原值的末位0的个数是一致的.仔细一想, 这个原值肯定是能被结果值整除的, 而这个结果值又是2^ n
当一个偶数与它的负值相与时,结果是能整除这个偶数的最大的2的幂, 即: m = n & -n , 则 n % m = 0, 且 m = 2 ^ k
最终结论
当一个数与其取负后的值相与, 如果这个数是偶数, 则结果是能整除这个偶数的最大的2的幂(即: m = n & -n , 则 n % m = 0, 且 m = 2 ^ k), 如果这个数是奇数, 则结果必为1