算法笔记(链表篇)

4 篇文章 0 订阅
1 篇文章 0 订阅
  • 链表

    • 移除链表元素

      • 203.移除链表元素

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

/**
* Definition for singly-linked list.
* public class ListNode {
*     int val;
*     ListNode next;
*     ListNode() {}
*     ListNode(int val) { this.val = val; }
*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
// 设置一个前指针一后指针 两两相邻进行移动 当后指针指向结点等于val 的节点时 直接使 前指针指向结点的后继指针 指向 后指针指向结点的后继结点 跳过当前等于val 的节点 即表示删除
// 考虑到头结点可能会是== val 的节点 直接在头结点前面再加一个虚拟头结点 它的后继结点指向头结点  方便循环操作删除结点      结果返回虚拟头结点的后继结点即可
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {                                   // 判断是否为空链表
            return null;                                       
        }

        ListNode dummy = new ListNode(-1, head);              // 新建虚拟头结点
        ListNode p = dummy;                                   // 前指针
        ListNode q = dummy.next;                              // 后指针
        
        while (q != null) {                                   // 遍历链表
            if (q.val == val) p.next = q.next;                // 当后指针结点== val节点时 直接使 前指针指向结点的后继指针 指向 后指针指向结点的后继结点 跳过当前等于val 的节点 即表示删除
            else p = q;                                       // 两两相邻进行移动
            q = q.next;                                       // 移动后指针
        }

        return dummy.next;                                    // 结果返回虚拟头结点的后继结点 即真实头结点
    }
}

    • 设计链表

      • 707.设计链表

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

在链表类中实现这些功能:

get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。

addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。

addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。

addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。

deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点

// 五个接口,已经覆盖了链表的常见操作  增删插查
class ListNode {                                    // 设定结点  数据域 指针域
    int val;
    ListNode next;
    ListNode () {}
    ListNode (int val) {
        this.val = val;
    }
}

class MyLinkedList {

    int size;                                       // 链表长度
    ListNode head;                                  // 链表头结点

    public MyLinkedList() {                         // 初始化链表
        size = 0;                                   // 初始化长度为0
        head = new ListNode (0);                    // 建立头结点
    }
    
    public int get(int index) {                     // 查找第index个结点的值
        if (index < 0 || index >= size) {           // 判断index是否超出链表范围
            return -1;
        }

        ListNode p = head;                          // 指针p
        for (int i = 0; i <= index; ++i) {          // 循环遍历  将 指针p 移动至第index个结点上
            p = p.next;                             // 指针p往后移动
        }
        return p.val;                               // 返回第index个结点的值
    }
    
    public void addAtHead(int val) {                // 头插法  调用addAtIndex(0, val);方法
        addAtIndex(0, val);
    }
    
    public void addAtTail(int val) {                // 尾插法  调用addAtIndex(size, val);方法
        addAtIndex(size, val);
    }
    
    public void addAtIndex(int index, int val) {    // 将元素值插入链表指定位置
        if (index > size) {                         // 不能超出链表范围
            return;
        }
        if (index < 0) {                            // 插入位置小于0则 插入到头结点
            index = 0;
        }
        size++;                                     // 可插入结点   链表长度++

        ListNode p = head;                          // 指针p
        for (int i = 0; i < index; ++i) {           // 循环遍历 找到要插入位置的前继结点
            p = p.next;                             // 指针p往后移动
        }
        ListNode m = new ListNode(val);             // 新建结点
        m.next = p.next;                            // 将新建结点插入链表
        p.next = m;
    }
    
    public void deleteAtIndex(int index) {          // 删除指定链表index位置上的结点
        if (index < 0 || index >= size) {           // 不能超出链表范围
            return;
        }
        size--;                                     // 可删除结点   链表长度--

        ListNode p = head;                          // 循环遍历 找到要删除位置的前继结点
        for (int i = 0; i < index; ++i) {
            p = p.next;
        }
        p.next = p.next.next;                       // 移动指针 越过该要删除的结点
    }
}

/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/

    • 反转链表

      • 707反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

/**
* Definition for singly-linked list.
* public class ListNode {
*     int val;
*     ListNode next;
*     ListNode() {}
*     ListNode(int val) { this.val = val; }
*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
// 改变链表的next指针的指向,直接将链表反转  一个前指针一个后指针 一个保存指针     
// 循环遍历链表 先将后指针结点的后继结点保存 将后指针的指针域指向前指针结点   再前指针指向后指针结点  后指向保存结点  再次移动  遍历完成即反转完成
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;                      // 保存指针
        ListNode p = head;                        // 前指针
        ListNode q = null;                        // 后指针

        while (p != null) {                       // 循环遍历链表
            pre = p.next;                         // 将后指针结点的后继结点保存
            p.next = q;                           // 后指针的指针域指向前指针结点
            q = p;                                // 前指针 指向 后指针结点
            p = pre;                              // 后指向保存结点  再次循环遍历移动
        }

        return q;                                 // 返回前指针 即反转后的头结点
    }
}

    • 两两交换链表的结点

      • 24.两两交换链表的结点

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

/**
* Definition for singly-linked list.
* public class ListNode {
*     int val;
*     ListNode next;
*     ListNode() {}
*     ListNode(int val) { this.val = val; }
*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
// 虚拟头结点方法
// 建立虚拟头结点 方便交换前两个结点  三个指针 按链表顺序依次指向 · 先交换指针二指针三结点 · 指针一指针域 指向 指针三结点 · 指针二指针域 指向 指针三后继结点 · 指针三指针域 指向 指针二结点 · 前两个结点交换完成 · 指针一再指向之前指针二 即交换后两个结点被换至到后面的结点 · 指针二指针三依次指向指针一后继结点  再一次交换结点   以次循环交换
class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyNode  = new ListNode(0, head);           // 建立虚拟头结点 方便交换前两个结点
        ListNode pre = dummyNode;                              // 指针一

        while (pre.next != null && pre.next.next != null) {    // 需要交换的两个不能为空
            ListNode p = pre.next;                             // 指针二
            ListNode q = pre.next.next;                        // 指针三
            pre.next = q;                                      // 指针一指针域 指向 指针三结点
            p.next = q.next;                                   // 指针二指针域 指向 指针三后继结点
            q.next = p;                                        // 指针三指针域 指向 指针二结点
            pre = p;                                           // 指针一再指向之前的指针二的结点 即交换后两个结点被换至到后面的结点
        }

        return dummyNode.next;                                 // 返回真实头结点
    }
}

// 递归方法
// 递归至最后两个不为空可以互换的结点处  进行互换  ·  完成后返回 交换后两个结点被换至到前面的结点
class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) {               // 两个不为空可以互换的结点
            return head;
        }

        ListNode p = head.next;                                // 两个交换结点中偏 后面的结点
        ListNode q = swapPairs(p.next);                        // 先交换链表后方的两两结点   返回交换后两个结点被换至到前面的结点
                                                               // 开始交换两两结点
        p.next = head;                                         // 后结点指针域 指向 前结点
        head.next = q;                                         // 前结点指针域 指向 后两两结点交换完成后的前结点
                                                               // 交换两两结点完成
        return p;                                              // 返回两两结点交换完成后的前结点
    }
}

    • 删除链表倒数第n个结点

      • 19.删除链表倒数第n个结点

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

/**
* Definition for singly-linked list.
* public class ListNode {
*     int val;
*     ListNode next;
*     ListNode() {}
*     ListNode(int val) { this.val = val; }
*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
// 双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。   fast和slow始终保持n步的距离  
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead = new ListNode(0, head);             // 新建虚拟头结点
        ListNode p = dummyHead;                                 // 前指针
        ListNode q = dummyHead;                                 // 后指针
        ListNode pre = null;                                    // 保存前指针前继结点  方便删除

        while (n-- > 0) {                                       // 先让后指针移动n步
            q = q.next;
        }

        while (q != null) {                                     // 然后让p和q同时移动,直到q指向链表末尾。删掉p所指向的节点就可以了
            pre = p;                                            // 保存前指针前继结点  方便删除
            q = q.next;                                         // 移动后指针
            p = p.next;                                         // 移动前指针
        }

        pre.next = p.next;                                      // 删除p指针结点    即删除倒数第 n 个结点
        return dummyHead.next;
    }
}

    • 链表相交

      • 面试题02.07.链表相交

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

/**
* Definition for singly-linked list.
* public class ListNode {
*     int val;
*     ListNode next;
*     ListNode(int x) {
*         val = x;
*         next = null;
*     }
* }
*/
情况—:两个链表相交
链表headA和headB的长度分别是m和n。假设链表head.A的不相交部分有α个节点,链表headB的不相交部分有b个节点,两个链表相交的部分有c个节点,则有α+c= m,b+C= n。
·如果a= b,则两个指针会同时到达两个链表相交的节点,此时返回相交的节点;
·如果a≠b,则指针 pA 会遍历完链表headA,指针pB会遍历完链表headB,两个指针不会同时到达链表的尾节点,然后指针pA移到链表headB的头节点,指针 pB移到链表headA的头节点,然后两个指针继续移动,在指针pA移动了α+c+b次、指针 pB移动了b+c+α次之后,两个指针会同时到达两个链表相交的节点,该节点也是两个指针第一次同时指向的节点,此时返回相交的节点。
情况二:两个链表不相交
链表headA和headB的长度分别是m和n。考虑当m =n和m≠n时,两个指针分别会如何移动;·
如果m=n,则两个指针会同时到达两个链表的尾节点,然后同时变成空值null,此时返回null;
。如果m≠n,则由于两个链表没有公共节点,两个指针也不会同时到达两个链表的尾节点,因此两个指针都会遍历完两个链表,在指针 pA移动了m+n次、指针 pB移动了n+m次之后,两个指针会同时变成空值null,此时返回null。
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {            // 两个单链表都不能为空 否则不会相交
            return null;
        }
        ListNode A = headA;
        ListNode B = headB;
        while (A != B) {                                 // 寻找相交结点   
            A = A != null ? A.next : headB;              // 遍历完A链表再遍历B链表不相交部分 即m+c+n
            B = B != null ? B.next : headA;              // 遍历完B链表再遍历A链表不相交部分 即n+c+m
        }
        return A;
    }  
}

    • 环形链表

      • 141.环形链表1

