目录
今天进入到“链表”算法内容,首先简单复习一下链表的基础内容:
- 链表分类:单链表(自己常用)、双链表、环形链表:关于双链表还有环形链表明白定义,但是不经常使用,常用且熟悉单链表。
- 单链表:链表里面的结点都由两部分组成,一个是存放数据的数据域,另外一个是存放指向下一个结点的指针,特别需要注意:单链表是单向的。
- *虚拟头结点的使用:对于在链表中涉及有可能会对头结点发生改变的操作,例如:删除结点还有插入结点,删除可能会删除头结点,插入也可能会从头结点前插入,那这样的话头结点会发生改变;删除结点和插入结点都要依靠此结点的前一个结点来帮助操作,因此遇到链表的相关题目首先思考一下是否需要使用“虚拟头结点”。
203.移除链表元素
题目链接:https://leetcode.cn/problems/remove-linked-list-elements/
思路:链表涉及到移除,那么需要设置虚拟头结点使过程具有一般化---由于头结点可能符合移除条件,那么就需要另外对头结点进行移除操作;在添加虚拟头结点后,就不需要另外进行判断,头结点与其他结点都一样的判断;虚拟头结点添加在头结点之前。
实现过程:由于移除结点需要借助此结点的前一个结点,那么设置头结点为当前(cur)结点,虚拟头结点为前一个结点(pre),对链表遍历,遍历结束条件是当cur指向为null时,判断是否移除的条件是看cur指向结点值与目标值是否相同,若同进行移除(pre.next=cur.next);若不同首先将pre移动到cur位置(pre=cur),这两种情况无论符合哪种都需要在进行以上操作后继续遍历(cur=cur.next),最后返回新的头结点,无论头结点是否改变都会是虚拟头结点的下一个结点,因此返回dummy.next。
* 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) {
//使用虚拟头结点,使操作不用特殊化
ListNode dummy=new ListNode(0,head);
ListNode pre=dummy;
ListNode cur=head;
while(cur!=null){
if(cur.val!=val){
pre=cur;
}else{
pre.next=cur.next;
}
cur=cur.next;
}
return dummy.next;
}
}
206.反转链表
题目链接:https://leetcode.cn/problems/reverse-linked-list/
第一想法:按照我看此题视角的链表是从左往右指向的,链表最后结点指向null,按题目要求要整个结点反转,那么就要改变指针指向,变成只是知道这个思路,但是具体如何实现是不知道的。
学习随想录后思路:
双指针,此题还可以用递归,但是我较能理解双指针所以选择。
反转链表改变指向,首先设置当前结点为头结点(cur),想到需要将cur指向结点的方向进行反转,从往右指变成往左指。但是改变方向后需要指向什么?所以需要(设一个在cur前的结点给改变方向进行操作,其实想想当cur到了其他结点位置时,改变方向后需要将指针变为往前指,那该如何表示),所以设置一个cur的前结点(pre),然后需要将cur指向pre,指向之后那第一个cur后就断了没有指针指向了,所以还需要设置一个结点temp用来存放cur.next;这个过程的结束条件是cur遍历到为null时,cur会遍历链表中每一个结点然后进行反转;
接下来是关键值的变化,首先要保存cur下一个结点和指向,用temp来存值,然后指向pre,开始疑惑为什么是这个指向?是因为后面pre变化到temp前一个结点,所以如此。然后就是pre和cur的变化,先要处理pre,因为若先处理cur那么cur=cur.next,那么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 cur=head;
ListNode pre=null;
ListNode temp=null;
while(cur!=null){
temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
}
707.设计链表
题目链接:https://leetcode.cn/problems/design-linked-list/
这道题基本包括了链表的几个操作,是非常必要掌握的,其中包括:按照索引对链表进行索引值获取、删除、按索引插入。
思路:添加虚拟头结点;
了解到此题需要对链表结点进行删除和插入,所以添加虚拟头结点,首先除去从头尾插入(可以调用按照所以插入的方法);现在就按照索引对链表进行获取值、删除、插入值,三者共同点都是要按照索引并且是同一链表,所以假设在这条链表添上虚拟头结点,在添上后等于将这条链表整体往后移动了一个,所以在找到索引位置的判断条件也需要结合此来设定,那么需要考虑到这个索引被推后一个;获取值最需要注意的就是索引被推后一个的问题;删除在考虑索引需要遍历到要删结点的上一个结点,然后借助这个结点删除下一结点;在某个索引结点前插入结点,也要注意遍历到需要进行操作结点的前一个结点,然后借助这前一个结点插入新结点。这所有都要注意判断size的大小,还要记得链表具有容错性,因此在插入时,若size小于0也是可以的,会将它按照在等于0的位置操作。
class MyLinkedList {
//定义链表中的属性
int size;
ListNode head;
public MyLinkedList() {
//初始化链表
head=new ListNode(0);
size=0;
}
public int get(int index) {
if(index<0||index>=size){
return -1;
}
ListNode cur=head;
for(int i=0;i<=index;i++){
cur=cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
addAtIndex(0,val);
}
public void addAtTail(int val) {
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
ListNode pre=head;
if(index<0){
index=0;
}
if(index>=size){
return;
}
size++;
for(int i=0;i<index;i++){
pre=pre.next;
}
ListNode newnode=new ListNode(val);
newnode.next=pre.next;
pre.next=newnode;
}
public void deleteAtIndex(int index) {
ListNode pre=head;
if(index>=size||index<0){
return;
}
if(index==0){
head=head.next;
return;
}
size--;//删除结点总长就减小
//因为添加了一个虚拟头结点,所以整个链表整体往后推一个,要注意索引
for(int i=0;i<index;i++){
pre=pre.next;
}
pre.next=pre.next.next;
}
}
// class ListNode{
// int val;
// ListNode next;
// void ListNode(){}
// void ListNode(int val){
// this.val=val;
// }
// void ListNode(int val,ListNode next){
// this.val=val;
// this.next=next;
// }
// }
/**
* 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.反转链表,此题的思路是理解的,但是在实现过程还是对于指向的代码编写不流畅,这方面的能力有待加强。