麻了!同事在会议室扇了自己好几个大嘴巴子 。。
就拿昨天晚上来说吧,领导发了个表格让大家填,结果大家都打不开,根本就填不了。那表格明明有问题,大家都在群里反映,但领导一个劲催,“为什么还没填?怎么都还没做?”
我作为程序员,理解这份情绪。看似小问题,结果却引爆了大波动。而那一巴掌,或许真是释放了所有的压力吧。
算法题:扁平化多级双向链表
聊个有意思的算法题:扁平化多级双向链表。
首先,咱们得了解一下这个题目说的“多级双向链表”到底是个什么鬼。
其实就是一个链表的链表。看这里,我举个简单例子:链表里的每个节点不仅有指向下一个节点的指针,还可能有指向一个子链表的指针。这种结构可以形象地想象成一张文件目录,文件夹下可以有子文件夹,子文件夹下还可以有子文件夹,以此类推。这个“多级”的意思,就是链表中某些节点本身还有嵌套的链表。
理解了这个结构后,问题来了——如何把这个多级链表“扁平化”呢?也就是说,咱们要把所有节点按照从上到下、从左到右的顺序展平成一个普通的链表,去掉子链表的嵌套,保证原来链表的顺序不变。
问题看起来挺复杂,但其实思路很清晰,下面来个简单的Java代码例子,看看如何处理这个问题。
假设我们有一个链表结构,定义成这样的一个节点类:
class Node {
public int val;
public Node next;
public Node prev;
public Node child; public Node(int val) {
this.val = val;
this.next = null;
this.prev = null;
this.child = null;
}
}
这其中,val 表示节点的值,next 和 prev 分别是指向前后节点的指针,child 则是指向子链表的指针。
接下来,我们的目标就是将这个链表“扁平化”,转换成一个没有子链表的单一链表。解决思路很简单,咱们就可以使用递归。每次遍历到一个节点,如果它有子链表,就先扁平化这个子链表,再接到当前链表的后面。搞定这一点,问题就迎刃而解了。
下面是Java实现:
class Solution {
public Node flatten(Node head) {
if (head == null) {
return null;
} // 使用栈来管理当前链表节点的遍历
Node curr = head;
while (curr != null) {
// 如果当前节点有子链表
if (curr.child != null) {
Node temp = curr.next;
// 将当前节点的子链表连接到当前节点后面
curr.next = curr.child;
curr.child.prev = curr;
curr.child = null; // 断开子链表的指针
// 找到子链表的最后一个节点,准备连接回原链表
while (curr.next != null) {
curr = curr.next;
}
// 连接回原链表的下一个节点
curr.next = temp;
if (temp != null) {
temp.prev = curr;
}
}
curr = curr.next;
}
return head;
}
}
上面的代码里,核心的做法就是:当我们遇到一个节点有子链表的时候,首先把子链表接到当前节点后面,并断开子链表的指针。接着,通过循环遍历子链表的最后一个节点,然后再把原本的 next 节点接到这个位置。
别看代码简洁,背后其实有个小细节就是如何保证子链表的末尾和原链表后续的节点顺利连接起来。所以这块的重点是通过while循环找到当前节点的末尾,再接上原链表的后继节点。
这道题的时间复杂度是 O(n),因为每个节点都只会被遍历一次;空间复杂度 O(1),没有额外的空间开销(除了常数空间)。
如果再想“加点料”,可以考虑怎么优化代码的结构,比如使用递归来简化过程。递归的方式可以让代码更优雅一些,但注意在实际的系统中,如果链表特别长,递归的深度可能会导致栈溢出。所以根据实际情况来决定是否使用递归。
-END-
以上,就是今天的分享了,看完文章记得右下角给何老师点赞,也欢迎在评论区写下你的留言。