擦,这是真牛马呀,某老板:工资5到6千,不是5千到6千
刚刷到这个,直接给我看笑了,这老板玩文字游戏是真有一套。
网友说工资5到6千,听着像区间,结果老板出来一句:不是5千到6千,是‘5到6千’,但实际核实工资3000,顺带补一句入职就说过了。
这事最骚的是,一个标点差不多能改成两个世界,一个以为是区间,一个是精确到‘最低保障都没有’的版本,打工人还得自己领悟语义学。评论区已经开始对‘5到6千’做语文阅读理解了。最离谱的是老板还觉得自己解释得很清楚。打工人:嗯,好,学会了,以后要学会读‘老板语法’。
这波属于合同前的语言艺术课。看完沉默。
链表翻转这题,最容易写出一个“看着对、跑起来断链”的版本。
尤其是这句:
cur.next = pre;
你只要在它前面没把下一个节点存住,后面的链表就没了。不是慢,也不是边界问题,是直接把路掐断。
比如原链表是:
1 -> 2 -> 3 -> 4 -> null
翻完之后要变成:
4 -> 3 -> 2 -> 1 -> null
这题我一般不先想什么递归,不划复杂图。就盯住三个指针:
pre:已经翻好的那一段
cur:当前正在处理的节点
next:先把后面的路记下来
关键代码就这么几行:
classNode{
int val;
Node next;
Node(int val) {
this.val = val;
}
}
publicclassLinkReverse{
public Node reverse(Node head){
Node pre = null;
Node cur = head;
while (cur != null) {
Node next = cur.next; // 先把后面的节点留住,不然后面就断了
cur.next = pre; // 当前节点回头指
pre = cur; // 已翻转链表往前挪
cur = next; // 继续处理原链表后面的节点
}
return pre;
}
}
这段代码里面最不能省的是 next。
有些人第一次写会这样:
cur.next = pre;
cur = cur.next;
这代码我第一眼就不太信。因为 cur.next 已经被你改成 pre 了,这时候再往下走,走的不是原链表后面的节点,而是已经翻过的那段。轻则死循环,重则结果乱掉。
可以拿三个节点手算一下。
刚开始:
pre = null
cur = 1
第一次循环:
next = 2
1.next = null
pre = 1
cur = 2
此时链表被拆成了两段:
1 -> null
2 -> 3 -> 4 -> null
第二次循环:
next = 3
2.next = 1
pre = 2
cur = 3
变成:
2 -> 1 -> null
3 -> 4 -> null
一直这么挪,最后 cur 走到 null,说明原链表处理完了。这个时候 pre 正好站在新链表的头节点,也就是 4。
边界也顺手过一下。
空链表:
reverse(null);
不会进 while,直接返回 null。
只有一个节点:
7 -> null
循环一次,7.next 指向 null,返回 7,也没问题。
这题还有递归写法,但我平时面试或者写业务工具代码,优先写迭代。原因很简单,指针变化都摆在眼前,不靠调用栈猜。
递归版也可以写:
public Node reverseByCall(Node head){
if (head == null || head.next == null) {
return head;
}
Node newHead = reverseByCall(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
递归这段最绕的是:
head.next.next = head;
它的意思不是“当前节点指向下一个”,而是让下一个节点反过来指向当前节点。
比如递归已经把:
2 -> 3 -> 4
翻成了:
4 -> 3 -> 2
这时候当前 head 是 1,只需要让 2 指回 1:
head.next.next = head;
再把 1 的 next 断掉:
head.next = null;
不然 1 和 2 会互相指,链表直接绕圈。
所以翻转链表这题,别背代码。就记一件事:改指针之前,先保存现场。链表题大部分 bug,不是算法多难,是你把后路改没了。