1. 构建链表
1. 要点
- 构造链表的三种方式:无参、单参数、双参数
- 需要明白java中引用的形式,了解cur = head,cur改变,head也改变,这种引用的概念,类似文件的快捷方式,通过引用我们就可以找到那个对象,通过引用,我们来遍历、修改链表
- 构造的方式有点像递归,可以联想到可以用递归创建链表
2. 代码
public class ListNode{
int val;
ListNode next;对象 :引用下一个节点对象。在Java中没有指针的概念,Java中的引用和C语言的指针类似
public ListNode(){}
pulic ListNode(int val){
this.val = val;
}
public ListNode(int val,ListNode next){
this.val = val;
this.next = next;
}
}
2. 遍历链表:移除节点
链表的遍历不同于数组
- 初始条件不是i=0,而是curNode = head(如有虚拟头节点dummyHead,则是curNode=dummy.next)
- 终止条件不是i<nums.length,而是curNode != null
- 递增条件不是i++,而是curNode=curNode.next
明白这些,使用for或者while遍历链表均可
1. 题目:
- 移除链表元素
https://leetcode.cn/problems/remove-linked-list-elements/
2. 初提结果及问题
问题在虚拟头节点使用,链表增删改查操作真正对象的理解以及链表遍历的细节三个方面
- 注意虚拟头节点对于链表的增加、删除及插入的帮助,以及其定义、返回
- 对虚拟节点的定义方式不熟悉 ListNode dummy = new ListNode(-1,head)
- 如果创建虚拟节点,返回值应该是return dummy.next,而不是head,因为head可能会被删除,插入等,dummy.next才是真正头节点
- 程序报错cannot read field next because local4 is null,如果if满足条件,cur.next会更新到下下个(下面图中返回值也错误)
- 删除节点本质是对前一个节点操作,因此终止条件、递增对象都应该是cur.next(单链表中这么操作,双链表不用)
3.设计链表
1. 题目
- 设计链表
https://leetcode.cn/problems/design-linked-list/description/
这道题分为基于index的增删查以及最前后插入。由于插入节点是对插入位置前一个节点操作,因此建立在最后一个插入不需要单独操作,对最前一个建立虚拟头节点即可。
根据index增删查
- 获取链表第index个节点的数值
- 在链表第index个节点前面插入一个节点
- 删除链表的第index个节点
头尾两个插入可以合并在根据index增里面
- 在链表的最前面插入一个节点
- 在链表的最后面插入一个节点
2. 初次提交结果与问题
这道题几个方法的结果都是依赖的,因此一个写错就可能报错 next属性不存在等,出现节点为空的情况
- 难点总结为:
- 学会通过嵌套类构建新类;
- 加强理解单链表操作都是根据其前驱节点操作(延申到虚拟头节点的使用、遍历条件、下标的初始值);
- 仔细读题判断不同操作下输入index的实际范围、合法范围及非法操作返回值
- 具体失误的问题是:
- 不知道要在MyLinkedList类里面构造链表后,再在MyLinkedList里面初始化一个链表对象(既一个节点都没有(或者只有一个虚拟节点)的对象)
- 在构造函数中赋值和直接给成员变量赋值的区别(https://blog.csdn.net/qq_43019319/article/details/107196434)
- 需要注意的是,删和查都是需要有链表长度的判断,因此需要加入size属性
- 在链表的最前面、最后面插入一个节点,都可以转为对于index定值的操作
- 要学会看输入值的范围,比如这里index恒大于等于0,因此不用写小于0的判断
- 添加到末尾是 直接加到index 为size的(实际下标大1)
- 注意题目中描述,查的index可输入范围和写的index范围不一样,写的index范围是可以比实际下标大1
- curIndex忘记要++
- 由于很容易出错,后续改了半天才开始用输出来debug,方法如下,输出在dele和add里面写,注意写法。这样可以很清楚的看出目前链表里面有哪些值,排查是那一步写错了
public void addAtIndex(int index, int val) {
if(index > size){return;}
ListNode cur = dummy; //循环从虚拟头节点开始,索引从-1开始,
int curIndex = -1;
while(cur.next != null && (curIndex+1) != index){
cur = cur.next;
curIndex ++;
}
ListNode addNode = new ListNode(val,cur.next);
cur.next = addNode;
size ++;
//测试当前链表值的方法
ListNode test = dummy.next;
while(test != null){
System.out.print(test.val +" ");
test=test.next;
}
System.out.println();
}
3. 题解
思路为:分析拆解归类问题→根据链表特点编写普适遍历条件→调整一些边界条件的细节,合理使用print来debug
//单链表写法
class MyLinkedList {
// 构造链表
class ListNode{
int val;
ListNode next;
public ListNode(){}
public ListNode(int val){ this.val = val;}
public ListNode(int val,ListNode next){
this.val = val;
this.next = next;
}
}
ListNode dummy;
int size;
public MyLinkedList() {
dummy = new ListNode(-1,null);
size = 0;
}
public void addAtHead(int val) {addAtIndex(0, val);}
public void addAtTail(int val) {addAtIndex(size, val);}
//增加的本质是把"这个位置原节点"作为增加节点的下一个,然后把原来节点前一个指向增加节点
public void addAtIndex(int index, int val) {
if(index > size){return;}
ListNode cur = dummy; //循环从虚拟头节点开始,索引从-1开始,
int curIndex = -1;
while(cur.next != null && (curIndex+1) != index){
cur = cur.next;
curIndex ++;
}
ListNode addNode = new ListNode(val,cur.next);
cur.next = addNode;
size ++;
//测试当前链表值的方法
// ListNode test = dummy.next;
// while(test != null){
// System.out.print(test.val +" ");
// test=test.next;
// }
// System.out.println();
}
public int get(int index) {
if(index >= size){return -1;}
ListNode cur = dummy;
int curIndex = -1;
while(cur.next != null && (curIndex + 1) != index){
cur = cur.next;
curIndex ++;
}
return cur.next.val;
}
public void deleteAtIndex(int index) {
if(index >= size){return;}
ListNode cur = dummy;
int curIndex= -1;
while(cur.next != null && (curIndex + 1) != index){
cur = cur.next;
curIndex ++;
}
cur.next = cur.next.next;
size --;
//测试当前链表值的方法
// ListNode test = dummy.next;
// while(test != null){
// System.out.print(test.val +" ");
// test=test.next;
// }
// System.out.println();
}
/**
* 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);
*/
3. 链表断链处理的例子:反转链表
这题可以深入理解链表中引用(指针)的概念和用法。
在做完两两交换节点后,更加明白这道题与其区别:一个是改变方向后断链了(用双指针+临时指针处理),一个是改变方向后没有断链(单指针即可)
1. 题目:
- 反转链表
https://leetcode.cn/problems/reverse-linked-list/
2. 初提结果及问题
- 由于更新循环时,需要pre和cur都移动一位:
- pre根据cur当前值移动,
- cur因为.next已经被更新了,需要创建的临时变量tmp来存当前节点的下一个用于cur的移动进入下一个循环;
- 没有注意最后一个前驱节点才是头节点 应该return pre;
3.题解
/**
* 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 pre = null;
ListNode cur = head;
while(cur != null){
//1. 存下一个
ListNode temp = cur.next;
//2. 指针翻转(翻转后得到的一个我们想要的cur)
cur.next = pre;
// 3. 更新
pre= cur;
cur = temp;
}
return pre;
}
}