目录
203.移除链表元素 (不使用虚拟节点)
建议: 本题最关键是要理解 虚拟头结点的使用技巧,这个对链表题目很重要。
题目链接/文章讲解/视频讲解:代码随想录
解题思路:
不使用虚拟节点来做,主要分两种情况去讨论,如下所示分析时一定先判断对哪些节点操作,首先要保证节点不能是空指针才能去对节点操作,再去判断节点的值域是否包含目标值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; }
* }
*/
class Solution {
//不使用虚拟节点来做
public ListNode removeElements(ListNode head, int val) {
//第一种情况:如果头节点中的值域包含target目标值val
//由于是对头节点操作,所以要保证head不能是空指针,否则对空指针操作就会报错
while(head != null && head.val == val){
head = head.next; //如果链表中就一个节点,也就是头节点还包含目标值val,则直接返回null值了
}
//第二种情况:如果头节点中的值域不包含target目标值val,所以head节点需要设置节点保存,第二种情况头节点就是head,因此head节点要保持不动;如果不设置节点变量保存,由于需要不断的遍历链表找到包含val值的节点并删除,所以head也是在不断向后移动的
由于是对current节点,以及current节点指向的next域操作,所以要保证current和current.next不能是空指针,否则对空指针操作就会报错
ListNode current = head;
while(current != null && current.next != null){
if(current.next.val == val){
current.next = current.next.next;
}else{
current = current.next;
}
}
return head;
}
}
206.反转链表(双指针)
建议先看我的视频讲解,视频讲解中对 反转链表需要注意的点讲的很清晰了,看完之后大家的疑惑基本都解决了。
题目链接/文章讲解/视频讲解:代码随想录
题解思路:
使用双指针,定义一个pre指针和current指针(使得head节点保持不动)进行同时移动,只需要将pre节点跟着current节点不断的往右移动,同时将指向当前节点的pre赋值给current的next域,就可以实现链表的反转,所以一开始需要将pre定义为空指针,在第一次移动之前current的next域指向的就是pre的null值,然后开始移动,注意在每次移动的同时赋值的顺序,需要先用一个临时变量保存好current指向的next域,否则当pre赋值给current.next的时候,current没法再往后移动了,因为current此时已经写死了,只能是next域指向pre值的current的节点了,所以可以先把temp = current.next保存好,然后再把temp值赋给current这样就可以移动current了,同时pre也要向后移动,则直接把当下的current赋值给pre即可!!!(这里看卡哥视频很清楚讲解的其实!!!
/**
* 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 reverseList(ListNode head) {
// ListNode head = new ListNode();
ListNode pre = null;
ListNode current = head;
while(current != null){ //当current移动到尾部节点,此时next域指向null值时,说明已经到达链表结尾了
ListNode temp = current.next;
current.next = pre; //实现next域反向赋值的操作
pre = current; //pre跟着向后移动一位
current = temp; //current也要跟着往后移动一位
}
return pre;
}
}
707.设计链表(使用虚拟节点方法)
建议: 这是一道考察 链表综合操作的题目,不算容易,可以练一练 使用虚拟头结点
题目链接/文章讲解/视频讲解:代码随想录
题解思路:
1、必踩的坑之一:初始化链表个数,第0个节点初始化的时候还没出生呢,因此初始化链表的个数为0,初始化的时候只有一个dummyNode虚拟节点
2、必踩的坑之二:链表中的头节点的下标视为0,使用一个虚拟节点指向头节点,真实链表中的结构:头结点(节点0)---->节点1--->节点2---->节点3........---->节点n,总共 n+ 1个节点,因此链表中的节点下标是从0开始,那么超过索引范围就会报错,即n < 0 || n > size - 1 时索引无效的!!!
- 难度虽然不大,但是坑就是太多了,一踩一个准!!!方法思路主要看注释,写的很清楚了,还需要关注的点有两个:
1、如何找到第n-1个节点操作,这个方法卡哥给的很棒,对于边界问题,可以代入第0个节点去试试!!!
while(index > 0){
current = current.next;
index--;
} //循环结束后current指向的就是第n-1个节点,只要记住这种方法就好,自己肯定没能力想出来
2、如何遍历到链表的尾部指针,这个方法卡哥给的也很棒!!!
`while (current.next != null) {
current = current.next;
} //循环结束后一定是next域指向null值的尾部节点,这里while循环是逆向思维,一定要学会多维度思考`
//定义单链表
class ListNode {
int val;
ListNode next;
//无参构造器
ListNode(){
}
//有参构造器
ListNode(int val) {
this.val=val;
}
}
class MyLinkedList {
int size = 0; //初始化链表个数,第0个节点初始化的时候还没出生呢,因此初始化链表的个数为0,初始化的时候只有一个dummyNode虚拟节点
ListNode dummyNode = new ListNode(); //使用虚拟节点,此时虚拟节点模拟就是头节点,来替代真实的头节点的方法对链表进行增删改操作,
public MyLinkedList() {
}
/*
对第n个节点操作时需要明确一下概念:
1、链表中的头节点的下标视为0,使用一个虚拟节点指向头节点,真实链表中的结构:头结点(节点0)---->节点1--->节点2---->节点3........---->节点n,总共 n+ 1个节点
2、第0个节点初始化的时候还没出生呢,因此初始化链表的个数为0,初始化的时候只有一个dummyNode虚拟节点
2、链表中的节点下标是从0开始,那么超过索引范围就会报错,即n < 0 || n > size - 1 时索引无效的!!!
3、对节点操作的步骤一定是先找到合适的节点,然后才能谈对节点的操作,思路不能乱!!!
4、一定是要找到第n - 1处的节点才能对第n个节点进行操作next域,如果找到的是第n个节点,那么只能对第n个节点的next域进行操作,这就不是对第n个节点操作了,而是对第n + 1个节点操作next域了!!!
5、添加节点操作后一定要将size加1,删除节点操作后一定要将size减1,这点很容易被忽视!!!!
*/
/*
***操作,你个临时变量current定义在类里面当属性,就离谱还是个类似零作用的临时变量,你几个意思啊,每次加载不同方法时不得重新归零
*/
//一、获取链表中下标为 index 的节点的值,下标为0的节点就是第0个节点,下标为1的节点就是第1个节点
public int get(int index) {
ListNode current = dummyNode.next; //踩过的坑!!!!!!!!!!在获取指定Index位置的值时,current一定是从dummyNode.next开始遍历的!!!!
if(index < 0 || index > size - 1 ){ //踩过的坑!!!!!!!!!!!!!!index > size - 1,这里一定是size - 1,因为初始化的时候size = 0,添加第一个一个节点的时候才下标为0的head节点(此时size = 1且下标为0),依次往后类推:添加第二个节点下标为1(此时size = 2,且下标依次为0、1)......
return -1;
}
//n > 0值时,循环执行条件为真,执行方法体,当n == 0时,不进入循环体。假设对第0个进行操作,则只需要对虚拟节点dummyNode操作即可,因此该while循环能够找到第n - 1个节点的位置
while(index > 0){
current = current.next;
index--;
} //循环结束后current指向的就是第n-1个节点,只要记住这种方法就好,自己肯定没能力想出来
return current.val;
}
//二、将一个值为 val 的节点插入到链表中第一个元素之前。根据题意对第1个节点操作,只需要对头节点操作即可,注意函数内部节点赋值的顺序是重点!!(在插入完成后,新节点会成为链表的第一个节点,这是水到渠成的事不用管)
public void addAtHead(int val) {
ListNode newNode = new ListNode(val);
newNode.next = dummyNode.next;
dummyNode.next = newNode;
size++;
}
//三、将一个值为 val 的节点追加到链表中作为链表的最后一个元素。根据题意对尾部头节点操作,只需要找到尾部节点位置再赋值即可
public void addAtTail(int val) {
ListNode current = dummyNode;
ListNode newNode = new ListNode(val);
while (current.next != null) {
current = current.next;
} //循环结束后一定是next域指向null值的尾部节点,这里while循环是逆向思维,一定要学会多维度思考
current.next = newNode;
size++;
}
//四、将一个值为 val 的节点插入到链表中下标为 index 的节点之前。(在第n个节点插入节点,还是需要找到第n - 1个节点处的位置对其进行正确赋值操作即可)
//如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将不会插入到链表中。
public void addAtIndex(int index, int val) {
ListNode current = dummyNode;
ListNode newNode = new ListNode(val);
if(index > size ){
return;
}
while(index > 0){
current = current.next;
index--;
} //循环体结束后,定位到的一定是第n - 1处的节点,然后对第n - 1处的节点操作即可
newNode.next = current.next;
current.next = newNode;
size++; //注意添加节点后一定要将其size加1操作
}
//五、如果下标有效,则删除链表中下标为 index 的节点。
public void deleteAtIndex(int index) {
ListNode current = dummyNode;
if(index < 0 || index > size - 1 ){ /!!!!!!!!!!!!!!!!!!!!!!!!!
return;
}
while(index > 0){
current = current.next;
index--;
}//循环体结束后一定是第n - 1处的节点位置
current.next = current.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);
*/