所用代码 java
链表理论基础
链表分为单向链表、双向链表和环形链表三种,但是基本的操作都是大同小异的,都是利用指针对需要处理的结点进行增删改查,无非就是不同的链表细节处理上有些差异。
对于最常见的单链表来说可以,我们必须掌握他的结点定义方法:
public 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;
}
}
有了结点,链表可以进行定义:如后面707设计链表
注意:
单链表最重要的就是双指针操作,一个前向指针,一个目标指针,很多操作都是两个指针有关。所以我们常常定义一个虚拟头结点进行操作,并使他的下一结点指向head结点。
ListNdoe dummy = new ListNode(-1);
// 虚拟头结点的下一结点指向头结点
dummy.next = head;
移除链表元素 LeetCode203
题目链接:移除链表元素 LeetCode203 - 简单
思路
本题有两种解法,一是采用虚拟头结点,另一种是没有使用虚拟头结点。使用虚拟头结点就不用判断是不是头结点该删除,直接按删除步骤进行删除。但是一般都会使用虚拟头结点进行操作,这样更加的方便。
1、使用虚拟头结点
public ListNode removeElements(ListNode head, int val) {
// 新建一个虚拟头结点进行操作
ListNode dummy = new ListNode(-1);
dummy.next = head;
// 新建两个指针对原链表进行操作
ListNode p1 = dummy; // p1指向待删除结点的前向节点
ListNode p2 = dummy.next; // p2指向待删除节点
while (p2 != null){
// 找到值,使该值(p2)的前向指针(p1)指向该值(p2)的后项指针,则删除
if (p2.val == val){
// System.out.println("打印p1的值:" + p1.val);
// System.out.println("打印p2的值:" + p2.val);
p1.next = p2.next;
p2 = p2.next; // 找到之后该节点删除,后移一位
continue;
}
p1 = p1.next;
p2 = p2.next;
}
return dummy.next;
}
2、不使用虚拟头结点
public ListNode removeElements(ListNode head, int val) {
// 判断待删除的是不是头结点,删除的时候要头结点不为空结点,否则可能空指针异常
while (head != null && head.val == val ) {
head = head.next;
}
// 判断是不是空结点,是就直接返回
if (head == null) {
return head;
}
// 以上保证了head不是空结点且head.val != val
// 保证了head与head.next都不为空
ListNode p1 = head; // p1前向指针
ListNode p2 = head.next; // 指向待删除的结点,上面head.next不为null就是为了这里
// while 这里可以这么写,比上面那种写法更简便一点
while (p2 != null) {
if (p2.val == val) {
p1.next = p2.next;
} else {
p1 = p1.next;
}
p2 = p2.next;
}
return head;
}
总结
1、使用虚拟头结点可以很快速轻松的解答出这道题,因为删除结点题找到结点后是没法删除的,必须找到该结点的前一个结点才可以删除。而且不用考虑各种头结点是否为null的情况,简单代码量又少。while里面的循环逻辑用下面那个更好一点,就一点都不冗余。
2、所犯的错误:未使用虚拟头结点时,一直报空指针异常,还以为是自己的逻辑有问题,结果是未考虑完整头结点是否为空的情况。
设计链表 LeetCode 707
题目连接: 设计链表 LeetCode 707 - 中等
思路
可采用单链表或双链表的方式。这里采用单链表的方式,单链表最重要的是虚拟头结点的使用!
// 这里是我idea的完整可运行代码,调试很方便!!!
public class DesignNode {
public static void main(String[] args) {
MyLinkedList linkedList = new MyLinkedList();
/*
linkedList.addAtHead(1);
// linkedList.printf();
linkedList.addAtTail(3);
// linkedList.printf();
linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3
linkedList.printf();
int i = linkedList.get(1);//返回2
System.out.println("第xx位结点的值为:" + i);
linkedList.deleteAtIndex(1); //现在链表是1-> 3
System.out.println("删除xx位后的链表:");
linkedList.printf();*/
linkedList.addAtTail(1);
int i = linkedList.get(0);
System.out.println("第i位的值位:"+i);
linkedList.printf();
}
}
class ListNode{
int val;
ListNode next;
ListNode(int val){
this.val = val;
}
ListNode(int val, ListNode next){
this.val = val;
this.next = next;
}
}
class MyLinkedList {
// 新建一个虚拟头结点
ListNode dummy;
// 链表长度
int size;
public MyLinkedList() {
dummy = new ListNode(-1);
size = 0;
}
public int get(int index) {
if (index < 0 || index >= size){
return -1;
}
ListNode p = dummy.next;
int i = 0;
while (p != null){
if (i == index){
// 找到就直接返回
return p.val;
}else{
i++;
p = p.next;
}
}
return -1;
}
public void addAtHead(int val) {
ListNode head = new ListNode(val);
if (size == 0){
dummy.next = head;
}else {
head.next = dummy.next;
dummy.next = head;
}
size++;
}
public void addAtTail(int val) {
ListNode tail = new ListNode(val);
ListNode p = dummy.next; // p指针操作
if (p == null){
// 这里不能用p=tail,这样就改变了p指针的指向,且dummy.next无关系了,
dummy.next = tail;
}else {
while (p.next != null){
p = p.next;
}
// 指向最后了就赋值
p.next = tail;
}
size++;
}
public void addAtIndex(int index, int val) {
// 小于0,头部插入
if (index <= 0){
addAtHead(val);
// 大于size 不插入
}else if (index > size) {
return;
// 等于链表长度,尾部插入
}else if (index == size){
addAtTail(val);
}else {
ListNode node = new ListNode(val);
ListNode pre = dummy;
ListNode cur = dummy.next;
int i = 0;
while (cur != null){
if (i == index){
pre.next = node;
node.next = cur;
// 插入成功,退出循环,否则会一直判断cur的值,然后被卡住!
break;
}else {
i++;
pre = pre.next;
cur = cur.next;
}
}
size++;
}
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size){
return;
}
ListNode pre = dummy;
ListNode cur = dummy.next;
int i = 0;
while (cur != null){
if (i == index){
pre.next = cur.next;
cur = cur.next;
// 由于只删除一位,删除完成后则退出
break;
}else {
i++;
pre = pre.next;
}
cur = cur.next;
}
size--;
}
// 打印链表的值
public void printf(){
int i = 0;
ListNode p = dummy.next;
while (p != null){
System.out.println("链表第"+i+"位结点的值为:" + p.val + "==> size=" + size);
p = p.next;
i++;
}
}
}
总结
1、本题全是自己想出来的,没有看解答,运行之后觉得时间和空间还可以优化,然后运行了一下代码随想录的代码发现时间和空间基本差不多,后意识到可能是我采用单向链表的缘故,就把双向链表的代码复制进去发现还是差不多。知道了我的代码还可以继续优化,就暂不现在优化,放以后二刷、三刷改进的时候优化。
2、设计链表这题有非常多的细节和坑,稍微一不注意就会出错,比如是否取等,什么时候退出循环,善于思考,多debug,就可以把大问题分解成小问题并一一解决。
反转链表 LeetCode 206
题目链接:反转链表 LeetCode 206 - 简单
思路
感觉很简单,但是想了很久一直都出错,就画了一幅图,跟着思路四步完成。
public ListNode reverseList(ListNode head) {
ListNode p1 = null;
ListNode p2 = head;
ListNode move = null;
while (p2 != null){
// 1.先让移动结点指向p2的next结点
move = p2.next;
// 2.再把p2的next结点指向前一结点(p1)
p2.next = p1;
// 3.p1向前移动到p2的位置,保证自己在头结点位置
p1 = p2;
// 4. p2移向自己的next结点处
p2 = move;
}
return p1;
}
总结
本来这题没做出来都睡了,突然灵光一现想到了就起来又把题做好,一次通过。
反思: 看起来特别简单自己以为自己很快就能做出来,但是突然卡住了就会想很久有点浪费时间了,以后出现这种情况还是先看解析再来写。毕竟现在基础薄弱,对这些题只是看着熟,做起来一点都不熟,特别是细节的地方需多多进步。