【算法】链表

多态苔藓
• 阅读 801

哨兵节点

哨兵节点是为了简化处理链表边界条件而引入的附加链表节点。哨兵节点通常位于链表的头部,它的值没有任何意义。在一个有哨兵节点的链表中,从第2个节点开始才真正保存有意义的信息。

  • 用哨兵节点简化链表插入操作
  • 用哨兵节点简化链表删除操作

使用哨兵节点可以简化创建或删除链表头节点操作的代码。

双指针

  1. 删除倒数第k个节点
题目:如果给定一个链表,请问如何删除链表中的倒数第k个节点?假设链表中节点的总数为n,那么1≤k≤n。要求只能遍历链表一次。

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    const dummy = new ListNode(0, head);
    let front = head, back = dummy;
    for(let i = 0;i<n;i++) {
        front = front.next
    }
    while(front !== null) {
        front = front.next;
        back = back.next
    }
    back.next = back.next.next
    return dummy.next

};
  1. 链表中环的入口节点
题目:如果一个链表中包含环,那么应该如何找出环的入口节点?从链表的头节点开始顺着next指针方向进入环的第1个节点为环的入口节点。例如,在如图4.3所示的链表中,环的入口节点是节点3。

反转链表

  1. 反转链表
题目:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点
var reverseList = function(head) {
  const prev = null;
  const cur = head;
  while(cur != null) {
    const next = cur.next;
    cur.next = prev;
    prev = cur;
    cur = next; 
  }
  return prev
}
  1. 链表中的数组相加
题目:给定两个表示非负整数的单向链表,请问如何实现这两个整数的相加并且把它们的和仍然用单向链表表示?链表中的每个节点表示整数十进制的一位,并且头节点对应整数的最高位数而尾节点对应整数的个位数。

分析:要考虑整数溢出的情况。

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */

var reverseList = function(head) {
    let prev = null
    let cur = head
    while(cur !== null) {
        const next = cur.next
        cur.next = prev
        prev = cur
        cur = next
    }
    return prev
}


/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
    let head1 = reverseList(l1)
    let head2 = reverseList(l2)
    let dummy = head = new ListNode(null)
    let sub = 0
    while(head1 !== null || head2 !== null) {
        let a = head1 !== null ?  head1.val :0
        let b = head2 !== null ? head2.val :0
        let val = a + b + sub
        let node
        if(val >= 10) {
            val = val - 10
            node = new ListNode(val)
            sub =  1
        } else {
            node = new ListNode(val)
            sub = 0
        }
        head.next = node
        head = head.next
        head1 && (head1 = head1.next)
        head2 && (head2 = head2.next)
    }
    if(sub==1) {
        head.next = new ListNode(1)
        head = head.next
    }
    return reverseList(dummy.next)
};
  1. 重排链表
问题:给定一个链表,链表中节点的顺序是L0→L1→L2→…→Ln-1→Ln,请问如何重排链表使节点的顺序变成L0→Ln→L1→Ln-1→L2→Ln-2→…?
  1. 切分链表
  2. 反转后半链表
  3. 链接链表
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
var reverseList = function(head) {
    let prev = null
    let cur = head
    while(cur!==null) {
        const next = cur.next
        cur.next = prev
        prev = cur
        cur = next
    }
    return prev
}

/**
 * @param {ListNode} head
 * @return {void} Do not return anything, modify head in-place instead.
 */
var reorderList = function(head) {
  let dummy = new ListNode(0, head);
  let fast = dummy, slow = dummy;

  while(fast != null && fast.next != null) {
    slow = slow.next
    fast = fast.next.next
  }

  const temp = slow.next;
  slow.next = null;
  link(head, reverseList(temp), dummy);
}

var link = function(node1, node2, head) {
  let prev = head;
  while(node1 !== null && node2 !== null) {
    const temp = node1.next;
    prev.next = node1;
    node1.next = node2
    prev = node2

    node1 = temp
    node2 = node2.next
  }
  if(node1 !== null) {
    prev.next = node1
  }
}
  1. 回文链表
问题:如何判断一个链表是不是回文?要求解法的时间复杂度是O(n),并且不得使用超过O(1)的辅助空间。如果一个链表是回文,那么链表的节点序列从前往后看和从后往前看是相同的。例如,图4.13中的链表的节点序列从前往后看和从后往前看都是1、2、3、3、2、1,因此这是一个回文链表。
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
var reverseList = function(head) {
    let prev = null
    let cur = head
    while(cur !== null) {
        const next = cur.next
        cur.next = prev
        prev = cur
        cur = next
    }
    return prev
}

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var isPalindrome = function(head) {
  if(head == null || head.next == null) {
    return true;
  }
  let slow = head;
  let fast = head.next;
  while(fast.next !== null && fast.next.next !== null) {
    slow = slow.next
    fast = fast.next.next
  }
  let secondHalf = slow.next;
  if(fast.next != null) {
      secondHalf = slow.next.next
  }
  slow.next = null;
  return equals(secondHalf, reverseList(head))
}

var equals = function(head1, head2) {
  while(head1 != null && head2 !== null){
    if(head1.val !== head2.val) return false

    head1 = head1.next
    head2 = head2.next
  }

  return head1 == null && head2 == null;
}

