经典问题再解读:不用中间变量交换两个变量的值

大概八年前,写过这样一篇文章:不使用中间变量来交换变量的值,后来面试的时候常常遇到这题,最近翻出来看,发现当时对这个问题的理解不够深刻,所以今天又整理了一下。

1. 一些有限制的方法

字符串版本

1
2
3
4
5
6
<?php
$a = "a";
$b = "b";
$a .= $b; // a=ab, b=b
$b = str_replace($b, "", $a); // a=ab, b=a
$a = str_replace($b, "", $a); // a=b, b=a
1
2
3
4
5
6
<?php
$a = "a";
$b = "b";
$a .= $b; // a=ab, b=b
$b = substr($a, 0, (strlen($a) - strlen($b))); // a=ab, b=a
$a = substr($a, strlen($b)); // a=b, b=a

上面这两个方法使用了字符串替换和截取的方法,有一个限制就是只适用于字符串。

加减法

1
2
3
4
5
a = 1;
b = 2;
a = a + b; // a=3, b=2
b = a - b; // a=3, b=1
a = a - b; // a=2, b=1
1
2
3
4
5
a = 1;
b = 2;
a = b - a; // a=1,b=2
b = b - a; // a=1,b=1
a = b + a; // a=2,b=2

乘除法

1
2
3
a = a * b;
b = a / b;
a = a / b;

用除法来解决这个问题,多了一个限制,b不能等于0

一句话版本

1
a = b + 0 * (b = a);
1
a = (b - a) + (b = a);
1
a = (a + b) - (b = a);
1
a = b + (b = a) * 0;

这些方法利用了表达式的返回值

所有的加减乘除的方法里有两方面限制:

  • 只适用于数字
  • 如果变量是浮点数,会有精度上的损失

看到网上有一些人提出适用+和*的时候会导致结果向上溢出,但其实这并不影响结果,因为最后逆操作会产生一次向下溢出。

eval版

1
eval("a="+b+";b="+a);

eval版本可能有两个问题

  • 安全性
  • 是如果想支持更多的数据类型比较麻烦

异或版本

1
2
3
4
5
6
<?php
$a=10; //$a=1010
$b=12; //$b=1100
$a=$a^$b; //$a=0110,$b=1100
$b=$a^$b; //$a=0110,$b=1010
$a=$a^$b; //$a=1100=12,$b=1010

下面是简化版本

1
2
3
4
<?php
$a ^= $b;
$b ^= $a;
$a ^= $b;

异或适用于整数和字符串

2. 适用于所有的数据类型,并且没有限制的方法

对象版

1
2
3
a = {a : b, b : a};
b = a.b;
a = a.a;

数组版

1
2
3
a = [a,b];
b = a[0];
a = a[1];
1
a = [b,b=a][0];

匿名函数版

1
2
3
4
5
6
7
8
a=(function(){
try {
return b;
}
finally {
b = a;
}
})();

PHP 版本

1
list($var1, $var2) = [$var2, $var1];

Python&Ruby版本

1
a,b = b,a; // python和ruby程序员都笑了:D

ES6 版本

1
[a,b] = [b,a];

3. 总结一下

解决这个问题的基本思想有以下几种:

  • 将两个变量同时放入其中一个变量,再分别取出,例如字符串版本,数组版本,对象版本

  • 将两个变量通过某种计算的结果放入其中一个变量,再用计算结果和另一个已知变量逆向取回结果,例如异或版本和一部分加减乘除的版本

  • 利用语言特性,例如eval版本,匿名函数版本,php版本和python&ruby版本