参考教程:
手把手带你学会操作链表 | LeetCode:203.移除链表元素_哔哩哔哩_bilibili
帮你把链表操作学个通透!LeetCode:707.设计链表_哔哩哔哩_bilibili
帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法_哔哩哔哩_bilibili
完成情况:自我可实现
203.移除链表元素
思路:简单的链表结果的删除操作的练习。但是其中可以引起重视的思想有:
- 若不增加首节点,而是直接操作头节点的删除——头节点的删除是与其他节点删除的情况不一样,需要单独的写一段逻辑来进行处理。这样的好处在于删除后续节点时逻辑清晰,且再也不用考虑头节点会是待删除的元素。
- 增加首节点后(也即虚拟节点)那么所有节点的删除操作是一样的。但是需要注意的是需要再定义一个指针(引用)来遍历含有首节点的链表☆。
代码实现:
// 时间复杂度O(n),空间复杂度O(1),定义了虚拟的头结点
class Solution {
public ListNode removeElements(ListNode head, int val) {
//创建一个首节点
ListNode fast=new ListNode(val-1);
fast.next=head;
ListNode res=fast;
// 关于这道题,我原来的解法是让fast指针在往后走动遍历,这里是错误的
// 原因在于fast是首节点,保存的就是整个链表的全部信息,如果fast指针next位置产生变化,则最后是找不到链表的可行head在哪里;其实移动也可以,只要head不为null,head.next不为null,fast完全可以自己移动
while(res.next!=null){
if(res.next.val==val){
res.next=res.next.next;
}else{
res=res.next;
}
}
return fast.next;
}
}
707.设计链表
思路:设计题,修修补补停不下来 ┭┮﹏┭┮
需要记忆的点有:① 链表可插入节点的范围是[0,length];② 链表可删除节点的范围是[0,length-1];
代码实现:
class MyLinkedList {
public Node head;
public int length;
// 建立链表,那么无需做任何其他的事情
public MyLinkedList() {
this.head = null;
this.length = 0;
}
public int get(int index) {
if(index>=0 && index<this.length){
int count = 0;
Node p = this.head;
while(p != null){
if(count == index)
return p.val;
p=p.next;
count++;
}
}
return -1;
}
public void addAtHead(int val) {
Node node = new Node(); //创建一个新的节点
node.val = val;
node.next = this.head;
this.head = node;
this.length++;
}
public void addAtTail(int val) {
// 当前链表内没有节点
if(this.length == 0){
addAtHead(val);
return;
}
Node p = this.head;
while(p != null && p.next != null){
p = p.next;
}
// p所停止的位置是最后一个节点
Node node = new Node();
node.val = val;
node.next = null;
p.next = node; // 尾部插入
this.length++;
}
public void addAtIndex(int index, int val) {
if(index>=0 && index<=this.length){
if(this.length == 0 || this.head==null){
addAtHead(val);
return;
}
// 先构建待添加的节点
Node node = new Node();
node.val = val;
node.next = null;
// 开始插入
int count = 0;
Node p = this.head; // 指向头结点,准备开始遍历
Node q = this.head;
while(p!=null){
if(count == index){
if(count == 0){
node.next = this.head;
this.head = node;
this.length++;
return;
}
else{
node.next = q.next;
q.next = node;
this.length++;
return;
}
} // end if
q = p;
p = p.next;
count++;
// 在末尾添加节点
if(index == this.length && count == this.length){
q.next = node;
this.length++;
return;
}
} // end while
}
return;
}
public void deleteAtIndex(int index) {
if(index>=0 && index<this.length){
Node q = this.head;
int count = 0;
while(count < this.length){
if(count == index){
if(index == 0){
this.head = this.head.next;
this.length--;;
return;
}
else{
q.next = q.next.next;
this.length--;
return;
}
}
if(count == 0)
count++;
else{
count++;
q = q.next;
}
} // end while
} // end if
return;
}
}
class Node{
public int val;
public Node next;
public Node(){
this.next = null;
};
}
/**
* 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);
*/
206.反转链表
思路:设计一个新的虚拟节点,然后通过链表的指针的优势,遍历原链表时将每一个可行的节点采用“头插法”插入新的链表,从而实现翻转;
代码实现:
/**
* 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; }
* }
*/
// 时间复杂度O(n),空间复杂度O(1),建立了虚拟首节点
class Solution {
public ListNode reverseList(ListNode head) {
ListNode tail = head; // 标记翻转之后的列表的尾结点是原来链表的头节点
ListNode res = new ListNode();
res.next = null;
ListNode p = head;
// 两个条件的判断可以有效防止head是null
while(p!=null){
// 当前的p是一个可行的节点
ListNode q = p.next; // 保存原来链表的下一个节点的地址
p.next = res.next; // 实现逆转
res.next = p; // 将新的可行节点放到开头去
p = q; // 继续遍历原来的链表
}
// p已经移动到最后一个节点
return res.next;
}
}