剑指Offer II 029.排序的循环链表

202 阅读4分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

每日刷题第26天 2021.1.21

剑指 Offer II 029. 排序的循环链表

题目

  • 给定循环单调非递减列表中的一个点,写一个函数向这个列表中插入一个新元素 insertVal ,使这个列表仍然是循环升序的。
  • 给定的可以是这个列表中任意一个顶点的指针,并不一定是这个列表中最小元素的指针。
  • 如果有多个满足条件的插入位置,可以选择任意一个位置插入新的值,插入后整个列表仍然保持有序。
  • 如果列表为空(给定的节点是 null),需要创建一个循环有序列表并返回这个节点。否则。请返回原先给定的节点。

示例

  • 示例1 image.png
输入:head = [3,4,1], insertVal = 2
输出:[3,4,1,2]
解释:在上图中,有一个包含三个元素的循环有序列表,你获得值为 3 的节点的指针,我们需要向表中插入元素 2 。新插入的节点应该在 1 和 3 之间,插入之后,整个列表如上图所示,最后返回节点 3 。

image.png

  • 示例2
输入: head = [], insertVal = 1
输出: [1]
解释: 列表为空(给定的节点是 null),创建一个循环有序列表并返回这个节点。
  • 示例3
输入: head = [1], insertVal = 0
输出: [1,0]

提示

  • 0 <= Number of Nodes <= 5 * 10^4
  • -10^6 <= Node.val <= 10^6
  • -10^6 <= insertVal <= 10^6

解法

解题思路

补充知识

  • 单调非递减列表:不是单调递增的,因为可能存在重复的值。举例:[1,2,3,3,5]
  • 同理可知:单调非递增列表:[6,3,2,2,1]

解题思路

  • 分析(处理数据):insertVal为一个数值,但是我们需要插入的是一个节点,因此先新建一个节点newInsert,并设置newInsert.val = insertVal; newInsertVal.next = null;

第一步:(‼️链表的题目首要判断‼️)链表为空,直接插入newInsert节点,返回。

第二步:将head节点保存到cur指针中(防止链表的头节点丢失)

第三步:分情况讨论:

  • 情况1head.val == insertVal

  • 举例:[5,6,2,3,3] insertVal = 5 ,直接插入到head节点后面即可。

  • 情况2head.val < insertVal,当head节点的值小于insertVal的时候,要找到合适的插入位:head -> min(链表中最小的值)

  • 举例1:[5,6,8,1,2,3] insertVal = 7,从5找到1

  • 举例2:[7,7,7,7] insertVal = 8

  • 实现的代码思路:while将当前节点curcur.next进行比较(curcur.next形成一个区间,判断该区间是否可以插入)

// 伪代码
while(当前节点值 <= 下一节点值){
  // 因为从`head -> min`,相当于找完大于head的值,如果找到min还没有找到,那么就放在min前面一个节点
  if(insertVal 小于等于 下一节点的值){
    // 找到合适的位置,在当前节点和下一节点之间插入即可
  }else {
    // 没有找到合适,就需要将找下一个区间
  }
}
// 还有一种情况:一直没有找到小于等于的数,也就是我们需要插入的数为当前链表的最大值
// while循环跳出后,在当前节点与min之间的区间,插入即可
  • 情况3head.val > insertVal,当head节点的值大于insertVal的时候,要找到合适的插入位:min(链表中最小的值) -> head
  • 举例:[5,6,8,1,2,3] insertVal = 4
  • 举例:[7,7,7,7] insertVal = 5
  • 实现的代码思路:由于head.val > insertVal,那么如果想:只查找min -> head之间的区间❌,其实应该查找链表中所有的数,知道找到位置才跳出while循环。为什么这样呢?因为样例[5,1,3] 0,此时的如果只查找min -> head,那么就丢掉了head -> min之间的区间,就会出错❌(详细分析见:错误🙅‍♂️反思)。因此最终将while循环条件写成1,就通过了。
