被裁后的第3个月,面试官问我空窗期在干嘛。 我说:"跑外卖。"他愣住了。我接着说:"送了1278单,超时率0.3%,差评0条。
刚看到个贴子:哥们儿被裁第三个月去面试,面试官问空窗期干嘛,他很实在地说去送外卖,还报了单量、超时率、差评数,结果全场安静。
我觉得这事吧,尴尬的不是“跑外卖”,而是很多人骨子里还是嫌这活儿“不体面”。网友们的回复我看了看,有人觉得他不懂包装自己,有人说这才是真正的靠谱打工人。我更倾向后者。能把外卖送出KPI,把服务做到0差评,这种执行力、抗压能力,放哪家公司都是资产。
但话说回来,职场毕竟有偏见,面试场上还是要讲点“翻译话术”,比如说体验即时配送行业、做数据复盘之类,让对方听得舒服点。外卖不丢人,脑子清醒、有手有脚,比躺在家里刷手机强多了。
面试题:两数相加
你是不是也有过这种体验,晚上刷题刷到困得要死,结果一道“看起来很简单”的题,硬是缠了你一晚上,还不如一堆动态规划好受 😂 我第一次遇到“两数相加”这个题,就是这种感觉。跟我以前写数据库那篇文章的心情差不多。
题目大概意思是这样的(我不用官方那种教科书描述啊,直接说人话):
有两条链表
l1和l2每个节点存的是 一位数字(0~9)
数字是倒着存的,也就是:
2 -> 4 -> 3其实代表的是 3425 -> 6 -> 4代表的是 465现在要你把这两个数相加,结果还是用同样格式的链表返回
举个例子:
l1 = 2 -> 4 -> 3(342)l2 = 5 -> 6 -> 4(465)
342 + 465 = 807,结果链表要长这样:
7 -> 0 -> 8(因为还是倒序存)
看起来就一个“加法”,但坑在哪? 链表长短可能不一样,还有进位,比如 9 + 9 = 18,这个进位怎么往后传,这是关键。
你想象一下我们小学算竖式:
342
+ 465
-----
807
是不是从个位开始,一位一位往上加,有进位就往前传?
链表版本也是一模一样的套路:
准备一个
carry表示进位,一开始是 0同时从
l1和l2的头结点开始往后走每一位的计算:
当前位的两个数字: x = (l1 == null ? 0 : l1.val)y = (l2 == null ? 0 : l2.val)总和: sum = x + y + carry新节点的值: sum % 10新的进位: carry = sum / 10
把这个新节点接到结果链表后面
l1 / l2 往后挪一位
最后循环结束后,如果 carry > 0,还要再补一个节点
唯一有点绕的是“结果链表怎么优雅地拼起来”,一般都是用一个“虚拟头结点”(dummy head):
先 new 一个头结点 dummy = new ListNode(0)用一个指针 cur指向当前结果链表的尾巴每算出一个新节点,就 cur.next = 新节点,然后cur = cur.next最后返回 dummy.next就是结果链表的真正头结点
这样就不用去单独处理“第一个节点”的特殊情况,写起来省心很多。
先假设题目给的链表结构是这种:
publicclassListNode{
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
主逻辑代码可以这么写:
publicclassSolution{
public ListNode addTwoNumbers(ListNode l1, ListNode l2){
// 虚拟头结点,方便操作
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
int carry = 0; // 进位
// 只要有一个链表没走完,或者还有进位,就继续
while (l1 != null || l2 != null || carry != 0) {
int x = (l1 != null) ? l1.val : 0; // l1 当前位
int y = (l2 != null) ? l2.val : 0; // l2 当前位
int sum = x + y + carry; // 当前总和
carry = sum / 10; // 新的进位
// 当前这一位的结果
ListNode node = new ListNode(sum % 10);
cur.next = node;
cur = cur.next;
// 往后挪
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
// 结果从 dummy.next 开始
return dummy.next;
}
}
你可以在脑子里跑一个例子:
第一轮:2 + 5 + 0 = 7 节点值 7,carry = 0第二轮:4 + 6 + 0 = 10 节点值 0,carry = 1第三轮:3 + 4 + 1 = 8 节点值 8,carry = 0两个链表都走完、carry 也没了,循环结束 链表就是 7 -> 0 -> 8,完全对上。
这个题本身不难,但面试的时候很容易因为细节扣分:
链表长度不一样
比如:
我们的
while (l1 != null || l2 != null || carry != 0)加上x = (l1 != null ? l1.val : 0)这种写法,其实已经自然处理好了不同长度的问题。
l1 = 9 -> 9 -> 9(999)l2 = 1(1)
最后的进位
经典用例:
99 + 1 = 100,结果应该是 0 -> 0 -> 1如果你的循环条件写成 while (l1 != null || l2 != null)那最后那个进位 1 就没地方放了,会直接丢掉。
所以循环条件里一定要把 carry != 0 加上。
l1 = 9 -> 9(99)l2 = 1(1)
不要想着把链表转成整数再加
有些同学第一反应是: “要不我把链表转成 int,然后相加,再转回链表?”
问题在于:
所以正规写法就是上面这种“逐位相加 + carry”。
链表可能很长,超过 int / long 范围就炸了 而且题目本来就是想让你练习“模拟加法+链表操作”
面试官有时候会顺嘴问一句,你就不要愣着了:
时间复杂度: O(max(m, n))m、n 是两条链表的长度,因为每条链表各自只遍历一遍空间复杂度: O(1)(不算结果链表的话) 我们只用了常数级别的额外变量:carry、几个指针
差不多就这样,这道“两数相加”你只要把“竖式加法”那个画面记住,代码其实就是在翻译你脑子里的竖式过程。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html