公司社招面试:请说出4种不使用第三方变量交换两个变量值的方法

哈喽,大家好,我是阿Q。前几天有个小伙伴去面试,被面试官的一个问题劝退了:请说出几种不使用第三方变量交换两个变量值的方法。

问题有点绕,好不容易缕清了面试官的问题,却发现答不上来。一时间尴尬无比,只能硬着头皮说不会。

遇到交换变量值的问题,通常我们的做法是:定义一个新的变量,借助它完成交换。

代码如下:

t = a;
a = b; 
b = t;

但问题的重点是“不使用第三方变量”,那就变得“可爱”起来了。思考过后,抛出以下四种方法来解决该问题:

  • 变量本身交换数值;
  • 算术运算;
  • 指针地址操作;
  • 位运算;

变量本身交换数值

b = (a + b) - (a = b);

首先执行 a + b 操作,然后将 b 赋值给 a,则 b = a + b - b = a,这就完成了 ab 的互换操作。

算术运算

如图所示:
OA = a;
OB = b;
AB = b - a;

首先我们把 AB 之间的距离 b - a 赋值给 a,此时 AB = a, OB = b 。

由于要达到 ab 交换的目的,所以 OA 要等于 b,而此时 OA 的距离为 b - a ,所以得将 b - a 赋值给 b ,此时 OA = b, AB = a 。

很容易从图中看出,OB 的距离为 b + a,所以我们只需要将 b + a 赋值给 a 就可以完成两者的交换了。

综上所述,我们的步骤为

int a = 10;
int b = 15;
a = b - a; //b=15;a=5;
b = b - a; //b=10;a=5;
a = b + a; //b=10;a=15;

该算法只能用于整型类型。

指针地址操作

我们可以把 a 和 b 想象为内存中的地址值,假设 a 为 0x01ff5e70 ,b 为 0x01ff5e90 ,而 b - a 表示两个变量在内存中的储存位置隔了多少个字节。所以我们理论上也可以按算术运算的逻辑来交换两个变量的值。

代码如下(此处是 c 语言):

//其中 a 和 b 都是指针变量,里边存储着10和20的地址
int *a = new int(10); //a=0x01ff5e70 ,此处代表a中存储的地址
int *b = new int(20); //b=0x01ff5e90 ,此处代表b中存储的地址

//指针变量相减得到20和10的地址间隔了多少个字节,然后转为指针变量
a = (int*)(b-a);     //b=0x01ff5e90;a=0x8
b = (int*)(b-a);     //b=0x01ff5e70;a=0x8
a=(int*)(b+long(a));//b=0x01ff5e70;a=0x01ff5e90

b - a = 0x01ff5e90 - 0x01ff5e70 = 0x20,0x20 转换为十进制为 32 位,因为一个 int 占4位,所以这里是 0x8 。

以上只是理论状态下的执行过程,如果直接执行是不能实现交换的。因为上边的代码忽略了一个问题:代码编译之后,变量都是存在内存中的,而内存区都会存在基地址。

基地址可以理解为某块内存的起点。上边的数据都是在基地址的基础上做了偏移。

变量的地址 = 变量的基地址 + 变量的偏移地址

当我们进行 b - a 操作的时候,得到结果为 8 ,然后转化为指针变量的时候就会给 8 自动添加基地址,此时的结果就不是 0x8 了,所以会导致结果错误。

另外,地址运算不能出现负数,即当 a 的地址大于 b 的地址时,b - a < 0 ,系统自动采用补码的形式表示负的位移,也会产生错误。

为了解决这个问题,我们只需要保证 b - a 得到的结果不受基地址的影响即可,所以给出以下解决方案。

int *a = new int(10);
int *b = new int(20); 
cout << a << "`````";
cout << b << "`````";
if(a < b){
    a = (int*)(b-a); 
    cout << a << "`````";

    b=(int*)(b-(long(a)&0x0000ffff));
    cout << b << "`````";

    a=(int*)(b+long(a));
    cout << a << "`````";
} else {
    b = (int*)(a-b); 
    cout << b << "`````";

    a=(int*)(a-(long(b)&0x0000ffff));
    cout << a << "`````";

    b=(int*)(a+long(b));
    cout << b << "`````";
}

执行结果:

