链表相关算法复习二

192 阅读7分钟

文章简要概述

  • 本篇主要是复习链表相关的内容,部分算法题之前可能写过,这里进行回顾,进一步加深印象。刷题不是目的,目的是理解与掌握

  • 本文一共有5道题,主要介绍leetcode中两数相加 II重排链表面试题 02.08. 环路检测剑指 Offer 18. 删除链表的节点设计链表的解题思路。

与链表相关算法

两数相加 II

两数相加 II -- leetcode

题目大意:

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例

1626420025-fZfzMX-image.png

输入: l1 = [7,2,4,3], l2 = [5,6,4]

输出: [7,8,0,7]

解题思路:

  • 从题意中可以看出,把两个链表从后往前每个节点值相加,大于十位的向前进位。
  • 本题比较简单的做法是将两个链表反转,然后从头遍历,将两个链表的值求和即可。
  • 这里采用栈的方式处理。将两个链表的值按照顺序放入栈中,然后出栈求和,知道栈为空或进位值为0。

代码:

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
function addTwoNumbers (l1, l2) {
  const stack1 = [];
  const stack2 = [];
  while(l1) {
      stack1.push(l1.val);
      l1 = l1.next;
  }
  while(l2) {
      stack2.push(l2.val);
      l2 = l2.next;
  }
  let tem = 0;
  let ans = null
  while(stack1.length > 0 || stack2.length > 0 || tem !== 0) {
      const val1 = stack1.length ? stack1.pop() : 0;
      const val2 = stack2.length ? stack2.pop() : 0;
      let sum = val1 + val2 + tem;
      tem = Math.floor(sum / 10);
      sum =  sum % 10;
      const res = new ListNode(sum);
      res.next = ans;
      ans = res;
  }
  return ans;
};

重排链表

重排链表 -- leetcode

题目大意:

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:

1626420311-PkUiGI-image.png

输入: head = [1,2,3,4]

输出: [1,4,2,3]

解题思路:

  • 第一步找到链表的中间节点,这里采用快慢指针的方式,快指针是慢指针的两倍速度,快指针到达链表尾部,慢指针的位置就是链表的中间位置。
  • 将链表中间后面的节目进行反转。
  • 将链表前面的部分和反转后的链表按照题意拼接到一起即可。

代码:

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
 
/**
 * @param {ListNode} head
 * @return {void} Do not return anything, modify head in-place instead.
 */
 function getMiddle (node) {
    let first = node;
    let second = node;
    while(first.next !== null && first.next.next !== null) {
        first = first.next.next;
        second = second.next;
    }
    return second;
 }
 function reserveNode (node) {
    let prev = null;
    while(node) {
        const tem = node.next;
        node.next = prev;
        prev = node;
        node = tem;
    }
    return prev;
 }
 function mergeNode (l1, l2) {
    let tem1, tem2
    while(l1 && l2) {
        tem1 = l1.next;
        tem2 = l2.next;

        l1.next = l2;
        l1 = tem1;

        l2.next = l1;
        l2 = tem2;
    }
 }
var reorderList = function(head) {
   if (!head) return head;
   const mid = getMiddle(head);
   const l1 = head;
   let l2 = mid.next;
   mid.next = null;
   l2 = reserveNode(l2);
   mergeNode(l1, l2);
};

面试题 02.08. 环路检测

面试题 02.08. 环路检测 -- leetcode

题目大意:

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况

示例:

circularlinkedlist.png

输入:head = [3,2,0,-4], pos = 1

输出:tail connects to node index 1

解释:链表中有一个环,其尾部连接到第二个节点。

解题思路:

  • 这道题和之前做的环形链表II是一样的,就是找到链表环形的入口节点。
  • 采用快慢指针来实现,具体解析看之前的文章

代码:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
function detectCycle (head) {
    if (!head) return null;
    let fast = slow = head;
    while(fast?.next) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow === fast) {
            slow = head;
            while(slow !== fast) {
                slow = slow.next;
                fast = fast.next;
            }
            return fast;
        }
    }
    return null;
};

剑指 Offer 18. 删除链表的节点

