前言
近来看见一些群里面多次出现讨论不用中间变量交换两个数怎么做。虽然这是很古老的问题,大家懂的也懂。但实际上,方法会有很多种,我们一起来看看
何为不用中间变量
如果使用中间变量,那就是这样
var a = 1;
var b = 2;
var temp = b;
b = a;
a = temp;
如果是不用中间变量,那就是无var temp
来辅助这个过程。刷算法题的时候,我们通常会看见一些题目有"不借助额外空间"的要求,实际上,不用中间变量 <=> 不借助额外空间 <=> 原地修改。那么也很明显,解决问题的根本方向就是,要把两个变量的信息同时存到一个现有变量上,再实现交换
基于四则运算
相信很多人很快可以写出这个:
var a = 1;
var b = 2;
a = a + b
b = a - b
a = a - b
我们可以看见,a = a + b
这句就让a同时把两个变量的信息都存到a上面去了;第二句b = a - b
,因为a是a、b两者原本值和,而b交换后结果是a原本的值,那么交换后的b,就是两者原本的值的和减去原本的b。最后一句,既然知道了b,那么a也可以知道了,减一下
举一反三
第一次先相减
var a = 1;
var b = 2;
a = a - b
b = a + b
a = b - a
console.log(a, b)
第一次先相除
// 注意:a、b不能有一个是0
var a = 1;
var b = 2;
a = a / b
b = a * b
a = b / a
console.log(a, b)
第一次先相乘
// 注意:a、b不能有一个是0
var a = 1;
var b = 2;
a = a * b
b = a / b
a = a / b
console.log(a, b)
基于位运算
一个很经典的答案:
var a = 1;
var b = 2;
a ^= b
b ^= a
a ^= b
console.log(a, b)
因为是位运算,我们直接来看看二进制数字的异或运算,看一下如何实现交换
a: 0011 b: 1001
a = a ^ b: 0001
b ^= a => a ^ b ^ b: 0001 ^ 1001 => 0011(等于旧的a)
a ^= b => 0011 ^ 0001 => 1001(等于旧的b)
这就实现交换了a、b。其实也是把ab同时存到一个a变量里面(异或形式),然后反过来分别获取
然而,用类似的方法使用&和|却不能实现。为什么呢?我们观察前面的例子,都是这样的规律:三行代码,第一行让a同时存储两个数(某种运算结果), 第二行使用逆运算从a中拿到旧的a(让新的b等于旧的a),第三行再使用逆运算从a中拿到旧的b(新的a等于旧的b)
如果使用&,那么第一行就是a & b的结果,第二行应该是逆运算|
获得旧的a,获得旧的a需要旧a & 旧b | 旧a
,但执行第一行后旧a已经不存在了,所以行不通
所以,这里娱乐一下,用~玩玩,其实也就装饰一下前面的四则运算
var a = 1;
var b = 2;
a = ~a + b;
b = ~(a - b);
a = b - ~a;
console.log(a, b)
要求放低一些(娱乐)
如果我们可以把“不用中间变量交换两个数”视为:没看见新定义一个变量的过程(不管执行过程是什么,我只关心我代码有没有写了一句定义一个新的辅助变量的语句),那么就有一些js语法方向的玩法了
基于数组解构
var a = 1;
var b = 2;
[a, b] = [b, a];
console.log(a, b)
// babel编译的结果是
var _a;
var a = 1;
var b = 2;
_a = [b, a], a = _a[0], b = _a[1];
// 或者先赋值给a
var a = 1;
var b = 2;
a = [a, b]
b = a[0]
a = a[1]
基于对象解构
var a = 1;
var b = 2;
var { a: b, b: a } = { a, b }
// 或者
var { a, b } = { a: b, b: a }
console.log(a, b)
// babel结果
var a = 1;
var b = 2;
var _a = { a: a, b: b }, b = _a.a, a = _a.b;
// 还是可以先赋值给a
var a = 1;
var b = 2;
a = { a, b }
b = a.a
a = a.b
console.log(a, b)
如果说,没看见新定义一个变量的过程都算的话,那就可以母猪上树
// 刚刚说的 & | 不行,现在也算了
var [a, b] = [a & b | b, a & b | a]
var [a, b] = [{ [a]: b, [b]: a }[a], { [a]: b, [b]: a }[b]]
var [a, b] = [a << b >> a, a << b >> b]
其他api一起上
还是一样的套路,先想办法让a同时存储两个数,后面再反过来找值。我们来试一下其他api
var a = 1;
var b = 2;
a = `${b}-${a}`;
b = +a.split('-')[1];
a = parseFloat(a);
console.log(a, b)
基于match
var a = 1;
var b = 2;
a = `${b}-${a}`.match(/(\d+).*?(\d+)/);
b = +a[2]
a = +a[1]
console.log(a, b)
字符串个数
var a = 1;
var b = 2;
a = `${'b'.repeat(b)}${'a'.repeat(a)}`;
b = /a+/.exec(a)[0].length
a = /b+/.exec(a)[0].length
console.log(a, b)
套路玩不完,看大家的想象力了......
基于高级数学运算
指数 => 对数
我们知道指数和对数的关系是这样的:如果c = a ** b, 那么b = loga(c)
又有对数换底公式:loga(b) = lnb / lna = log2(b) / log2(a) = logn(b) / logn(a)
所以我们可以这样子交换两个数:
// a、b大于0
var a = 1;
var b = 2;
a = b ** a;
b = Math.log2(a) / Math.log2(b)
a = a ** (1 / b) // 最后一步就是开方: a的b次开根号
console.log(a, b)
三角函数
基于四则运算,包多一层三角函数
// 局限性很大,因为弧度超过两个PI就是一个周期导致不准确
// 而且sin的单调性区间拐点是k * PI / 2,可能结果存在预期之外
// 如果a+b>Math.PI / 2,需要分很多情况讨论,这里简单起见取小一点
// 最终结果并不是彻底准确,但我们可以在误差为EPSILON范围内可视为相等
var a = Math.PI / 4;
var b = Math.PI / 6;
a = Math.sin(a + b)
b = Math.asin(a) - b;
a = Math.asin(a) - b;
console.log(Math.abs(Math.PI / 6 - a) < Number.EPSILON)
console.log(Math.abs(Math.PI / 4 - b) < Number.EPSILON)
同样的,cos、tan、cot,道理都一样
双曲函数
类似三角函数的套路,再来一波。但要求就没那么苛刻了,随意
var a = 2;
var b = 3;
// 双曲正弦
a = Math.sinh(a + b)
b = Math.asinh(a) - b;
a = Math.asinh(a) - b;
console.log(a, b)
// 双曲余弦
a = Math.cosh(a + b)
b = Math.acosh(a) - b;
a = Math.acosh(a) - b;
console.log(a, b)
ln和e
再来一个轻松一些的玩法
var a = 1;
var b = 2;
a = Math.E ** (a + b);
// js的ln就是Math.log
b = Math.log(a) - b;
a = Math.log(a) - b;
console.log(a, b);
var a = 1;
var b = 2;
a = Math.E ** (a - b);
b = Math.log(a) + b;
a = b - Math.log(a);
console.log(a, b);
最后
研究骚操作,其实没什么意义......但是,我又带着大家复习/学习了一遍基本语法、api和数学基础了喔
路过的如果有其他的骚操作也可以分享一波~
关注公众号《不一样的前端》,以不一样的视角学习前端,快速成长,一起把玩最新的技术、探索各种黑科技