【算法-LeetCode】234. 回文链表(数组;双指针/快慢指针)

234. 回文链表 - 力扣(LeetCode)

文章更新:2021年10月13日14:34:28

问题描述及示例

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

示例 1:

输入:head = [1,2,2,1]
输出:true

示例 2:
在这里插入图片描述
输入:head = [1,2]
输出:false

提示:
链表中节点数目在范围[1, 105] 内
0 <= Node.val <= 9

我的题解

我的题解1(数组)

之前做过一个回文数的题目,可以做一下参考:

参考:【算法-LeetCode】9. 回文数(数组方法)_赖念安的博客-CSDN博客

我是先遍历链表,并在遍历的同时将链表中的节点值按遍历顺序存入一个字符串 str。最后只需要判断这个字符串翻转之后的结果是否和未翻转的结果一致即可。

/**
 * 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 {boolean}
 */
var isPalindrome = function(head) {
  let p = head;
  let str = '';
  while(p) {
    str += p.val;
    p = p.next;
  }
  return str === str.split('').reverse().join('');
};


提交记录
85 / 85 个通过测试用例
状态:通过
执行用时:244 ms, 在所有 JavaScript 提交中击败了11.21%的用户
内存消耗:65.4 MB, 在所有 JavaScript 提交中击败了23.78%的用户
时间:2021/10/13 14:31	

当然,这种思路还可以针对回文的判断做一些改进:

var isPalindrome = function(head) {
  let p = head;
  let arr = [];
  while(p) {
    arr.push(p.val);
    p = p.next;
  }
  // 优化回文判断逻辑,只需要遍历到arr的一半即可
  for(let i = 0; i <= Math.floor(arr.length / 2); i++) {
    if(arr[i] !== arr[arr.length-1-i]) {
      return false;
    }
  }
  return true;
};


提交记录
85 / 85 个通过测试用例
状态:通过
执行用时:172 ms, 在所有 JavaScript 提交中击败了57.32%的用户
内存消耗:64.1 MB, 在所有 JavaScript 提交中击败了51.49%的用户
时间:2021/10/13 15:05	

我的题解2(双指针/快慢指针)

更新:2021年10月14日19:15:44

之前我就总结过,链表类的题目往往要充分利用指针的思想。本题也是。利用如果利用快慢指针的速度差的话,可以比较巧妙地确定链表的中点位置。大体思路如下:

  1. 先让快指针 fast 和 慢指针 slow 从头开始遍历链表。其中,快指针每次走两步,而慢指针每次只走一步。这样的话,当快指针到达链表末尾时,则慢指针就到达了链表的中点。这也是本题的关键点。
  2. 在慢指针往前行进的过程中,顺便也将链表的前半部分进行反转操作。这样的话,当快指针到达链表末尾的时候,慢指针所在位置的前半部分链表就是反转之后的链表。让指针 r 指向该反转后的链表头结点。
  3. 快指针完成遍历后,需要重复利用一下它。所以将在链尾的快指针重新指向链表的中点位置,当然这个中点位置需要根据链表的长度进行判断。
  4. 之后就是让 r 指针和 fast 指针开始各自往后移动,并比较节点的值,一旦发现当前节点的值不一样,则立即返回 false,如果两指针都顺利到达了各自的链表尾部,则返回 true

之前做过反转链表的题目,可以参看下面的博客:

参考:【算法-LeetCode】206. 反转链表(单链表;生成LeetCode单链表)_赖念安的博客-CSDN博客

详解请看下方注释:

/**
 * 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 {boolean}
 */