剑指 Offer 18. 删除链表的节点 -- leetcode

题目大意:

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

**注意:** 此题对比原题有改动

示例:

输入: head = [4,5,1,9], val = 5

输出: [4,1,9]

解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

解题思路:

  • 本题与上一篇文章最后一题很相似。首先我们是找到待删除节点位置。
  • 遍历链表,当链表不为空且当前节点不是待删除节点,就继续遍历链表。
  • 找到待删除节点后,判断待删除节点之后是否还有节点,有就将当前节点删除,链接好原链表。

代码:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
function deleteNode (head, val) {
    if (head.val === val) return head.next;
    let prev = head; 
    let cur = head.next;
    while(cur !== null && cur.val !== val) {
        prev = cur;
        cur = cur.next;
    }
    if (cur !== null) prev.next = cur.next;
    return head;
};

设计链表

设计链表 -- leetcode

题目大意:

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

示例:

MyLinkedList linkedList = new MyLinkedList();

linkedList.addAtHead(1);

linkedList.addAtTail(3);

linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3

linkedList.get(1); //返回2

linkedList.deleteAtIndex(1); //现在链表是1-> 3

linkedList.get(1); //返回3

解题思路:

  • 这道题可以用单链表来处理,也可以用双链表。
  • 采用双链表时间复杂度: addAtHead,addAtTail: O(1) get,addAtIndex,delete:O(min(k,N−k)),其中 kk 指的是元素的索引。 空间复杂度:所有的操作都是 \mathcal{O}(1)O(1)。

代码:

function ListNode (val, pre, next) {
    this.val = val || 0;
    this.pre = pre || null;
    this.next = next || null;
}
var MyLinkedList = function() {
  this.head = new ListNode(-1);
  this.tail = new ListNode(-1);
  this.head.next = this.tail;
  this.tail.pre = this.head;
  this.length = 0;
};

MyLinkedList.prototype.getNodeOfIndex = function (index) {
    let node = this.head.next;
    while(index) {
        node = node.next
        index--;
    }
    return node;
}
/** 
 * @param {number} index
 * @return {number}
 */
MyLinkedList.prototype.get = function(index) {
  if (index > this.length) return -1;
  return this.getNodeOfIndex(index).val;
};

/** 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtHead = function(val) {
  const tem = new ListNode(val, this.head, this.head.next);
  this.head.next.pre = tem;
  this.head.next = tem;
  this.length++;
};

/** 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtTail = function(val) {
  const tem = new ListNode(val, this.tail.pre, this.tail);
  this.tail.pre.next = tem;
  this.tail.pre = tem;
  this.length++;
};

/** 
 * @param {number} index 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtIndex = function(index, val) {
  if (index > this.length) return;
  if (index === this.length) {
      this.addAtTail(val);
      return
  }
  if (index < 0) {
      this.addAtHead(val);
      return
  }
  const cur = this.getNodeOfIndex(index);
  const tem = new ListNode(val, cur.pre, cur);
  cur.pre.next = tem;
  cur.pre = tem;
  this.length++
};

/** 
 * @param {number} index
 * @return {void}
 */
MyLinkedList.prototype.deleteAtIndex = function(index) {
  if (index < 0 || index >= this.length) return;
  const cur = this.getNodeOfIndex(index);
  cur.pre.next = cur.next;
  cur.next.pre = cur.pre;
  this.length--;
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * var obj = new MyLinkedList()
 * var param_1 = obj.get(index)
 * obj.addAtHead(val)
 * obj.addAtTail(val)
 * obj.addAtIndex(index,val)
 * obj.deleteAtIndex(index)
 */

结束语

数据结构与算法相关的练习题会持续输出,一起来学习,持续关注。当前是链表复习部分。后期还会有其他类型的数据结构,题目来源于leetcode。

往期文章:

链表相关算法复习                         数据结构与算法-栈一                         数据结构与算法-栈二

数据结构与算法-队列一                数据结构与算法-队列二                     数据结构与算法-链表一

数据结构与算法-链表二                数据结构与算法-链表三

有兴趣的可以一起来刷题,感谢点赞👍 , 关注!