链表——前端算法与数据结构

124 阅读3分钟

链表

链表是一种物理结构,不受编程语言限制,java python等都有链表的数据结构概念。链表各元素间是连续的,但是在内存存储上是离散的,这有区别于数组。常见考察:

  • 链表的处理:合并、删除
  • 链表的反转
  • 链表成环问题

实现一个数组转链表:

interface ILineListNode {
  val: number
  next?: ILineListNode
}

function createLinkList(nums: number[]): ILineListNode {
  const len = nums.length

  let linkNode: ILineListNode = {
    val: nums[len - 1]
  }
  for (let i = len - 2; i >= 0; i--) {
    linkNode = {
      val: nums[i],
      next: linkNode
    }
  }
  return linkNode
}

console.log('LinkList', createLinkList([1,2,3,4]))

合并两个有序链表

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
const mergeTwoLists = function(l1, l2) {
  // 定义头结点,确保链表可以被访问到
  let head = new ListNode()
  // cur 这里就是咱们那根“针”
  let cur = head
  // “针”开始在 l1 和 l2 间穿梭了
  while(l1 && l2) {
      // 如果 l1 的结点值较小
      if(l1.val<=l2.val) {
          // 先串起 l1 的结点
          cur.next = l1
          // l1 指针向前一步
          l1 = l1.next
      } else {
          // l2 较小时,串起 l2 结点
          cur.next = l2
          // l2 向前一步
          l2 = l2.next
      }
      
      // “针”在串起一个结点后,也会往前一步
      cur = cur.next 

  }
  
  // 处理链表不等长的情况
  cur.next = l1!==null?l1:l2
  // 返回起始结点
  return head.next
};

删除排序链表中的重复元素

有序的链表,因此对前后节点进行比较就行。

/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var deleteNode = function(head, val) {
  let dummy = new ListNode()
  dummy.next = head
  let cur = dummy
  while(cur !== null && cur.next !== null) {
    if(cur.next.val === val) {
      cur.next = cur.next.next
    } else {
      cur = cur.next
    }
  }
  return dummy.next
};

删除排序链表中的重复元素 II

这道题与以上不一样的地方在于,重复的值都删除掉。

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var deleteDuplicates = function(head) {
  // head有0个或1个结点时,直接返回
  if(!head || !head.next) {
    return head
  }
  // dummy结点
  let dummy = new ListNode()
  dummy.next = head
  let cur = dummy
  // 遍历比较
  while(cur.next && cur.next.next) {
    if(cur.next.val === cur.next.next.val) {
      // 有重复的值时,记下val,遍历是否有多个重复的值
      let val = cur.next.val
      while(cur.next && cur.next.val === val) {
        // 有重复cur继续往下走
        cur.next = cur.next.next
      }
    } else {
      // 无重复,cur正常遍历
      cur = cur.next
    }
  }
  // 返回链表的起始结点
  return dummy.next
};

删除链表的倒数第 N 个结点

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
  // 定义dummy结点
  let dummy = new ListNode()
  dummy.next = head
  // 快指针
  let fast = dummy
  // 慢指针
  let slow = dummy
  // fast先往前冲n步
  while(n) {
    fast = fast.next
    n--
  }
  // 快慢指针齐头并进, 走完链表
  while(fast.next) {
    slow = slow.next
    fast = fast.next
  }
  slow.next = slow.next.next
  // 返回头节点
  return dummy.next
};

反转链表

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
const reverseList = function(head) {
    // 初始化前驱结点为 null
    let pre = null;
    // 初始化目标结点为头结点
    let cur = head;
    // 只要目标结点不为 null,遍历就得继续
    while (cur !== null) {
        // 记录一下 next 结点
        let next = cur.next;
        // 反转指针
        cur.next = pre;
        // pre 往前走一步
        pre = cur;
        // cur往前走一步
        cur = next;
    }
    // 反转结束后,pre 就会变成新链表的头结点
    return pre
};

环形链表

/**
 * @param {ListNode} head
 * @return {boolean}
 */
// 入参是头结点 
const hasCycle = function(head) {
    // 只要结点存在,那么就继续遍历
    while(head){
        // 如果 flag 已经立过了,那么说明环存在
        if(head.flag){
            return true;
        }else{
            // 如果 flag 没立过,就立一个 flag 再往下走
            head.flag = true;
            head = head.next;
        }
    }
    return false;
};

环形链表 II

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
const detectCycle = function(head) {
    while(head){
        if(head.flag){
            return head;
        }else{
            head.flag = true;
            head = head.next;
        }
    }
    return null;
};