var isPalindrome = function(head) {
  // r指针指向前半部分反转后的链表头部
  let r = head;
  // fast是快指针,用于遍历链表
  let fast = head;
  // slow是慢指针,也是用于遍历链表,但是它的移动速度是fast的一半
  let slow = head;
  // p和q指针是用于反转前半部分链表的辅助指针
  let p = head.next;
  let q = null;
  // step用于记录快指针移动的步数,同时也起到控制慢指针移动速度和记录链表长度的作用
  let step = 0;
  // 开始让快指针遍历链表
  while (fast.next) {
    fast = fast.next;
    // 快指针每往前走一步,就让step的值加一
    step++;
    // 如果当前step的值为奇数,那么就跳过后面的让慢指针移动的语句,
    // 这也就是控制慢指针移动速度的始终是快指针一半的原理
    if(step % 2) {
      continue;
    }
    // 如果step的值为偶数,则让慢指针也往后移动一步
    slow = slow.next;
    // 慢指针往后移动的同时也顺便进行对前半部分链表节点进行反转的操作,
    // 以下的操作可以参考上面的相关博客,里面有比较详细的描述,这里就不再赘述了
    q = p.next;
    p.next = r;
    r = p;
    p = q;
  }
  // 遍历完链表后,一定要记得将原先的头结点的next域置空,否则就会出现环形结构
  head.next = null;
  // 根据链表长度的奇偶性来判断反转后的前半部分链表的头结点
  r = step % 2 ? r : r.next;
  // 同时将链表尾部的fast指针指向链表中点以重复利用它
  fast = p;
  // 开始逐个比较链表后半部分和反转后的链表前半部分的节点值以判断是否是回文
  // 一旦出现不一样的值,则立即返回false
  while(fast) {
    if(fast.val !== r.val) {
      return false;
    }
    fast = fast.next;
    r = r.next;
  }
  // 如果每个节点的值就相等,那就说明该链表是回文链表
  return true;
};


提交记录
85 / 85 个通过测试用例
状态:通过
执行用时:116 ms, 在所有 JavaScript 提交中击败了97.90%的用户
内存消耗:52.2 MB, 在所有 JavaScript 提交中击败了98.11%的用户
时间:2021/10/14 19:11	

可以看到这种解法的性能提升还是比较大的。

对题解2的小小改进

上面解法2中的 slow 指针其实只是充当了遍历前半部分链表的作用,那么完全可以让它代替上面的 p 指针,因为该指针其实也是在遍历链表的前半部分。而且在开始反转链表时也可以让 r 指针从一开始就指向 null,这样的话,遍历完成后就不会出现头部的环形结构。

其他的思路还是和解法2一致,就不做过多注释了。只是要注意一些边界条件的判断的变化。

/**
 * 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 {boolean}
 */
var isPalindrome = function (head) {
  // 注意这里r指针初始指向的变化
  let r = null;
  let fast = head;
  // slow指针用于代替p指针的作用,注意它的初始指向也发生了变化
  let slow = head;
  let q = null;
  let step = 0;
  // 注意这里遍历结束条件的变化
  while (fast) {
    // 遍历逻辑不变
    fast = fast.next;
    step++;
    if (step % 2) {
      continue;
    }
    // 反转逻辑不变
    q = slow.next;
    slow.next = r;
    r = slow;
    slow = q;
  }
  // 注意这里无论原链表的长度是奇数还是偶数,r最终指向的节点都是我们预期的反转链表的头结点
  // 而fast指针的指向则需要考虑原链表的长度了
  fast = step % 2 ? slow.next : slow;
  // 判断回文的逻辑不变
  while (fast) {
    if (fast.val !== r.val) {
      return false;
    }
    fast = fast.next;
    r = r.next;
  }
  return true;
};


提交记录
85 / 85 个通过测试用例
状态:通过执行用时:108 ms, 在所有 JavaScript 提交中击败了99.23%的用户
内存消耗:53.1 MB, 在所有 JavaScript 提交中击败了95.69%的用户
时间:2021/10/14 20:10

可以看到表现还是有点提升的,不过我纳闷的是为啥内存消耗的表现反倒是下降了一点?按理来说少用了一个指针不应该是有所提升吗?不管了,反正LeetCode的判题一直都有点玄学,理论上能解释得通就行了~

官方题解

更新:2021年7月29日18:43:21

因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。

更新:2021年10月14日21:00:07

参考:回文链表 - 回文链表 - 力扣(LeetCode)

【更新结束】

有关参考

更新:2021年10月13日14:40:13
参考:【算法-LeetCode】9. 回文数(数组方法)_赖念安的博客-CSDN博客
参考:【算法-LeetCode】206. 反转链表(单链表;生成LeetCode单链表)_赖念安的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值