3周攻克数据结构[链表篇-1]

353 阅读3分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

本文题目全部来自 LeetCode

使用 Typescript

本篇文章全部收藏于专栏 3周攻克数据结构-LeetCode

本文所有代码和解题步骤将放置 GitHub仓库

DAY7

1. 环形链表

给定一个链表,判断链表中是否有环。

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

如果链表中存在环,则返回 true 。 否则,返回 false 。

环形链表

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

输出:true

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


环形链表2

输入:head = [1], pos = -1

输出:false

解释:链表中没有环。

单链表的构造方法:👇🏻

class ListNode {
      val: number
      next: ListNode | null
      constructor(val?: number, next?: ListNode | null) {
          this.val = (val===undefined ? 0 : val)
          this.next = (next===undefined ? null : next)
      }
  }

方法1:哈希表查询

  1. 建一个哈希表储存已经处理过的值
  2. 遍历链表;当前值内链表包含则表示有换
function hasCycle(head: ListNode | null): boolean {
    // 建表记录
    const MAP = []

    while(head) {
        // 存即有环
        if(MAP.includes(head)){
            return true
        }
        // 判断过的计入哈希表
        MAP.push(head)
        // 判断下一个 链路
        head = head.next
    }

    // 无环
    return false
};

方法1-1:哈希表延升法:链表记录法,也叫污链表法

  1. 找过的链路增加一个 flag 标识符

  2. 遇到标识符说明有环

    • 缺点:会改变原有数据结构
    • 优点:不需要单独展位一个内存
function hasCycle(head: any): boolean {
    while (head) {
        if(head.flag) {
            return true
        }
        head.flag = true;
        head = head.next;
    }

    return false
};

方法2:快慢指针

快慢指针和我们之前做的双指针的题原理大致是一样的:

  • 一个是用2个指针缩短路径,一个是用2个指针减少占用内存(对比哈希表解法)
  • 快慢指针的原理很简单就是遍历一次链表,快指针比慢指针快一步,指针重叠则说明有环
function hasCycle(head: ListNode | null): boolean {
    if (!head) return false

    // 初始化 快指针和慢指针
    let slow_p = head
    let fast_p = head

    while (fast_p.next !== null && fast_p.next.next !== null) {
        // 快指针比慢指针快一步
        slow_p = slow_p.next
        fast_p = fast_p.next.next
        // 如果快指针追上了慢指针则证明有环
        if (slow_p === fast_p) return true
    }

    return false
};

方法3:JS语言特性

JSON.stringify方法会自动检测传入的对象是否为环,如果JSON.stringify成功执行,那说明传入的对象一定不是环

function hasCycle(head: ListNode | null): boolean {
    try {
        JSON.stringify(head)
        return false
    } catch {
        return true
    }
};

2. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

合并两个有序链表

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

输入:l1 = [], l2 = []
输出:[]
示例 3:

输入:l1 = [], l2 = [0]
输出:[0]


提示:

两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列

方法1:递归

function mergeTwoLists(l1: ListNode | null, l2: ListNode | null): ListNode | null {
    
    // 其中一个空了证明排序 排完了
    if (!l1 || !l2) {
        return l1 || l2
    } 

    if (l1.val < l2.val) {
        l1.next = mergeTwoLists(l1.next, l2);
        return l1;
    } else {
        l2.next = mergeTwoLists(l1, l2.next);
        return l2;
    }
};

方法2:迭代

function mergeTwoLists(l1: ListNode | null, l2: ListNode | null): ListNode | null {
    // 已知范围最小的是 0,nwe 一个出来做比较
    const newList = new ListNode(0);
    let cur = newList;

    while (l1 && l2) {
        if (l1.val <= l2.val) {
            cur.next = l1;
            l1 = l1.next;
        } else {
            cur.next = l2;
            l2 = l2.next;
        }
        cur = cur.next;
    }

    // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
    cur.next = l1 ? l1 : l2;
    return newList.next;
};

3. 移除链表元素

方法1:递归

function removeElements(head: ListNode | null, val: number): ListNode | null {
    if (head === null) return head

    head.next = removeElements(head.next, val); 
    // 满足条件跳过当前 链表
    return head.val === val ? head.next : head;

};

方法2:迭代

function removeElements(head: ListNode | null, val: number): ListNode | null {
   const preHead = new ListNode(0);
    preHead.next = head;

    let cur = preHead;

    while (cur.next !== null) {
        if (cur.next.val == val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }
    return preHead.next;
};

总结

链表数据结构,大都是使用递归来处理