0x8dbe70`````0x8dbe90`````0x8`````0x8dbe70`````0x8dbe90`````

看到这,不知道大家是否真的看懂了。反正我第一次看到这儿时,感觉非常清晰(其实完全没有理解),第二次看的时候懵逼了,完全不懂,所以还得大家仔细思考一下才行。

b=(int*)(b-(long(a)&0x0000ffff)); 指令的精妙之处就在于采用了位运算中的与运算,将 a 和 0x0000ffff 进行与运算后,b - a 的基地址计算结果被屏蔽,只保留了偏移地址的计算结果,也就是我们需要的字节数。

在交换很大的数据类型时,该方法执行速度比算术算法快。因为它交换的是地址,而变量值在内存中是没有移动过的。

位运算

既然上边用到了位运算,那我们再说一种直接通过“异或“完成交换的方法。

简单介绍一下异或的规则:

  • 如果a、b两个值不相同,则异或结果为1;
  • 如果a、b两个值相同,异或结果为0。

代码如下

int a=10, b=12;//二进制:a=1010;b=1100;
a = a^b;//a=0110;b=1100
b = a^b;//a=0110;b=1010
a = a^b;//a=1100;b=1010
System.out.println("a="+ a +",b="+ b);

执行结果

a=12,b=10

异或运算能够使数据中的某些位翻转,其他位不变。这就意味着任意一个数与任意一个给定的值连续异或两次,值不变。

简单总结

以上四种方法均实现了不借助第三方变量来完成两个变量值的交换:

  • 算术运算和位运算计算量相当,只能进行整形数据的交换;
  • 地址运算中计算较复杂,可以很轻松的实现大类型(比如自定义的类或结构)的交换;
  • 理论上重载 “^” 运算符,也可以实现任意结构的交换;

此类面试题虽然在实际开发中影响不大,但是可以考验一下面试者的知识储备、逻辑思维能力以及对JVM执行过程的理解,不会太偏向八股文,却又得到了面试者对“八股文”的掌握程度,可谓一举两得。

以上就是今天的全部内容了,如果你有不同的意见或者更好的idea,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!

#面试##面经##Java##社招##技术栈#
全部评论
有点意思哦😄
点赞 回复 分享
发布于 2022-04-23 12:23
学到了,给你点个赞😁
点赞 回复 分享
发布于 2022-04-22 18:09
感谢楼主的分享,描述的太详细了,给你个赞
点赞 回复 分享
发布于 2022-04-19 14:14

相关推荐

2025-12-31 18:42
复旦大学 Java
点赞 评论 收藏
分享
2025-11-21 03:09
已编辑
南昌大学 golang
bg普211本,走的golang后端方向。找实习经历:最近一个月投了一些日常,面了4场,都是一面挂。简历包装成分比较多,当时这个简历准备了两个星期,问AI解决什么问题用什么技术,跟其他技术对比优缺点在哪,等等。但是面试的时候一些基础的八股都答的模模糊糊,然后项目延伸的场景题一点不会。有点害怕面试,面前焦虑…本文可能带点碎碎念…省流就是因为每周面心态不行,不知道先学什么以及三天打鱼两天晒网…现在的主要问题,一个是只能依靠即时满足无法撑过枯燥的学习,另一个是难以调整心态,面试焦虑。个人背景:主包其实本来是大一开始学后端的,但是当时不知道合适的学习方法(学习路线和借助AI),也社恐不太敢问学长,走了很多弯路,也没有花很多时间在后端上面(按兴趣学的只有大二上学期写了opencamp的rustlings和learning-cxx,还有玩steam的图灵完备,剩余时间比较摆烂)。结果就是现在这鬼样子,只会写crud,差不多就是会gin&nbsp;gorm基础,会写注册登录和简单业务接口,写过几种项目结构和设计模式。缺乏自己延展的能力。计算机基础:也相当差,之前大二学的计网全忘光了,操作系统60飘过。虽然大一的时候打算法竞赛(也没什么成绩就是,省二等奖收集者),但到现在一年半没碰了,就只有dfs,并查集啥的一些很基础的题目随便写,hot100链表因为竞赛没练过相当不熟练。大二下的时候,数据库课看八股,又困又累,什么都没看进去,后面自然又是全忘光了。现在我虽然有了个概览,知道后端除了crud有缓存、微服务、分布式、消息队列等等东西,知道后端架构设计是要做权衡,性能、一致性、容灾,需要通过实验测出具体的数据来做决策,但是具体的方案不会,看基础知识是真看不进去。现在的主要问题,一个是只能依靠即时满足无法撑过枯燥的学习,另一个是难以调整心态。我高中以前一直是优等生,能够享受大部分题目都会的快感,能明确地有信心自己能做出来,解题过程需要进行推理,并且做完立刻就能得到正确反馈,其中的失败调整过程长度也在可接受范围内。(喜欢写rustlings一类的语言lab和玩《图灵完备》大概也是因为这个吧…)而现在的情景相当于我成了高三但是基础知识基本不会的状态,比我当年(会基础知识只是差做题)差多了。在这种情况下去面试也是相当痛苦,因为面试是不知道范围的。每次准备都不知道先看什么,学也学不进去。明明知道面试只是为了了解真实会问什么,但是还是很焦虑,拧巴心态。学长说去投简历面试实践是为了了解自己在哪里,别人在哪里,市场在哪里,但是我似乎还没有找到收敛的下限,只是一直失败…但是我也不能确定不面试就能学进去啊,因为我大二暑假是真的一点代码都不想碰,相当烦躁,八股也不想看。现在甚至连稍微花点时间的算法题(不能即时反馈的)都不想写了。还在纠结要不要整块时间搓项目压测试试,感觉会非常花时间。可能我项目管理也是一坨。
圆规学java:27届不着急,边投边学,克服恐惧感,你现在不敢面试,你为什么认为你暑期就勇敢了,你现在的进度其实还很早,我当时大三下才开始实习,我也很焦虑着急。永远没有准备好的时候,当下努力就是最好的加油!
点赞 评论 收藏
分享
评论
7
12
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务