给你一个链表的头节点 head ,判断链表中是否有环。

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

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

/**
* Definition for singly-linked list.
* class ListNode {
*     int val;
*     ListNode next;
*     ListNode(int x) {
*         val = x;
*         next = null;
*     }
* }
*/
// 一个快指针 一个慢指针    快指针每次走两步 慢指针每次走一步 如果链表存在环 快慢指针一定会相遇
// 当一个链表有环时,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode p = head;                            // 慢指针
        ListNode q = head;                            // 快指针

        while (q != null && q.next != null) {         // 快指针后继结点不能为空   否则就是此链表无环
            p = p.next;                               // 慢指针每次走一步
            q = q.next.next;                          // 快指针每次走两步
            if (p == q) {                             // 链表存在环 快慢指针一定会相遇  此时pq都为相遇点
                return true;
            }
        }
        return false;                                 // 此链表没有环
    }
}

// 拓展   如果存在环则环的长度为多少?
// 方法是,快慢指针相遇后继续移动,直到第二次相遇。两次相遇间的移动次数即为环的长度。
// 当第一次相遇时 记录打开一个标识位 两指针继续往下走环 同时标识位打开开始记录环的长度   当两指针第二次相遇时 表明慢指针已经将环走了一遍 记录下来的长度即为环的长度
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode p = head;                            // 慢指针
        ListNode q = head;                            // 快指针
        bollean b = false;                            // 标识位
        int size = 0;                                 // 环的长度

        while (q != null && q.next != null) {         // 快指针后继结点不能为空   否则就是此链表无环
            p = p.next;                               // 慢指针每次走一步
            q = q.next.next;                          // 快指针每次走两步
            if (p == q) {                             // 链表存在环 快慢指针一定会相遇  此时pq都为相遇
                if (b) reture size;                   // 第二次相遇 已收集完环的长度 退出
                b = true;                             // 第一次相遇 打开标识位 开始记录环的长度 
            }
            if (b) {                                  // 打开标识位 开始记录环的长度
                size ++;                              // 记录环的长度 size ++;
            }
        }
        return false;                                 // 此链表没有环
    }
}

      • 142.环形链表2

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

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

