前端算法必刷题系列[60]

268 阅读1分钟

这是我参与更文挑战的第 9 天,活动详情查看 更文挑战

这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。

114. 回文链表 (palindrome-linked-list)

标签

  • 链表
  • 简单

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2
输出: false

示例 2:

输入: 1->2->2->1
输出: true

基本思路

  1. 复制链表值到数组列表中。
  2. 判断是否为回文。

写法实现

var isPalindrome = function(head) {
  // 准备一个数组,存链表值
  let compareArr = []
  // 遍历把链表值进数组
  while (head !== null) {
    compareArr.push(head.val)
    head = head.next
  }
  // 判断数组回文即可
  return compareArr.join('') === compareArr.reverse().join('')
}

效率高些可用双指针

var isPalindrome = function(head) {
    const vals = [];
    while (head !== null) {
        vals.push(head.val);
        head = head.next;
    }
    // 双指针判断回文
    for (let i = 0, j = vals.length - 1; i < j; ++i, --j) {
        if (vals[i] !== vals[j]) {
            return false;
        }
    }
    return true;
};

115. 排序链表 (sort-list)

标签

  • 链表
  • 中等

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

给你链表的头结点 head ,请将其按 升序 排列并返回排序后的链表

进阶: 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

image.png

示例 1:

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

image.png

示例 2:

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

基本思路

如果对归并排序不了解,请看我这篇 [算法拆解] 归并排序

本题正需要使用这个算法,还有我们上篇提到的分治思想

根据题目的时间复杂度要求,暴力做法肯定达不到效果,我们可以采用把这个复杂问题分解成

  1. 先拆分大链表

    • 分的过程,直到二分到不能再二分,即递归压栈压到链只有一个结点(有序),于是在递归出栈时进行合并。

    • 二分方法具体做法是找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2 步,慢指针每次移动 1 步,(跟环形链表思想类似)当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。

  2. 再合并两个有序链表

    • 合的过程,合并后的结果返回给父调用,一层层向上,最后得出大问题的答案。
    • 合并两个有序链表 我们之前有做过这题,所以其实,很多困难题都是由简单题合并而成的,你要做的就是拆解。而这项能力需要有长足的积累。

写法实现

我们可以先给个伪代码的思路

var sortList = function(head) {
    // 二分 (递归)
    l = sortList(左链) // 已排序的左链
    r = sortList(右链) // 已排序的右链
    merged = mergeList(l, r) // 进行合并
    // 返回合并的结果给父调用
    return merged
};

接下来补全逻辑,不要怕看上去代码多,其实思路很清晰

// 合并两个有序链表
var mergeTwoLists = function(l1, l2) {
  // 终止条件:当两个链表都为空时,表示我们对链表已合并完成。
  if (l1 === null) {
    return l2
  } else if (l2 === null) {
    return l1
  } else if (l1.val < l2.val) {
    // 较小结点的 next 指针指向其余结点的合并结果。
    l1.next = mergeTwoLists(l1.next, l2)
    return l1
  } else {
    l2.next = mergeTwoLists(l1, l2.next)
    return l2
  }
};

var sortList = function(head) {
  // 特判空
  if (!head || !head.next) {
    return head
  }
  // 快慢指针来分割链表
  let [fast, slow, preSlow] = [head, head, null]
  while (fast && fast.next) {
    // 记录下可能断开点,就是当前 slow 位置
    preSlow = slow
    // 快指针2步,慢指针1步
    fast = fast.next.next
    slow = slow.next
  }
  // 要把第一个链表的尾部置空,断开
  preSlow.next = null
  // 当快指针走到头了,当前慢指针就是分隔点
  // 递归操作继续分左右子链
  let l = sortList(head)
  let r = sortList(slow)
  return mergeTwoLists(l, r)
};

另外向大家着重推荐下这个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列

今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友 Or 搜索我的微信号infinity_9368,可以聊天说地 加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我 presious tower shock the rever monster,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考