// 伪代码
while(当前节点 <= 下一节点){
  if(下一个节点 等于 head){
    // 自身成为一个循环链表,[1]
    // 插入新建的节点,返回
  }else{
    // 不相等即没有找到合适的位置,继续找下一个区间
  }
}

// 当while循环结束后,就找到链表最大的节点和最小的节点之间的区间
while(1){
  if(insertVal 是否小于 下一个节点){
    // 找到合适的位置,插入新建的节点,返回
  }else {
    // 没有找到合适的位置,就需要找下一个区间
  }
}
// 因为是必定会找到插入点,因此不需要额外补充代码

错误🙅‍♂️反思

出错样例:

  • [1,3,5] 0
  • [5,1,3] 0
  • [5,1,3] 2

代码

/**
* // Definition for a Node.
* function Node(val, next) {
* this.val = val;
* this.next = next;
* };
*/


/**
* @param {Node} head
* @param {number} insertVal
* @return {Node}
*/

var insert = function(head, insertVal) {
  // 单调非递减 -> 1 2 3 3 4
  // 分三种情况
  // 需要插入的节点
  let newInsert = new Node(insertVal,null);
  if (head == null){
    // 直接加入节点即可
    head = newInsert;
    head.next = head;
    return head;
  }

  // 当前所给的链表不为空
  // 3. head == insert
  // 临时存储的节点
  let tempt;
  // 两个指针
  let cur = head;
  
  if (head.val == insertVal){
    // 表示数值相同,插入到当前节点的下一个节点即可
    // 1.后面一定有节点,循环
    tempt = head.next;
    head.next = newInsert;
    newInsert.next = tempt;
    
  }else if (head.val < insertVal) {
    // 1. head < insert
    // 表示:需要找的区间:head -> min,临界条件:找到最小值都没有找到合适的位置,就放在最小值前面即可
    // 如何判断为最小值,即next.val < cur.val
    while (cur.val <= cur.next.val) {
      // console.log('cur',cur.val,'curnext',cur.next.val);
      // 找到合适的位置跳出循环 或 找到最小还是没找到跳出循环 cur < cur.next
      if (insertVal <= cur.next.val || cur.next == head){
        // 找到合适的位置
        tempt = cur.next;
        cur.next = newInsert;
        newInsert.next = tempt;
        return head;
      }else {
        cur = cur.next;
      }
    }
  // 找到最后min都没有找到合适的位置,直接插入即可
  // 找到合适的位置
  tempt = cur.next;
  cur.next = newInsert;
  newInsert.next = tempt;
  // !!!还有一种情况:【7,7,7】 8
  // 如果链表的所有数值都相等,则插入到head的前一个节点

}else if (head.val > insertVal) {
  // 2. head > insert
  // 表示:需要找的区间:min -> head,临界条件:找到head都没有找到,就放在head前面即可
  // 情况:链表的值不全部相等
  // 3 -> 5 -> 1 -> 2
  // 5 -> 3 -> 4
  // 1 -> 3 -> 5
  while (cur.val <= cur.next.val) {
    // console.log('111');
    if (cur.next == head){
      // 找到合适的位置
      tempt = cur.next;
      cur.next = newInsert;
      newInsert.next = tempt;
      return head;
    }
  // 找到最小值min与最大值之间,还没有地方,那么就插入尾部
  // cur.next 即为最小值min
  cur = cur.next;
}
// console.log('>最小的节点',cur);
// 最小值到head之间寻找合适的位置
while (1) {
  // console.log('循环',cur,'h',head);
  if (insertVal <= cur.next.val) {
    // 找到合适的位置
    tempt = cur.next;
    cur.next = newInsert;
    newInsert.next = tempt;
    return head;
  }else {
    cur = cur.next;
  }
}
// 漏掉一种情况:head就是最大值,刚好要插入head前面
// 5 1 3
// tempt = cur.next;
// cur.next = newInsert;
// newInsert.next = tempt;
// 情况:链表值全部相等
}
return head;
};

附录

  • 边界需要多注意,可以多想一些有关边界的样例