内容:
- 链表理论基础
- 移除链表元素(203)
- 设计链表(707)
- 翻转链表(206)
1.链表理论基础
-
链表的类型
- 单链表
- 双链表
- 循环链表
-
链表的存储方式
- 链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理
- 链表的操作
- 增删改查
- 性能分析
2.移除链表元素
2.1 思路分析
一开始我的思路很简单,就是单纯的通过一个辅助指针来遍历链表,找到待删除的链表元素的前一个节点,让该节点的next域指向它的next的next,但很显然我忽略了如果删除的点为第一个节点的情况;
题解有两种方式 ,一是直接使用原来的链表来进行删除操作,只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。二是设置一个虚拟头结点在进行删除操作,这样也能轻易解决头结点的问题。
2.2 代码实现
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) {
//不使用虚拟头节点的方法
//处理头结点如果是要移除的数据的情况
while(head != null && head.val == val){
head = head.next;
}
ListNode cur = head;//定义一个辅助指针来帮助我们遍历
while(cur != null){
if (cur.next != null && cur.next.val == val){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
return head;
}
}
2.3 注意事项
- 在while 循环内应是if-else语句,一开始我只是写了一个if,但容易知道这种情况会在删除一个节点后cur指针直接后移,而后面这个数可能也是我们要删除的元素,也会在要删除的元素为最后一位是报空指针。
3.设计链表
3.1 思路分析
这是一道比较综合的题目,涉及链表的添加,查找,删除。
删除操作如图:
添加操作如图:
3.2 代码实现
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;
}
}
//leetcode submit region begin(Prohibit modification and deletion)
class MyLinkedList {
int size;//链表中的元素的个数
ListNode head;//虚拟头结点
public MyLinkedList() {
size = 0;
head = new ListNode(0);//初始化头节点为空,不存放数据
}
//获取链表中第 index(索引位置,即index = 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);
}
//先写addAtIndex:在链表中的第 index 个节点之前添加值为 val 的节点。
// 如果 index 等于链表的长度,则该节点将附加到链表的末尾。
// 如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
public void addAtIndex(int index, int val) {
if (index > size){
return;
}
if (index < 0){
index = 0;
}
size++;
ListNode addNode = new ListNode(val);
ListNode pre = head;//pre指向要插入节点的前一位
for(int i = 0;i < index;i++){
pre = pre.next;
}
//for循环出来后则找到
addNode.next = pre.next;
pre.next = addNode;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
if (index < 0){
index = 0;
}
size--;
if (index == 0){
head = head.next;
return;
}
ListNode pre = head;
for(int i = 0;i < index;i++){
pre = pre.next;
}
pre.next = pre.next.next;
}
}
3.3 注意事项
- 注意Index、size的表示含义,定义虚拟头节点
- 一定要先判断形参的合法性,再做后续的处理
- 操作中一定要注意空指针的情况
4.反转链表
4.1 思路分析
我一开始想要定义一个新的链表,并且有一个辅助指针帮助我们遍历原链表,保存该节点的下一个节点,依次遍历让该节点指向新的链表的最前端,同时让新的链表的虚拟头结点的next指向该节点。
实际上只需要改变链表的next指针的指向,直接将链表反转 ,如图:(纠正:动画应该是先移动pre,在移动cur)
4.2 代码实现
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) {
if (head == null){
return null;
}
ListNode pre = null;
ListNode cur = head;
ListNode temp = null;
while (cur != null){
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
4.3 注意事项
- 一定要用temp保存cur的下一个节点,否则该节点会因为没有引用指向而被垃圾回收
- cur 指针指向了null,循环结束,链表也反转完毕了。 此时我们return pre 指针就可以了,pre指针就指向了新的头结点