使用虚拟头节点来进行链表的删除操作,可以省去删除的是头节点时判断的麻烦
例题:203. 移除链表元素
代码
class Solution {
public ListNode removeElements(ListNode head, int val) {
//建立虚拟头节点
ListNode dummy = new ListNode(0);
dummy.next = head;
//让cur节点从head开始,虚拟头节点是没有意义的节点,相当于就是null
ListNode pre = dummy,cur = head;
while(cur != null){
if(cur.val == val){//如果删除了元素,pre指针暂时不用前移,判断是否下一个元素也需要删除
pre.next = cur.next;
}else{
pre = cur;
}
cur = cur.next;
}
return dummy.next;
}
}
设计链表
设计一个带有虚拟头结点的链表,注意如何定义链表
例题
707 设计链表
方法一:使用单链表的方式
注意事项
- 因为有虚拟头节点的存在,所以返回和处理的节点索引都应该是index+1
- 画图判断会更好理解
代码
//使用单链表
class ListNode{
int val;
ListNode next;
ListNode(){}
ListNode(int val){
this.val = val;
}
}
class MyLinkedList {
//定义链表长度
int size;
//定义虚拟头节点
ListNode head;
public MyLinkedList() {
head = new ListNode(0);
size = 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) {//相当于在index 0 前插入节点
addAtIndex(0,val);
}
public void addAtTail(int val) {//相当于在index size前插入节点
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
//判断索引是否大于链表长度,大于链表长度则不会插入节点
if(index > size) return;
if(index < 0){
index = 0;
}
size++;
ListNode newNode = new ListNode(val);
ListNode pre = head;
for(int i = 0;i < index;i++){
pre = pre.next;
}
newNode.next = pre.next;
pre.next = newNode;
}
public void deleteAtIndex(int index) {
if(index < 0 || index >= size) return;
size--;
ListNode pre = head;
for(int i = 0;i < index;i++){
pre = pre.next;
}
pre.next = pre.next.next;
}
}
方法二:使用双链表
//使用双链表
class ListNode{
int val;
ListNode prev;
ListNode next;
ListNode(){}
ListNode(int val){
this.val = val;
}
}
class MyLinkedList {
//定义链表长度
int size;
//定义虚拟头节点
ListNode head,tail;
public MyLinkedList() {
head = new ListNode(0);
tail = new ListNode(0);
size = 0;
//一定要事先这样定义,不然会有空指针异常
head.next = tail;
tail.prev = head;
}
public int get(int index) {
//判断索引是否越界
if(index < 0 || index >= size){
return -1;
}
ListNode cur;
//判断从哪边查找更近
if(index > size/2){
cur = tail;
for(int i = 0;i < size - index;i++){
cur = cur.prev;
}
}else{
cur = head;
for(int i = 0;i <= index;i++){
cur = cur.next;
}
}
return cur.val;
}
public void addAtHead(int val) {//相当于在index 0 前插入节点
addAtIndex(0,val);
}
public void addAtTail(int val) {//相当于在index size前插入节点
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
//判断索引是否大于链表长度,大于链表长度则不会插入节点
if(index > size) return;
if(index < 0){
index = 0;
}
size++;
ListNode newNode = new ListNode(val);
ListNode pre = head;
for(int i = 0;i < index;i++){
pre = pre.next;
}
//注意双链表的处理
newNode.next = pre.next;
pre.next.prev = newNode;
newNode.prev = pre;
pre.next = newNode;
}
public void deleteAtIndex(int index) {
if(index < 0 || index >= size) return;
size--;
ListNode pre = head;
for(int i = 0;i < index;i++){
pre = pre.next;
}
//注意双链表的处理
pre.next = pre.next.next;
pre.next.prev = pre;
}
}
两两交换链表中的值
[24 两两交换链表中的值]
注意事项:
一定要画图,不然自己就乱掉了!!!!
每次操作完后自己画的图也要及时更新!!!
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy;
//注意终止条件,每次要判断cur节点之后的两个节点,所以必须保证这两个节点都不为空!!
while(cur.next != null && cur.next.next != null){
//保存临时节点
ListNode tmp1 = cur.next;
ListNode tmp2 = cur.next.next.next;
//两两交换
cur.next = cur.next.next;
cur.next.next = tmp1;
cur.next.next.next = tmp2;
//每次移动两位
cur = cur.next.next;
}
return dummy.next;
}
}
翻转链表
206 翻转链表
注意事项:一定要保存cur节点的后一个节点,不然修改后这个值就是pre了,就没办法把整个链表反转了
//双指针翻转链表
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
//保存cur的下一个节点
ListNode tmp;
while(cur != null){
tmp = cur.next;
cur.next = pre;
//指针后移
pre = cur;
cur = tmp;
}
return pre;
}
}
删除链表的倒数第N个节点
19删除链表的倒数第N个节点
本题可以使用快慢指针法,快指针先走n步,然后慢指针再和快指针一起开始向后走,当快指针为空时慢指针指向的位置就是要删除的位置。
注意:本题设定虚拟头节点对头节点的处理会更加方便,但此时快指针需要多走一步。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//设置一个虚拟头节点
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode slow = dummy,fast = dummy;
while(n-- > 0){
fast = fast.next;
}
//因为有虚拟头节点,需要多走一步
fast = fast.next;
while(fast != null){
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
链表相交
链表相交
方法一:根据快慢指针,走的快的一定会追上走的慢的。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null) return null;
ListNode curA = headA,curB = headB;
while(curA != curB){
if(curA != null){
curA = curA.next;
}else{
curA = headB;
}
if(curB != null){
curB = curB.next;
}else{
curB = headA;
}
}
return curA;
}
}
方法二:A链表和B链表末尾对齐的方式
这种方式更好理解。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//计算链表A和链表B的长度
ListNode curA = headA,curB = headB;
int lenA = 0,lenB = 0;
while(curA != null){
lenA++;
curA = curA.next;
}
while(curB != null){
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
//如果链表B长于链表A,则交换
if(lenB > lenA){
int tmpLen = lenA;
lenA = lenB;
lenB = tmpLen;
ListNode tmp = curA;
curA = curB;
curB = tmp;
}
int len = lenA - lenB;
//A链表移到与B链表末尾对齐的位置
while(len-- > 0){
curA = curA.next;
}
while(curA != null){
if(curA == curB){
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
环形链表
还是之前的思想,如果链表中存在环,那么走的快的指针一定会追上走的慢的指针。
public class Solution {
//1.判断是否是环
//定义快慢指针
//快指针每次走两个节点,慢指针每次走一个节点,如果快慢指针相遇即是链表中存在环;
//2.确定入口位置
//定义从头节点到环形入口节点的节点数为x
//定义环形入口节点到相遇节点的节点数为y
//定义相遇节点到环形入口节点的节点数为z
//那么快慢指针的等量关系为:2*(x+y) = x+ y + n(y+z);
//当n = 1时,可以得出x = z;
public ListNode detectCycle(ListNode head) {
//定义快慢指针
ListNode fast = head,slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){//相遇了,需要找到入口节点
ListNode index1 = fast;
ListNode index2 = head;
while(index1 != index2){
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}