首先让我们对链表进行一系列的说明:
链表
定义:
链表是一种通过指针串联在一起的线性结构,每个结点由两部分组成,数据域和指针域,指针域负责存放指向下一个结点的指针,最后一个结点的指针域指向null
类型:
- 单链表
- 双链表:每个节点有两个指针域,分别指向上一个和下一个
- 循环链表:首尾相连的链表,可用来解决约瑟夫环问题
存储方式:
链表在内存中不是连续分布的,而是散乱分布在内存中的某地址上
链表的定义方式:
//单链表:
struct ListNode{
int val;
ListNode *next;
ListNode(int x): val(x),next(NULL){}//默认构造函数
链表的操作
- 删除节点
- 添加节点
203.移除链表元素
题目链接:203. 移除链表元素 - 力扣(LeetCode)
解题思路:
移除链表中的元素是一个比较简单的问题,一个节点是否需要删除是通过对当前节点的下一个节点是否需要删除判定的,具体则只需要让当前的指针域指向下一个指针域的下一个指针域即可
当然在实际操作时,由于我们需要考虑到头结点同样需要被删除的情况,因此我们首先创建一个虚节点,将头结点接在其后面,这样做能够更有利于我们的解题。
具体ac代码如下:
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) {
//链表的删除是一个很简单的操作,找到对应值,将其前面的链表指向后方即可
//首先是异常判断
if(head == null){
return null;
}
//创建虚节点,使原来的链表位于其后
ListNode vir = new ListNode();
vir.next = head;
ListNode tmp = new ListNode();//创建临时的当前变量
tmp = vir;
while(tmp.next != null && tmp != null){
if(tmp.next.val == val){
tmp.next = tmp.next.next;
}
else{
tmp = tmp.next;
}
}
return vir.next;
}
}
思路:本题中我们同样使用一个虚拟头节点,同时需要我们自定义好节点的相关内容,只需要考虑好size 以及 index 的具体位置就能够较为轻松的解决此道题。
遇到的问题:
讲实话这道题卡了我很久,总结下来主要其实就是index和我们自定义的链表中的size不符合的问题,即当前链表的节点个数与index的相关遍历问题,我觉得首要的应当是在加入一个新的节点时,在进行完异常判断后首先将size++后在进行之后的相关操作处理。
ac代码如下:class ListNode{
int val;
ListNode next;
ListNode(){}
ListNode(int val){
this.val = val;
this.next = null;
}
}
class MyLinkedList {
int size;
ListNode dummyhead;//定义虚拟头节点
public MyLinkedList() {
size = 0;
dummyhead = new ListNode(0){};
}
public int get(int index) {
if(index < 0 || index >= size) return -1;
else{
ListNode tmp = dummyhead;
for(int i = 0; i <= index;i++){
tmp = tmp.next;
}
return tmp.val;
}
}
public void addAtHead(int val) {
size++;
ListNode tmp = new ListNode(val);
tmp.next = dummyhead.next;
dummyhead.next = tmp;
}
public void addAtTail(int val) {
size++;
ListNode toadd = new ListNode(val);
ListNode tmp = dummyhead;
while(tmp.next != null) tmp = tmp.next;
tmp.next = toadd;
}
public void addAtIndex(int index, int val) {
if(index < 0){
addAtHead(val);
}
else if(index == size){
addAtTail(val);
}
else if(index > size){
return;
}
else{
size++;
ListNode tmp = dummyhead;
for(int i = 0; i < index;i++){
tmp = tmp.next;
}
ListNode Second = tmp.next;
ListNode toadd = new ListNode(val);
tmp.next = toadd;
toadd.next = Second;
}
}
public void deleteAtIndex(int index) {
if(index < 0 || index >= size){
return;
}
else{
size--;
ListNode tmp = dummyhead;
for(int i = 0; i < index;i++){
tmp = tmp.next;
}
tmp.next = tmp.next.next;
}
}
}
初见这道题的思路:
就是通过简单的两次遍历,第一次遍历拿到链表中所有的值,第二次遍历再赋值就好,当然这样做不可避免的产生了空间的浪费,ac代码如下:
//简单的遍历链表一次后,将值全部保存好,在下次遍历时给链表重新赋值
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null){
return null;
}
ListNode tmp = head;
Stack<Integer> stack = new Stack<Integer>();
while(tmp.next != null){
stack.push(tmp.val);
tmp = tmp.next;
}
stack.push(tmp.val);
tmp = head;
while(tmp.next != null){
tmp.val = stack.pop();
tmp = tmp.next;
}
tmp.val = stack.pop();
return head;
}
}
思路:使用栈对数据进行单独的保存不可避免的产生了空间资源的浪费,然而我们有更好的方法来对此问题进行处理,即之前一直使用的双指针法
具体的解题步骤就是利用两个指针,一前一后的逐步改变next指针的朝向,最终返回最后的指针pre即可,具体ac代码如下:
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null) return null;
ListNode cur = head;
ListNode tmp = new ListNode();
ListNode pre = null;
while (cur != null){
tmp = cur;
cur = cur.next;//将顺着走的下一个结点保存下来
tmp.next = pre;
pre = tmp;
}
return pre;
}
}