链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链接的入口节点称为链表的头结点也就是head。
如图所示:
单链表中的指针域只能指向当前节点的下一个节点。
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。
如图所示:
循环链表,顾名思义,就是链表首尾相连。
循环链表可以用来解决约瑟夫环问题。
// 使用结构体定义
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;
前面介绍了链表节点的定义,对于链表类的定义如下:
c++实现:
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。
如果使用C,C++编程语言的话,不要忘了还要从内存中删除这两个移除的节点, 清理节点内存之后如图:
当然如果使用java ,python的话就不用手动管理内存了。
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;
}
};
反转链表:顾名思义将链表整个翻转。
但是,其实就是将链表的节点连接顺序翻转。
思想:
定义两个指针,pre和cur分别表示前面的一个节点和当前节点
具体实现:将cur的next指针指向pre,从而实现链表翻转
伪代码:
pre=None,cur=head
while cur!=None:
~
return pre
伪代码:
dummyhead=ListNode(0,head)
cur=dummyheadwhile cur.next!=None and cur.next.next!=None:
first=cur.next
second=cur.next.nextcur.next=second
first.next=second.next
second.next=firstcur=cur.next.next
return dummyhead.next
采用双指针思想,但是这里需要注意的是,双指针的间隔不再是1了。
定义快慢两个指针:slow,fast。如果要删除倒数第n个元素,那么设置两个指针之间的间隔为n,当fast移动到链表末尾时,slow指针的位置即为要删除的元素的前一个位置。
伪代码:
dummy_head=ListNode()
slow,fast=dummy_head,dummy_headfor _ in range(n):
fast=fast.next
while fast!=None:
fast=fast.next
slow=slow.next
slow.next=slow.next.next
return dummy_head.next
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
思想:
双指针解决,但是关键点在于指针要遍历两遍定义两个指针分别为,nodeA=headA,nodeB=headB
两个指针同时向右移动,走到链表末尾的None时则从另一个链表的头结点继续向后移动,直到两个指针相遇或者两个指针都走到末尾(None)即为结束。伪代码:
nodeA=headA
nodeB=headBwhile nodeA!=nodeB:
nodeA=nodeA.next if nodeA!=None else headB
nodeB=nodeB.next if nodeB!=None else headA
return nodeA
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 _如果链表无环,则返回 null。 _
该问题的两个关键点:
思想:
采用双指针方法。定义一个快指针一个慢指针。
快指针每次走两个节点,慢指针每次走一个节点。
如果两个指针相遇了,则表示链表有环;
如果链表有环,那么记住一个结果:从相遇点到环起始点的节点数==链表头到环起始点的节点数
(详细推导:
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