05 链表相关知识以及题目总结

链表

  • 链表类型
    • 单链表
    • 双链表
    • 循环链表
  • 链表头结点定义
    • C++定义链表节点
  • 链表操作的两种方式
    • 设计链表
        • [707. 设计链表](https://leetcode.cn/problems/design-linked-list/)
    • 链表删除
        • [203. 移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/)
    • 其他相关题目
        • [206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/)
        • [24. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/)
        • [19. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)
        • [面试题 02.07. 链表相交](https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/)
        • [142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)

链表类型

单链表

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链接的入口节点称为链表的头结点也就是head。
如图所示: 05 链表相关知识以及题目总结_第1张图片

双链表

单链表中的指针域只能指向当前节点的下一个节点。
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。
如图所示: 05 链表相关知识以及题目总结_第2张图片

循环链表

循环链表,顾名思义,就是链表首尾相连。
循环链表可以用来解决约瑟夫环问题。
05 链表相关知识以及题目总结_第3张图片

链表头结点定义

C++定义链表节点

// 使用结构体定义
struct ListNode{
    int val;
    ListNode* next;
    ListNode(){}	//默认构造函数
    ListNode(int val):val(val),next(null){}
}

// 使用自定义的构造函数初始化节点
ListNode* head=new ListNode(5);
// 如果没有自定义构造函数,就会使用默认构造函数初始化节点
ListNode* head=new ListNode();
head->val=val;

链表操作的两种方式

  • 直接使用原来的链表来进行删除操作。
  • 设置一个虚拟头结点在进行删除操作。
    • 使用虚拟头结点可以简化代码的书写,要对链表进行一步的遍历时,使用虚拟头结点:
      • 循环结束条件:node.next!=None;
      • 最终返回头结点:prehead.next。

设计链表

707. 设计链表

前面介绍了链表节点的定义,对于链表类的定义如下:
c++实现:

  • 定义链表类,
  • 类的私有成员变量:
    • Node* _dummyHead; // 定义虚拟头结点
    • int _size; // 链表的元素个数(不包含虚拟头结点)
  • 链表公有成员函数:
struct Node{
    int val;
    Node* next;
    Node(){}
    Node(int val,Node* next):val(val),next(next){}
    Node(int val):val(val),next(NULL){}
};

class MyLinkedList {
private:
    Node* _dummyHead;  // 定义虚拟头结点
    int _size;  // 链表的元素个数(不包含虚拟头结点)

public:
    MyLinkedList() {
        _dummyHead=new Node(0);
        _size=0;
    }
    
    int get(int index) {
        //获取链表中第 index 个节点的值。如果索引无效,则返回-1
        //注意index是从0开始的
        if(index<0 || index>=_size){
            return -1;
        }
        else{
            Node* now=_dummyHead;
            for(int i=0;i<=index;i++){
                now=now->next;
            }
            return now->val;
        }
    }
    
    void addAtHead(int val) {
        // 在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
        Node* node=new Node(val,_dummyHead->next);
        _dummyHead->next=node;
        _size++;
    }
    
    void addAtTail(int val) {
        // 将值为 val 的节点追加到链表的最后一个元素 
        addAtIndex(_size,val);
    }
    
    void addAtIndex(int index, int val) {
        // 在链表中的第 index 个节点之前添加值为 val的节点
        if(index<0){
            index=0;
        }
        else if(index>_size){
            return;
        }

        Node* pre=_dummyHead;
        Node* now=_dummyHead;
        for(int i=0;i<=index;i++){
            pre=now;
            now=now->next;
        }
        Node* temp=new Node(val,now);
        pre->next=temp;
        _size++;
    }
    
    void deleteAtIndex(int index) {
        // 如果索引 index 有效,则删除链表中的第 index 个节点
        if(index<0||index>=_size){
            return;
        }
        Node* pre=_dummyHead;
        for(int i=0;i<index;i++){
            pre=pre->next;
        }
        Node* del=pre->next;
        pre->next=del->next;
        delete del;
        _size--;
    }
};

Java实现:

class Node{
    public int val;
    public Node next=null;

    public Node(int val){
        this.val=val;
    }
    
}

class MyLinkedList {
    Node head;
    int size;

    public MyLinkedList() {
        size=0;
        head=new Node(0);
    }
    
    public int get(int index) {
        //节点下标从0开始,head不算
        if(index>=size)  return -1;

        Node temp=head;
        while(index>=0){
            // if(temp==null) return -1;
            temp=temp.next;
            index--;
        }
        return temp.val;

    }
    
    public void addAtHead(int val) {
        Node node=new Node(val);
        node.next=head.next;
        head.next=node;
        size++;
    }
    
    public void addAtTail(int val) {
        addAtIndex(size,val);
    }
    
    public void addAtIndex(int index, int val) {
        if(index>size)  return;
        index--;
        Node temp=head;
        while(index>=0){
            // if(temp==null) return -1;
            temp=temp.next;
            index--;
        }
        Node node=new Node(val);
        node.next=temp.next;
        temp.next=node;
        size++;
    }
    
    public void deleteAtIndex(int index) {
        if(index>=size) return;
        Node temp=head;
        index--;
        while(index>=0){
            // if(temp==null) return -1;
            temp=temp.next;
            index--;
        }
        temp.next=temp.next.next;
        size--;
    }
}

/**
 * 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);
 */

链表删除

这里以链表 1 4 2 4 来举例,移除元素4。
05 链表相关知识以及题目总结_第4张图片
如果使用C,C++编程语言的话,不要忘了还要从内存中删除这两个移除的节点, 清理节点内存之后如图:
05 链表相关知识以及题目总结_第5张图片
当然如果使用java ,python的话就不用手动管理内存了。

203. 移除链表元素

https://leetcode.cn/problems/remove-linked-list-elements/

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //1、额外定义头结点
        ListNode* newhead=new ListNode(0);
        newhead->next=head;
        ListNode* now=newhead;

        while(now->next!=NULL){
            if(now->next->val==val){
                ListNode* tmp=now->next;
                now->next=tmp->next;
                delete tmp;
            }
            else{
                now=now->next;
            }
        }
        head=newhead->next;
        delete newhead;
        return head;
    }

    
    ListNode* removeElements(ListNode* head, int val) {
        //链表删除元素:
        //需要注意的一点就是头元素的删除
        //2、直接在原链表中操作,不额外定义头结点

        //1、对头结点进行判断
        ListNode* tmp;
        while(head!=NULL && head->val==val){
            tmp=head;
            head=head->next;
            delete tmp;
        }
        //2、删除后面的元素
        ListNode* now=head;
        while(now!=NULL && now->next!=NULL){
            if(now->next->val==val){
                tmp=now->next;
                now->next=tmp->next;
                delete tmp;
            }
            else{
                now=now->next;
            }
        }
        return head;
    }
};

