🔥leetcode链表算法的通用解决思路

771 阅读4分钟

前言

最近在刷 leetcode 针对链表的算法题。在刷完 面试经典 150 题LeetCode 热题 100 中链表类型的算法题目之后,发现针对链表相关的算法题可以总结出通用思路进行解题。且将总结的思路用在后续刷链表的算法题时大概率场景下都可以通过。目前已经将面试经典 150 题LeetCode 热题 100 的链表类目的算法题都已经刷完了。

  1. 面试经典 150 题 链表 image.png
  2. LeetCode 热题 100 链表 image.png

解题思路

针对链表的数据结构 JavaScript 没有直接可用的数据类型进行表示,因此也就不存在对应的方法和操作可以直接使用。但是类比于链表的数据结构 JavaScript 的数组可以模拟,链表的数据单元 ListNode 包含一个 value 内容和一个 next 指针指向下一个节点,类比到 JavaScript Array 数组中每一项值就可以对应 value 而数组的索引 index 也恰可以表示这种指针的关系。因此换一种思路考虑,如果将链表转换成数组,而 JavaScript 针对 Array 这种基本数据类型提供了丰富的 API 可以使用。由此,我们将链表转换成数组进行算法求解,然后在将数组转换成链表做结果返回即可。解题思路可以总结成如下的步骤:

  1. 针对链表为 null 边缘场景直接返回。
if (head === null) return null;
  1. 将链表转换为数组。
cosnt nodes: ListNode[] = [];
while (head) {
    nodes.push(head);
    head = head.next;
}
  1. 针对题目描述运用数组相关的 Api 进行算法求解
这里使用数组一系列的Api进行算法求解
  1. 将数组结构转换成链表并返回结果
nums: ListNode[] = [];
for (let i = 0; i < nums.length; i++) {
    nums[i].next = i === nums.length - 1 ? null : nums[i + 1];
}
return nums[0];

总体思路如上,但针对不同的题目可能四个步骤并不需要都执行仅需要其中的几个步骤即可解题,需要针对不同的题目灵活运用上述步骤。总体思路就是将链表转换成数组熟练组合数组 Api 进行算法求解。

题目示例

这里我们从简单、中等、困难找三个 leetcode 上的链表算法题运用上面的步骤进行求解。

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

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

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

解题:
function isPalindrome(head: ListNode | null): boolean {
    const listVal = [];
    // 将链表转换成数组
    while (head) {
        listVal.push(head.val);
        head = head.next;
    }
    // 运用数组的 Api 进行解题
    return listVal.join('') === [...listVal].reverse().join('');
};

这是一道难度为简单的链表算法题,我们仅运用了上述总结的步骤 2 和 3 即完成了求解。

  1. 148. 排序链表
描述:
给你链表的头结点 `head` ,请将其按 `升序` 排列并返回 `排序后的链表` 。

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

约束:
1. 链表中节点的数目在范围 `[0, 5 * 104]` 内
2. `-105 <= Node.val <= 105`

解题:
function sortList(head: ListNode | null): ListNode | null {
    // 针对为 null 边缘场景直接返回
    if (head === null) return null;
    // 链表转换为数组
    const list: ListNode[] = [];
    while (head) {
        list.push(head);
        head = head.next;
    }
    // 进行算法求解,即是利用数组的 sort 方法进行排序
    list.sort((a, b) => a.val - b.val);
    // 将数组转换成链表并返回结果
    for (const [index, value] of list.entries()) {
        index === list.length - 1 ? value.next = null : value.next = list[index + 1];
    }

    return list[0];
};

这是一道难度为中等的链表算法题,我们充分运用了上述总结的步骤完成解题。

  1. 25. K 个一组翻转链表
描述:
给你链表的头节点 `head` ,每 `k` 个节点一组进行翻转,请你返回修改后的链表。

`k` 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 `k` 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例:
输入: head = [1,2,3,4,5], k = 2
输出: [2,1,4,3,5]

约束:
1. 链表中的节点数目为 `n`
2. `1 <= k <= n <= 5000`
3. `0 <= Node.val <= 1000`

解题:
function reverseKGroup(head: ListNode | null, k: number): ListNode | null {
    // 针对为 null 边缘场景直接返回
    if (head === null) return null;
    // 链表转换为数组
    const list: ListNode[] = [];
    while (head) {
        list.push(head);
        head = head.next;
    }
    // 进行算法求解
    const nums: ListNode[] = [];
    for (let i = 0; i < list.length; i += k) {
        const numbers = list.slice(i, Math.min(k + i, list.length));
        const len = numbers.length;
        if (len === k) numbers.reverse();
        nums.push(...numbers.flat());
    }
    // 将数组转换成链表并返回结果
    for (let i = 0; i < nums.length; i++) {
        nums[i].next = i === nums.length - 1 ? null : nums[i + 1];
    }
    return nums[0];
};

这是一道难度为困难的链表算法题,我们充分运用了上述总结的步骤也完成了解题。

总结

本文只是针对链表该类型的算法题提供一种可行的通用解决思路,但可能不是最优解,如果有更优解法也欢迎文章下留言讨论。