今日前置知识
链表理论知识
今日主要题目
主要学习网址
做题思路与具体代码
题目一:203.移除链表元素
做题思路一
思路一:不设置虚拟头结点,分两种情况讨论:
1.头结点就是要移除的元素
2.头结点不是要移除的元素
第一种情况:如果头结点是我们要移除的元素,就移除,并继续往下遍历,知道将头结点放在一个不是需要被移除的元素上
第二种情况:此时头结点已经不是我们要移除的元素,初始化移动指针cur向后走,如果cur的下一个节点不为空且下一个节点值是我们要移除的元素,则进行删除,并遍历到能够将cur放在一个不是需要被移除的元素上,记得每次循环将cur向后移动
3.结果返回头结点也就是整个链表
(记住这里两种情况每次循环结束时都需要将head以及cur放在一个不是需要被移除的元素上)
具体代码
/**
* 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 {
// 第一种方法:不设置虚拟头结点,分两种情况
// 1.头结点就是要移除的元素
// 2.移除的元素不是头结点
public ListNode removeElements(ListNode head, int val) {
// 1.移除掉满足条件的头结点
while(head!=null&&head.val==val){
head=head.next;
}
// 2.移除掉满足条件的但不是头结点的节点
// 定义移动指针cur指向head,因为是为了删除元素
// 我们要删的永远是cur.next,用的是cur.next=cur.next.next
ListNode cur=head;
// 遍历整个链表
while(cur!=null){
// 这里找到一个要继续往后找,一次性全部删除
// 这里用while的原因是我们要讲cur放在一个确定不需要被删除的地方,因为cur所在位置的元素不能被删除,所以继 续往前删除可能正确的元素
while(cur.next!=null&&cur.next.val==val){
// 找到目标点就替换
cur.next=cur.next.next;
}
// 没找到,cur就向前进一步
cur=cur.next;
}
return head;
}
}
做题思路二
思路二:采用虚拟头结点,新设立一个虚拟头结点,这样的话就不用分成两种情况,即头结点和头结点后面的都可以用同样的规则去删除元素
1.初始化虚拟头结点放在头结点前面
2.初始化移动指针指向虚拟头结点,因为移动指针需要放在一个不是需要被移除的元素上
(侧面说明前面需要前面需要先让head放在一个不是需要被移除的元素上再把移动指针放在上面的原因)
3.剩下的就和思路一一样了,即:
移动指针cur向后走,如果不是仍有下一个节点且下一个节点值是我们要移除的元素,则进行删除,并遍历到能够将cur放在一个不是需要被移除的元素上,记得每次循环将cur向后移动
4.返回头结点也就是虚拟头结点的下一节点也就是整个链表
具体代码
/**
* 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 dumhead=new ListNode(-1,head);
//初始化移动指针,指向虚拟头结点
ListNode cur=dumhead;
//循环条件是当前cur没走到终点
while(cur!=null){
// cur接下来还有值且cur接下来的值是我们想要清除的
while(cur.next!=null&&cur.next.val==val){
cur.next=cur.next.next;
}
// cur往前走一步
cur=cur.next;
}
return dumhead.next;
}
}
题目二:707.设计链表
做题思路+具体代码
本题分为多个部分:
1.初始化链表
2.得到某个位置的元素
3.在头部添加
4.在尾部添加
5.在任意位置添加
6.删除某个位置元素
分别讲解一下思路
1.首先初始化链表,需要初始化链表的长度int size=0
同时初始化头结点
具体代码
// 直接使用虚拟头结点的方法来解答
// 没有头结点,直接就是dummyhead
// 初始化链表的大小
int size;
// 初始化虚拟头结点
ListNode head=new ListNode(0);
// 初始化链表
public MyLinkedList() {
size=0;
head=new ListNode(0);
}
2.得到某个位置的元素
首先判断一下下标合不合法,不合法返回-1,用一个移动指针cur用来遍历,记得采用for循环,因为是得到某个地方元素,所以这个循环的条件需要cur移动到这个位置元素所在的位置,也就是循环运行到i<=index
最后再返回cur所在位置的值
//得到某个位置的元素
public int get(int index) {
// 判断此时的index是否合法,不合法返回-1
if(index<0||index>=size){
return -1;
}
// 初始化移动指针用来遍历
// 这个cur一开始所在的位置固定在head位置,移动到哪里取决于这个方法是什么
// 现在是要得要某个位置的元素,要移动到该元素上
// 所以循环条件是i<=index
ListNode cur=head;
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur.val;
}
3.在头部添加
4.在尾部添加
统一代码实现具体在在任意位置添加代码中,注意传入参数的不同就行
//在头部添加
public void addAtHead(int val) {
addAtIndex(0,val);
}
//在尾部添加
public void addAtTail(int val) {
addAtIndex(size,val);
}
5.在任意位置添加
注意三点:
1.在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
2. 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
3. 如果 index 大于链表的长度,则返回空
首先要注意以上的事项,然后在一开始做判断
然后初始化要插入的节点,移动到要插入的位置的签名,也就是循环运行到i<index,然后采用这个顺序进行添加,注意这个顺序,因为newNode.next=cur.next cur.next=newNode 因为要是顺序反了,链表就无法成功连接
(在写这些代码时最好画图来展示一下哪条线先连,哪条后连,因为每连一条线就意味着有的线要先断开)
记得添加完元素要size++
具体代码
//在任意位置添加
public void addAtIndex(int index, int val) {
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
if(index>size){
return;
}
if (index < 0) {
index = 0;
}
//初始化新插入的节点
ListNode newNode=new ListNode(val);
// 初始化移动指针用来遍历
// 这个cur一开始所在的位置固定在head位置,移动到哪里取决于这个方法是什么
// 现在是要添加某个元素在一定位置上,要移动到位置的前面
// 所以循环条件是i<index
ListNode cur=head;
for(int i=0;i<index;i++){
// 移动到要插入的位置
cur=cur.next;
}
//记得要按这个顺序来进行添加!!!不可更改
newNode.next=cur.next;
cur.next=newNode;
//每添加一次,长度+1
size++;
}
6.删除某个位置元素
这部分一开始也要先判断index的合法性
然后移动到要删除元素的前面,类似于添加,比添加难
记得删除完元素要size--
(个人小总结:查询,移动指针需要到达要求位置上,增加删除需要移动指针到达要去位置前,但是这三者移动指针的起步都是一样的,都是已经被排除是目标元素的位置,本题是虚拟头结点所在位置,可以结合前面题目再进行总结)
具体代码
//删除某个位置元素
public void deleteAtIndex(int index) {
// 判断此时的index是否合法
if(index<0||index>size-1){
return;
}
// 初始化移动指针用来遍历
// 这个cur一开始所在的位置固定在head位置,移动到哪里取决于这个方法是什么
// 现在是要删除某个位置上的某个元素,要移动到该位置的前面
// 所以循环条件是i<index
ListNode cur=head;
for(int i=0;i<index;i++){
//移动到要删除元素的前面
cur=cur.next;
}
cur.next=cur.next.next;
//每删除一次长度减一
size--;
}
题目三 206.反转链表
做题思路一
使用双指针解法
1.初始化前后两个指针,前指针pre指向null,后指针cur指向head
2.两个指针分别向后走知道走到后指针指向null
3.每走一步,就进行翻转,但是要设定要临时指针temp,先存储cur.next,再进行翻转cur.next=pre
就是把链表给反向了
4.然后前后指针分别向后走,因为反转了线也断了没法找到cur.next了,所以前面锁存储的临时指针就可以派上用场了,所以前指针移动pre=cur,后指针移动cur=temp
5.最后返回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 pre=null;
ListNode cur=head;
// 两个指针同时向后移动,直到后指针运行到null地方退出循环
while(cur!=null){
// 这里需要设定个暂时值,因为后面cur.next=pre一翻转原先的连接就变了
// 这里记得要提前存好,顺序要确定
ListNode temp=cur.next;
// 翻转
cur.next=pre;
// 前指针向后移动
pre=cur;
// 后指针向后移动
cur=temp;
}
//返回翻转后链表的头结点
return pre;
}
}
做题思路二
本题采用递归简化思路一的双指针写法,也是双指针,只是在用递归简化了前后指针的移动
直接贴代码,这里注意每次递归的参数传入pre传入cur,cur传入temp,实现了下一次递归的初始值已经进行了移动
具体代码
/**
* 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) {
//传入左右指针初始值
return reverse(null,head);
}
private ListNode reverse(ListNode pre, ListNode cur) {
if (cur == null) {
return pre;
}
// 这里需要设定个暂时值,因为后面cur.next=pre一翻转原先的连接就变了
// 这里记得要提前存好,顺序要确定
ListNode temp=cur.next;
// 翻转
cur.next=pre;
// 实行递归
return reverse(cur, temp);
}
}