单链表Ⅱ
一、虚拟头节点
在链表内部创建一个不存在存储具体元素,只作为链表头
Node dummyHead = new Node(-1);
所有链表的插入和删除->在中间位置进行
1.增加
package seqlist;
//带头单链表
public class SingleLinkedListWithHead {
//当前存储的元素个数
private int size;
//虚拟头节点
private Node dummyHead= new Node(-1);
//添加方法
public void addIndex(int index,int val){
//判断index的合法性
if (index<0||index >size) {
System.err.println("add index illagal!");
return ;
}
//插入全部是中间节点
Node node = new Node(val);
//找到待插入位置的前驱
Node prev= dummyHead;
for (int i = 0; i < index; i++) {
prev=prev.next;
}
//prev指向待插入位置的前驱
node.next=prev.next;
prev.next=node;
size++;
}
public void addFirst(int val){
addIndex(0,val);
}
public void addLast(int val){
addIndex(size,val);
}
public String toString(){
String ret="";
Node node= dummyHead.next;
while(node!=null){
ret+=node.val;
ret+="->";
node=node.next;
}
ret+="NULL";
return ret;
}
}
测试代码
package seqlist;
public class Test {
public static void main(String[] args) {
SingleLinkedListWithHead singleLinkedListWithHead=new SingleLinkedListWithHead();
singleLinkedListWithHead .addLast(1);
singleLinkedListWithHead.addLast(2);
singleLinkedListWithHead.addLast(3);
singleLinkedListWithHead.addFirst(4);
singleLinkedListWithHead.addIndex(1,10);
System.out.println(singleLinkedListWithHead);
//4,10,1,2,3
}
2.输出
public String toString(){
String ret="";
Node node= dummyHead.next;
while(node!=null){
ret+=node.val;
ret+="->";
node=node.next;
}
ret+="NULL";
return ret;
}
3.删除
public void removeIndex(int index){
if (index<0||index>=size) {
System.err.println("remove index illegal!");
return;
}
//删除中间节点
Node prev=dummyHead;
//找到待删除位置的前驱
for (int i = 0; i < index; i++) {
prev=prev.next;
}
//prev指向待删除位置的前驱
prev.next=prev.next.next;
size--;
}
测试代码
//4,10,1,2,3
singleLinkedListWithHead.removeIndex(4);
System.out.println(singleLinkedListWithHead);
4.虚拟头节点LeetCode203
//虚拟头节点
public ListNode removeElements(ListNode head,int val){
ListNode dummyHead = new ListNode(-1);
//链接原来的链表
dummyHead.next=head;
ListNode prev = dummyHead;
while (prev.next!=null) {
if (prev.next.val ==val ) {
prev.next=prev.next.next;
}else {
//prev下一个不是待删除节点,prev继续向后移动位置
prev=prev.next;
}
}
return dummyHead.next;
}
二、单链表习题
1.LeetCode83
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
1.判断重复元素->需要两个引用来对比内容,prev.val=cur.val
prev永远指向第一个重复节点,删除对应cur节点,保留prev
2.终止条件
while()->当前用的元素是谁,就要保证谁不为空
所以边界条件为:while(cur!=null)
3.只有当prev和cur值不相等时,才能移动prev引用(保证删除后若再次出现重复元素不会漏删)
4.插入和删除index位置的节点,找到index的前驱从dummyHead开始向后走。走index步刚好走到前驱(比之前的单链表多走了一个虚拟头节点)
prev=dummyHead;
for(int i=0;i<index;i++) { prev=prev.next;}
2.LeetCode82
给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
1.判断重复
prev与cur,发现找到重复元素时,prev指向第一个重复元素,此时prev指向的元素就删除不掉->不能使用prev和cur来判断重复,需要再次引用一个新的引用next,cur.val==next.val判断重复节点
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummyHead=new ListNode(101);
dummyHead.next=head;
ListNode prev=dummyHead;
ListNode cur=prev.next;
while(cur!=null){
ListNode next=cur.next;
if(next==null){
return dummyHead.next;
}else{
//至少有两个节点
if(cur.val!=next.val){
//三引用同时向后移动
prev=prev.next;
cur=cur.next;
}else {
//将用到元素都要进行判断是否为空
//保证所有的引用都不为空,否则出现空指针问题
while(next!=null&&cur.val==next.val){
next=next.next;
}
//prev指向重复节点的前驱
//next指向重复节点的后继
prev.next=next;
//更新cur的指向
cur=next;
}
}
}
return dummyHead.next;
}
}
3.LeetCode206
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
方法一:头插法
public class Num206 {
//1.头插法
public ListNode reverseList(ListNode head) {
if (head == null||head.next==null) {
return head;
}
//新链表的虚拟头节点
ListNode dummyHead =new ListNode(5001);
//边遍历原链表,边头插新链表
while(head!=null){
//原链表1,2,3,4,5,
//不断创建新节点
//dummyHead->1,1后面是空
//dummyHead->2->1 以此类推
ListNode node =new ListNode(head.val);
node.next=dummyHead.next;
dummyHead.next=node;
head=head.next;
}
return dummyHead.next;
}
}
方法二:原地移动-O(1)
原链表的next不再指向后继而指向前驱
if (head == null||head.next==null) {
return head;
}
ListNode prev=null;
//当前需要处理的节点(需要反转)
ListNode cur =head;
while(cur!=null){
//暂存一下下一个需要处理的节点
ListNode next=cur.next;
cur.next=prev;
prev=cur;
cur=next;
}
return prev;
方法三:递归法
if (head == null||head.next==null) {
return head;
}
ListNode sec=head.next;
//反转第二个节点之后的子链表
ListNode newHead=reverseList(head.next);
//3.将sec.next=head;
//head.next=null;
sec.next=head;
head.next=null;
return newHead;
4.LeetCode876
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
public class Num876 {
public ListNode middleNode(ListNode head) {
ListNode fast=head;
ListNode low=head;
while(fast !=null && fast.next!=null){
low=low.next;
fast=fast.next.next;
}
return low;
}
}
5.剑指 Offer 22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
//未验证k的合法性
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast=head ,low = head;
//fast 先走K步,保持fase,low的相对距离
for(int i=0;i<k;i++){
fast=fast.next;
}
while(fast!=null){
fast=fast.next;
low=low.next;
}
return low;
}
}
6.LeetCode234:回文链表
即:找到中间节点+反转中间节点之后的子链表
class Solution {
//回文链表
public boolean isPalindrome(ListNode head) {
//1.找到中间节点
ListNode middleNode=middleNode(head);
//2.反转中间节点后的链表,一定比原链表的一半短
ListNode l2=reverseList(middleNode);
//同时遍历原链表l1
while (l2!=null){
if (l2.val !=head.val ) {
return false;
}
l2=l2.next;
head=head.next;
}
return true;
}
//反转
public ListNode reverseList(ListNode head) {
if (head == null||head.next==null) {
return head;
}
ListNode sec=head.next;
//反转第二个节点之后的子链表
ListNode newHead=reverseList(head.next);
//3.将sec.next=head;
//head.next=null;
sec.next=head;
head.next=null;
return newHead;
}
//中间节点
public ListNode middleNode(ListNode head) {
ListNode fast=head;
ListNode low=head;
while(fast !=null && fast.next!=null){
low=low.next;
fast=fast.next.next;
}
return low;
}
}
7.LeetCode141:链表带环Ⅰ
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
//判断链表是否带环
public class Num141 {
public boolean hasCycle(ListNode head) {
ListNode fast = head, low = head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
low=low.next;
//指向同一节点
if (fast == low) {
return true;
}
}
return false;
}
}
8.LeetCode21:合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
//判断边界条件,l1\l2\l1和l1为空
if (list1==null){
return list2;
}
if (list2==null){
return list1;
}
ListNode dummyHead=new ListNode(-1);
ListNode last=dummyHead;
while (list1!=null&&list2!=null){
if (list1.val<=list2.val){
last.next=list1;
last=list1;
list1=list1.next;
}else{
last.next=list2;
last=list2;
list2=list2.next;
}
}
//剩余节点
if (list1==null){
last.next=list2 ;
}
if (list2 == null) {
last.next=list1;
}
return dummyHead.next;
}
}
9.LeetCode:分割链表
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你不需要 保留 每个分区中各节点的初始相对位置。
public class Num0204 {
public ListNode partition(ListNode head, int x) {
ListNode bigHead=new ListNode(-1);
ListNode smallHead=new ListNode(-1);
//按照升序插入,需要尾插
//分别指向两个子链表的尾部
ListNode smalltTail=smallHead;
ListNode bigTail=bigHead;
//边遍历原链表,根据剩下节点依次放在子链表中
while (head!=null){
if (head.val<x){
smalltTail.next=head;
smalltTail=head;
}else{
bigTail.next=head;
bigTail=head;
}
head=head.next;
}
//将bigTail尾部元素置为null
bigTail.next=null;
//拼接两个链表
smalltTail.next=bigHead.next;
return smallHead.next;
}
}
10.LeetCode160:相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
设A:未相交链表为A,B:未相交链表为B,相交链表为C。按照下面思路有,A+C+B=B+C+A。
让A和B遍历自己的链表当走到NULL时,遍历对象链表。如果有交点在交点处汇合,如果没交点在结尾NULL处汇合。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA=headA,pB=headB;
while (pA!=pB){
pA=pA==null?headB:pA.next;
pB=pB==null?headA:pB.next;
}
return pA;
}
}
11.LeetCode141:链表带环Ⅱ
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode low=head,fast=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
low=low.next;
//此时low和fast相遇
if (fast==low){
//创建一个新的node,从头开始和low一起走
ListNode third=head;
while (low!=third){
low=low.next;
third=third.next;
}
return third;
}
}
return null;
}
}