所用代码 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; }
总结
本来这题没做出来都睡了,突然灵光一现想到了就起来又把题做好,一次通过。
反思:** 看起来特别简单自己以为自己很快就能做出来,但是
突然卡住了就会想很久有点浪费时间了
,以后出现这种情况还是先看解析再来写。毕竟现在基础薄弱,对这些题只是看着熟,做起来一点都不熟,特别是细节的地方需多多进步。
【代码训练营】day3 | 203.移除链表元素 & 707.设计链表 & 206.反转链表
于 2023-01-14 05:01:46 首次发布