引言
补博客打卡阶段,之前做了笔记,但没有发布博客,借着回顾把这些笔记都整理发布吧。
一些做题思路(很简略)和心得
一、移出链表元素
思路
链表操作的两种方式:
- 直接使用原来的链表来进行删除操作。
- 设置一个虚拟头结点在进行删除操作。
本题采用设置一个虚拟头结点进行操作
代码
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode curr = dummyHead;
while (curr != null && curr.next != null){
if (curr.next.val == val){
curr.next = curr.next.next;
}else{
curr = curr.next;
}
}
return dummyHead.next;
}
}
注意点
最后的返回值要注意,是虚拟头结点的下一个节点,因为会发生把头结点删除的情况,如果返回头结点,那么返回的是删除的头结点。
二、设计链表
链接: 707.设计链表
思路
【细节】本题的关键,我认为有两个。一个非常重要的是对于虚拟头节点的理解,另一个是for循环过程中, i 和 index之间关系中是否带有等号
虚拟头节点是在定义链表过程中的一个虚拟项,并不真实存在于链表中,并不代表着该链表Index的第一项即 index=0 是定义的虚拟头节点 head。
其实对于这两点,主要是在草稿上面举例尝试一下,用脑子一般会想岔
代码
class ListNode {
int val;
ListNode next;
ListNode(){}
ListNode(int val) {
this.val=val;
}
}
class MyLinkedList {
int size;
ListNode head;
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
public int get(int index) {
if (index < 0 || index >= size){
return -1;
}
ListNode current = head;
for(int i = 0;i <= index;i++){
current = current.next;
}
return current.val;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
if (index > size){
return;
} ·
if (index < 0){
// index小于0时的处理方法 和 index=0时的处理方法相同,所以此种情况直接将index赋值为0
index = 0;
}
size++;
//找到要插入节点的前驱
ListNode pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
ListNode toAdd = new ListNode(val);
toAdd.next = pred.next;
pred.next = toAdd;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size){
return;
}
size--;
// 一定要单独考虑头节点的情况。
// 头节点和一般情况不同,删除头节点时,是直接将头节点设置成了虚拟头节点
// 所以这个时候就不能简单的只是删除,也要改变虚拟头节点的值
if (index == 0){
head = head.next;
return;
}
ListNode current = head;
for (int i = 0;i < index;i++){
current = current.next;
}
current.next = current.next.next;
}
}
心得
可以称之为错误集锦,总会出现一些不可思议的错误
1.为了不影响阅读性,我将力扣中的一段代码剪贴进微信,在调试完其他代码将该代码粘贴至原位置时疯狂报错。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
原因是:在粘贴进微信后,微信改变了空格的编码方式。
解决方法:在力扣中注释就好,不要剪切到其他文件中。
2.在删除和增加节点时,一定要记得在链表的size里+1
3.关于头节点,不一定能像一般节点一样处理,一定要思考一遍,如果能和一般情况合并再合并。
4.删除头结点时很特殊。头节点和一般情况不同,删除头节点时,是直接将头节点设置成了虚拟头节点,所以这个时候就不能简单的只是删除,也要改变虚拟头节点的值
三、反转链表
链接: 206.反转链表
思路
双指针法,cur.next = pre 然后同时移动,注意边界条件
代码
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre =null;
ListNode curr = head;
while (curr != null){
ListNode temp = curr.next;
curr.next = pre;
pre = curr;
curr = temp;
}
head = pre;
return head;
}
}
注意,为了更符合逻辑一点,将pre 赋值给head,最后返回head
四、两两交换链表中的节点
链接: 24.两两交换链表中的节点
思路
思路很简单,就是两个节点交换,需要考虑两个节点前面和后面的节点
很容易绕晕,多设几个变量也没关系,清楚明了是重点、
代码
// 自己的方法 容易绕晕
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null) return null;
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode pre = dummyHead;
ListNode cur = head;
while (cur != null && cur.next != null){
ListNode temp = cur.next;
pre.next = temp;
cur.next = cur.next.next;
temp.next = cur;
pre = pre.next.next;
//这步我出错了,我写成了cur = cur.next.next
//cur其实已经换过位置了
cur = cur.next;
}
return dummyHead.next;
}
}
// 代码随想录的解法
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dumyhead = new ListNode(-1); // 设置一个虚拟头结点
dumyhead.next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
ListNode cur = dumyhead;
ListNode temp; // 临时节点,保存两个节点后面的节点
ListNode firstnode; // 临时节点,保存两个节点之中的第一个节点
ListNode secondnode; // 临时节点,保存两个节点之中的第二个节点
while (cur.next != null && cur.next.next != null) {
temp = cur.next.next.next;
firstnode = cur.next;
secondnode = cur.next.next;
cur.next = secondnode; // 步骤一
secondnode.next = firstnode; // 步骤二
firstnode.next = temp; // 步骤三
cur = firstnode; // cur移动,准备下一轮交换
}
return dumyhead.next;
}
}
代码随想录中的方法,第一个节点firstnode,第二个节点secondnode,两个节点前面的节点cur,两个节点后面的节点temp
思路很清晰
五、删除链表的倒数第N个节点
链接: 19.删除链表的倒数第N个节点
思路
本题的思路很巧妙,运用双指针,设置快指针和慢指针,快指针先进行n个节点的遍历,然后快慢两个指针同时继续遍历
当快指针到达null时,慢指针正好到达需要删除节点的前一个节点 slowIndex.next = slowIndex.next.next;
方法是这个方法,具体细节和边界条件,需要找个具体的链表模拟一下。
代码
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 1.定义一个虚拟头节点
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
// 2.将fast节点先移动到对应位置
ListNode fast = dummyHead;
ListNode slow = dummyHead;
n = n + 1;
while (n-- > 0 && fast != null){
fast = fast.next;
}
// 3.fast与slow同时移动,当fast = null时,slow在对应位置
while (fast != null){
fast = fast.next;
slow = slow.next;
}
// 删除
slow.next = slow.next.next;
// [1] 删除1 的例子中,head始终指向头节点,即使利用虚拟头节点删除后也是指向的被删除的头节点,要注意
// return head; 错误
return dummyHead.next;
}
}
关于涉及到链表删除的操作,不管是删除什么元素,要设置虚拟头节点,并且在最后 return dummyHead.next
目的是为了防止符合条件的元素是头节点,这样在返回时就会出错。
六、链表相交
链接: 160.链表相交
思路
本题是求链表相交 注意理解题目,一旦相交了,后面所有的节点都重合了。
所以对于长度较长的那一方,我们需要遍历到长度差的节点,两个链表在同一起点(末尾位置对齐)
代码
自己卡顿的错误做法:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int lenA = 1;
int lenB = 1;
ListNode curr = headA;
while (curr != 0){
lenA++;
curr = curr.next;
}
ListNode curr = headB;
while (curr != 0){
lenB++;
curr = curr.next;
}
// 知道应该交换链表A和链表B,之后会很复杂
if (lenB > lenA){
}
}
}
一开始给链表A和B分别设定长度 和 指向头节点的变量,之后交换时,只用交换指向头节点的变量即可。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int lenA = 0;
int lenB = 0;
ListNode curA = headA;
ListNode curB = headB;
while (curA != null){
curA = curA.next;
lenA++;
}
while (curB != null){
curB = curB.next;
lenB++;
}
curA = headA;
curB = headB;
if (lenB > lenA){
int tempN;
tempN = lenB;
lenB = lenA;
lenA = tempN;
ListNode tempList;
tempList = curB;
curB = curA;
curA = tempList;
}
int gap = lenA - lenB;
while (gap-- > 0 && curA != null){
curA = curA.next;
}
while (curA != null){
if(curA == curB) return curA;
curA = curA.next;
curB = curB.next;
}
return null;
}
}
很顺利,带着脑子做,就很赞,加油!
七、环状链表II
思路
类似数学问题
数学问题与双指针的结合
设置快慢两个指针,快指针每次遍历跳两个节点,慢指针一个节点,如果链表有环,迟早能追上。
记录下快慢指针相等的节点,经过一系列的数学公式
【图片转载至代码随想录】
x = z
设置一个新的指针index1 = head ; index2 = slow;以一个节点为单位向前遍历,相等的节点,即为我们所求的链表入环的节点
代码
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if (fast == slow){
ListNode index1 = head;
ListNode index2 = slow;
// if 和 while 要想一想
// 既然有fast = slow了,那一定存在index
while (index1 != index2){
index1 = index1.next;
index2 = index2.next;
}
return index2;
}
}
return null;
}
}