其他相关题目

206. 反转链表

反转链表:顾名思义将链表整个翻转。
但是,其实就是将链表的节点连接顺序翻转。
05 链表相关知识以及题目总结_第6张图片

思想:
定义两个指针,pre和cur分别表示前面的一个节点和当前节点
具体实现:将cur的next指针指向pre,从而实现链表翻转
伪代码:
pre=None,cur=head
while cur!=None:
~
return pre

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

05 链表相关知识以及题目总结_第7张图片
注意区分该题目与上面的链表翻转题目

伪代码:
dummyhead=ListNode(0,head)
cur=dummyhead

while cur.next!=None and cur.next.next!=None:
first=cur.next
second=cur.next.next

cur.next=second
first.next=second.next
second.next=first

cur=cur.next.next

return dummyhead.next

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

采用双指针思想,但是这里需要注意的是,双指针的间隔不再是1了。

定义快慢两个指针:slow,fast。如果要删除倒数第n个元素,那么设置两个指针之间的间隔为n,当fast移动到链表末尾时,slow指针的位置即为要删除的元素的前一个位置。


伪代码:
dummy_head=ListNode()
slow,fast=dummy_head,dummy_head

for _ in range(n):
fast=fast.next
while fast!=None:
fast=fast.next
slow=slow.next
slow.next=slow.next.next
return dummy_head.next

面试题 02.07. 链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
05 链表相关知识以及题目总结_第8张图片

思想:
双指针解决,但是关键点在于指针要遍历两遍

定义两个指针分别为,nodeA=headA,nodeB=headB
两个指针同时向右移动,走到链表末尾的None时则从另一个链表的头结点继续向后移动,直到两个指针相遇或者两个指针都走到末尾(None)即为结束。

伪代码:
nodeA=headA
nodeB=headB

while nodeA!=nodeB:
nodeA=nodeA.next if nodeA!=None else headB
nodeB=nodeB.next if nodeB!=None else headA
return nodeA

142. 环形链表 II

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 _如果链表无环,则返回 null。 _
05 链表相关知识以及题目总结_第9张图片
该问题的两个关键点:

  1. 如何判断链表是否有环
  2. 如何求解链表环的起始节点

思想:
采用双指针方法。定义一个快指针一个慢指针。
快指针每次走两个节点,慢指针每次走一个节点。
如果两个指针相遇了,则表示链表有环;
如果链表有环,那么记住一个结果:从相遇点到环起始点的节点数==链表头到环起始点的节点数
(详细推导:
https://programmercarl.com/0142.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8II.html#%E6%80%9D%E8%B7%AF)

slow,fast=head

while fast.next!=None and fast.next.next!=None:
    fast=fast.next.next
    slow=slow.next
    
    if fast==slow:
        meet=slow
        temp=head
        while meet!=head:
            meet=meet.next
            head=head.next
        return head
return None

你可能感兴趣的:(链表,数据结构,java)