不允许修改 链表。

/**
* Definition for singly-linked list.
* class ListNode {
*     int val;
*     ListNode next;
*     ListNode(int x) {
*         val = x;
*         next = null;
*     }
* }
*/
// 先证明此链表有没有环   在找环的入口
// 一个快指针 一个慢指针    快指针每次走两步 慢指针每次走一步 如果链表存在环 快慢指针一定会相遇     此时证明了链表是存在环的
// 找环的入口 
//  根据:
//  1. f = 2s (快指针每次2步,路程刚好2倍)    f = 快指针移动步数   s = 慢指针移动步数
//  2. f = s + nb (相遇时,刚好多走了n圈)     b = 环长度 n = 快指针多走了n圈
//  推出:s = nb
//  从head结点走到入环点需要走 : a + nb,      (先走 a 步到入口节点,之后每绕 1 圈环( b 步)都会再次到入口节点)。
//  而slow已经走了nb,那么slow再走a步就是入环点了。   (推出:s = nb)
//  如何知道slow刚好走了a步? 从head开始,和slow指针一起走,相遇时刚好就是a步   (slow指针位置不变 ,将fast指针重新指向链表头部节点 ;slow和fast同时每轮向前走 1 步  ; 当 fast 指针走到f = a 步时,slow 指针走到步s = a + nb,此时 两指针重合,并同时指向链表环入口 。)
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode q = head;
        ListNode p = head;

        while (p != null && p.next !=null) {         // 证明有环
            q = q.next;
            p = p.next.next;
            
            if (q == p) {                            // 第一次相遇点  开始找环入口
                ListNode last = p;                   // p指针位置不变
                ListNode first = head;               // 将first指针指向链表头部节点
                while (last != first) {              // 两指针重合,并同时指向链表环入口
                    last = last.next;                // last和first同时每轮向前走 1 步
                    first = first.next;              // 直到重合即环入口
                } 
                return last;                         // 指向链表环入口
            }
        }
        return null;                                 // 无环无入口
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值