文章仅作记录 此次刷题记录是作为时长40天的个人项目 欢迎读者,有问题可进行评论,看到有价值的问题会进行回复
接上篇,内容为上次未更完的内容
主要内容:链表
移除链表元素
java链表定义:value,next
三个构造函数
处理链表,需要注意到的点就是关于头节点的处理方式
可以设置一个虚拟节点,去统一所有的处理方式
ListNode vir = new ListNode(-1,head)
class Solution {
public ListNode removeElements(ListNode head, int val) {
//设置虚拟节点并添加前置节点
if(head == null){
return head;
}
ListNode vir = new ListNode(0,head);
ListNode pre = vir;
ListNode cur = head;
while(cur != null){
if(cur.val==val){
pre.next = cur.next;
}
else{
pre = cur; // 恰好可作为目标处理节点的前一节点
}
cur = cur.next;
}
return vir.next;
}
}
设计链表
链表基础的增删操作
需要注意的点就是在操作的同时不要忘记处理链表的长度变化
class MyLinkedList {
class ListNode {
int val;
ListNode next;
ListNode(){}
ListNode(int val){
this.val = val;
}
}
int size;
ListNode vir;
//初始化链表
public MyLinkedList() {
size = 0;
vir = new ListNode(0);
}
public int get(int index) {
// 判断是否合法
if(index < 0||index >= size){
return -1;
}
ListNode cur = vir;
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);
}
public void addAtIndex(int index, int val) {
if(index < 0){
index = 0;
}
if(index > size){
return;
}
size++;
ListNode cur = vir;
int count = 0;
while(count != index){
cur = cur.next;
count++;
}
ListNode ins = new ListNode(val);
ins.next = cur.next;
cur.next = ins;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
size--;
if (index == 0) {
vir = vir.next;
return;
}
ListNode pred = vir;
for (int i = 0; i < index ; i++) {
pred = pred.next;
}
pred.next = pred.next.next;
}
}
反转链表
有节省空间的需求所以不去创建一个新链表,这里直接反转next即可,反转只需要改变两个指针即可
使用双指针法去解决,设置一个前节点
class Solution {
public ListNode reverseList(ListNode head) {
// 双指针法
ListNode pre = null;
ListNode cur = head;
ListNode temp;
while (cur != null) {
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
第二种方法就是使用虚拟头节点的方式,使用头插法
public static ListNode reverseList1(ListNode head) {
ListNode vir = new ListNode(-1);
vir.next = null;
ListNode cur = head;
while(cur != null){
ListNode temp = cur.next;
cur.next = vir.next;
vir.next = cur;
cur = temp;
}
return vir.next;
}
交换链表节点
一定需要注意的点,不要忘记节点的实时变化,在思路不足够清晰而且没有图示的情况下很容易紊乱
同时注意这里是一次性移动两个位置,所以这里判断为了防止漏单个未遍历也是判断两个
class Solution {
public ListNode swapPairs(ListNode head) {
// 要实现两两交换的前置的是什么? 涉及到三个节点 即获取到这三个节点
// 如何获取?
// 假设 Vir - 1 - 2 - 3 - 4
// 首先 vir的next切换为 2 , 之后 2 next 切换为 1 1 next切换为 3
ListNode vir = new ListNode(-1,head);
ListNode cur = vir;
ListNode temp,temp2;
while(cur.next != null && cur.next.next != null){
temp = cur.next;
temp2 = cur.next.next.next;
cur.next = cur.next.next;
cur.next.next = temp;
temp.next = temp2;
cur = cur.next.next;
}
return vir.next;
}
}
删除链表倒数第N个节点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode vir = new ListNode(0,head);
int size = 0;
ListNode cur = vir;
ListNode pre = null;
ListNode temp = cur;
while(temp != null){
size++;
temp = temp.next;
}
for (int i = 0; i < (size - n); i++) {
pre = cur;
cur = cur.next;
}
pre.next = cur.next;
return vir.next;
}
}
注意在size=n的时候,循环不会被执行所以需要添加一个判断
但是有个问题,这里使用了两次整体循环,一次是为了获取对应size值,这无疑添加了执行时间,那么如何简化,只用一次扫描实现呢?
此时就双指针就派上用场了
特征:处理前后存在差值 也可以理解为时间差
这里使用虚拟头节点,复习一下,作用是不对头指针处进行差异化处理
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyNode = new ListNode(0,head);
ListNode slow = dummyNode;
ListNode fast = dummyNode;
while(n != 0){
fast = fast.next;
n--;
}
while(fast.next != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return head.next;
}
}
链表相交
本题同样可以使用双指针,但是使用的方式和之前有不同,因为条件不同
可以理解为:将AB链拼接起来然后判断是否存在一致点
注意:这里是找指针一样,而非值一样
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pa = headA;
ListNode pb = headB;
while(pa != pb){
if(pa == null){
pa = headB;
}
else {
pa = pa.next;
}
if(pb == null){
pb = headA;
}
else {
pb = pb.next;
}
}
return pa;
}
}
环形链表
这个问题有两问:
-
判断链表是否存在环
-
找到环的入口返回其值
对于第一问,我们可以使用快慢指针的方式去做 why?
特征:判断是否存在回路,存在回路快慢指针必然相遇 why?
在回路中的过程其实是:快指针去追慢指针,(2-1)每步的速度逐渐逼近慢指针的(step one)
对于第二问:
方法一:可以使用一个集合将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 index = slow;
ListNode index2 = head;
while(index != index2){
index = index.next;
index2 = index2.next;
}
return index;
}
}
return null;
}
}
总结
虚拟头结点 在head处理需要进行特殊化的时 一般时进行index插入,删除,反转等
双指针法 用于清楚指针间差值的情况 ;减少多循环 ; 两头进行操作 同时加快遍历速度
快慢指针 判断环 ;快速寻找某些值