leetcode——链表

162 阅读7分钟

链表题目最好都先设置一个头节点来做就方便很多

题目 203. 移除链表元素

给你一个链表的头节点 `head` 和一个整数 `val` ,
请你删除链表中所有满足 `Node.val == val` 的节点,并返回新的头节点。

image.png

输入: head = [1,2,6,3,4,5,6], val = 6
输出: [1,2,3,4,5]
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }

这道题目说head里面都是LietNode元素。我们自己定义链表的时候是有头节点元素的,但这里没有。

image.png
现在的难题是怎么定义指针,指针指向什么元素. 标准代码如下:

var removeElements = function(head, val) {
    const ret = new ListNode(0, head);
    // 应该是ListNode { val: 0, next: [ 1, 2, 1, 3, 4 ] }
    // 但是是ret:[0,1,2,6,3,4,5,6]
    let cur = ret;
    // cur指向ret,cur:[0,1,2,6,3,4,5,6]
    while(cur.next) {
        //[0,1,2,6,3,4,5,6] 
          [1,2,6,3,4,5,6] 
          [2,6,3,4,5,6] 
          [6,3,4,5,6] 
          [3,4,5,6] 
          [4,5,6] 
          [5,6] 
          [6]
         // 奇怪的一笔
        if(cur.next.val === val) {
            cur.next =  cur.next.next;
            continue;
        }
        cur = cur.next;
    }
    return ret.next;
};

奇怪点:首先是const ret = new ListNode(0, head)相当于在链表首位加了个vai=0的节点。其次是cur.next把所有后面的节点都显示出来
解决:题目中说的head就是头节点,就相当于一个链表,它是一个个给你的

707. 设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:
val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。
如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。
假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:
get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

用的单链表做的,下次用双链表做一次

206. 反转链表

给你单链表的头节点head,请你反转链表,并返回反转后的链表。
输入: head = [1,2,3,4,5]
输出: [5,4,3,2,1]

这道题就是用双指针做,但注意的是你修改了cur.next指针前要把cur.next先保存下来。

24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。
你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

image.png

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

多看几遍,链表题最好都有个头元素来做

19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 `n` 个结点,并且返回链表的头结点。

image.png

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

这道题目就是双指针才链表上的应用 图中的slow指针最后最好指向删除节点前的节点,所以fast指针先走n步 此外还要考虑链表情况:(1)链表空值就返回false(2)判断链表个数,如果是一个,这时候fast指向null。if(!fast){return head.next}

面试题 02.07. 链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

image.png

listA = [4,1,8,4,5]
listB = [5,0,1,8,4,5]

自己思路:双循环找相同值
❌:我先遍历的链表a,第一个元素是4,然后看链表b有没有4,明显错误!!!
下面牛逼写法,遍历链表结束这个指针就开始遍历另一链表

var getIntersectionNode = function(headA, headB) {
    let p1 = headA
    let p2 = headB
    while (p1 != p2) {
        p1 = p1 == null ? headB : p1.next
        p2 = p2 == null ? headA : p2.next        
    }
    return p1
};

142. 环形链表 II

给定一个链表的头节点`head`,返回链表开始入环的第一个节点。如果链表无环,则返回 `null`

image.png

输入: head = [3,2,0,-4], pos = 1
输出: 返回索引为 1 的链表节点
解释: 链表中有一个环,其尾部连接到第二个节点。

个人思路:是每经过一个节点就保存下来,然后继续next遍历,当遇到第一个相同节点的时候我们就可以说明这是闭环,且是入口。 使用哈希表做---内存小大太大
方法2.快慢指针
可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢
首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。 image.png 此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示: image.png 那么相遇时: slow指针走过的节点数为: x + y,fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点。
所以 fast指针走过的节点数 = slow指针走过的节点数 * 2
(x + y) * 2 = x + y + n (y + z) == x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示头结点到环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y ==x = (n - 1) (y + z) + z
当n为1的时候,公式就化解为 x = z
这就意味着,从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点,那么当这两个指针相遇的时候就是环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是环形入口的节点。\

那么 如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。