双向链表和循环链表

  1. 展平多级双向链表
在一个多级双向链表中,节点除了有两个指针分别指向前后两个节点,还有一个指针指向它的子链表,并且子链表也是一个双向链表,它的节点也有指向子链表的指针。请将这样的多级双向链表展平成普通的双向链表,即所有节点都没有子链表。
  • 深度遍历

    var flatten = function(head) {
      const dfs = (node) => {
          let cur = node;
          // 记录链表的最后一个节点
          let last = null;
    
          while (cur) {
              let next = cur.next;
              //  如果有子节点,那么首先处理子节点
              if (cur.child) {
                  const childLast = dfs(cur.child);
    
                  next = cur.next;
                  //  将 node 与 child 相连
                  cur.next = cur.child;
                  cur.child.prev = cur;
    
                  //  如果 next 不为空,就将 last 与 next 相连
                  if (next != null) {
                      childLast.next = next;
                      next.prev = childLast;
                  }
    
                  // 将 child 置为空
                  cur.child = null;
                  last = childLast;
              } else {
                  last = cur;
              }
              cur = next;
    
          }
          return last;
      }
    
      dfs(head);
      return head;
    };
  1. 排序的循环链表
问题:在一个循环链表中节点的值递增排序,请设计一个算法在该循环链表中插入节点,并保证插入节点之后的循环链表仍然是排序的。
点赞
收藏
评论区
推荐文章
Redis缓存高可用集群
在redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点的状态,如果master节点异常,则会做主从切换,将某一台slave作为master,哨兵的配置略微复杂,并且性能和高可用性等各方面表现一般。
浪人 浪人
4年前
利用“哨兵”“实现双链表
下面的代码用一个”哨兵“实现双链表,感觉很简洁,中间也有点绕,暂时实现,供学习之用staticNodelist_handle{&list_handle,&list_handle,};booladdNode(Nodenode){if(nodeNULL){returnf
Wesley13 Wesley13
4年前
275,环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回null。为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从0开始)。如果 pos 是 1,则在该链表中没有环。说明:不允许修改给定的链表。示例1:输入:head3,2,0,4,pos
Stella981 Stella981
4年前
Redis系列
Redis的主从复制模式下,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址,对于很多应用场景这种故障处理的方式是无法接受的。可喜的是Redis从2.8开始正式提供了RedisSentinel(哨兵)架构来解决这个问题。总结:Redis主从复制的缺点:没有办法对master进行动态
Stella981 Stella981
4年前
Redis Sentinel 哨兵模式
RedisSentinel哨兵模式Sentinel介绍Redis的主从模式下,主节点一旦发生故障不能提供服务,需要人工干预,将从节点晋升为主节点,同时还需要修改客户端配置。对于很多应用场景这种方式无法接受。Sentinel(哨兵)架构解决了redis主从人工干预的问题。Redis
Wesley13 Wesley13
4年前
Java源码解析之LinkedList源码剖析(基于JDK1.8)
    学过数据结构的都知道双端队列(链表),没学过的也没有关系,下面我先介绍一下双端队列(链表),为什么要介绍它了,因为LinkedList本质上就是一个双端队列(链表)。一. 双端队列(链表)的介绍1. 如下图:双端队列(链表)中单个节点对应的结构!(https://oscimg.oschina.net/oscn
Stella981 Stella981
4年前
LeetCode 142 环形链表 II python
题目描述给定一个链表,返回链表开始入环的第一个节点。如果链表无环,则返回null。说明:不允许修改给定的链表。样例如果不是环,则输出None如果是环,则输出入口节点想法:通过ac141,知道慢节点循环的次数就是环的长度无环的情况不用考虑,直接返回No
Stella981 Stella981
4年前
Redis 哨兵节点之间相互自动发现机制(自动重写哨兵节点的配置文件)
Redis的哨兵机制中,如果是多哨兵模式,哨兵节点之间也是可以相互感知的,各种搜索之后出来的是千篇一律的一个基础配置文件,在配置当前哨兵节点的配置文件中,并没有配置其他哨兵节点的任何信息。如下是一个哨兵节点的配置信息,可以看到,哨兵与哨兵之间没有任何配置,死活想不明白,哨兵之间是如何自动识别的。sentinel端口port
菜园前端 菜园前端
2年前
什么是链表?
原文链接:什么是链表?链表是有序的数据结构,链表中的每个部分称为节点。可以首、尾、中间进行数据存取,链表的元素在内存中不必是连续的空间,每个节点通过next指针指向下一个节点。优点链表的添加和删除不会导致其余元素位移。缺点无法根据索引快速定位元素。数组和链
深度学习 深度学习
8个月前
头插法实现的树结构:链表式多叉树实现指南
一、简介和特点头插法实现的树是一种使用子节点的多叉。本文实现的树类通过链表头插法高效管理子节点关系,适合需要频繁插入子节点的场景。‌主要特点‌:1.动态子节点管理:使用链表存储子节点1.高效插入:头插法实现O(1)的子节点插入1.泛型支持:模板类设计支持多