Java单链表:常见操作整理

首先明确两个概念:头结点和头指针

头结点:
头结点是为了操作的统一与方便而设立的,放在第一个元素结点之前,其数据域一般无意义(当然有些情况下也可存放链表的长度);头结点不是必须的!;头结点后面的第一个节点称为首元结点
头指针:
头指针是指链表指向第一个结点的指针,若链表有头结点,则头指针就是指向链表头结点的指针。没有头结点,头指针就指向首元节点,如图所示。
在这里插入图片描述

LeetCode中的 head,一般是 头指针,指向首元节点!解题时可将head视作第一个节点

一、删除节点

1.1 删除链表指定值的结点

在这里插入图片描述
注:输入为头指针。头指针是指链表指向第一个结点的指针,若链表有头结点,则头指针就是指向链表头结点的指针。此处没有头结点,所以默认是从第一个节点开始的!

【法一】

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if(head == null) return head;
        //head=[4,5,1,9],head为头指针,此处没有头结点,所以默认是从第一个节点开始的!
        if(head.val==val) return head.next;//如果要删除的是第一个节点,要从head.next开始返回
        ListNode cur = head;
        while(cur.next != null && cur.next.val != val){
            cur = cur.next;
        }
        if(cur.next != null){
            cur.next = cur.next.next;
        }
        return head;
    }
}

【法二】伪头结点法

也可以设计一个伪头结点,ListNode header = new ListNode<-1>; header.next = head;

class Solution {
    public ListNode removeElements(ListNode head, int val){ //head
        if(head == null) return head;
        ListNode header = new ListNode(-1);
        header.next = head;
        ListNode cur = header;
        
        while(cur.next != null){
            if(cur.next.val == val){
                cur.next = cur.next.next;
            }else{
                cur = cur.next;
            }
        }
        return header.next;
    }
}

——前面的删除单链表的一个值,亦可用该方法!

1.2 删除指定位置上的结点

	public static void delectIndexNode(ListNode head,int index)
	{
		ListNode cur = head;
		int count = 1;
		while(cur.next!=null)
		{
			if(count==index)
			{
				cur.next=cur.next.next;//执行删除操作
			}
			else
			{
				count++;
				cur=cur.next;
			}
		}
	}

也可以考虑用双指针法来做:

//删除指定位置上的结点,从0开始
public void delectIndexNode(ListNode head.int index){
	ListNode cur = head;
	ListNode pre = new ListNode(-1);
	pre.next = head;
	int count = 0;
	while(cur !=null){
		if(count == index){
			pre.next = cur.next;
		}else{
			cur = cur.next;
			pre = pre.next;
			count++;
		}
	}
}

1.3 删除链表中的重复结点

(1) 未排序链表

在这里插入图片描述

class Solution {
    public ListNode removeDuplicateNodes(ListNode head) {
        if(head==null) return null;
        Set<Integer> set = new HashSet<Integer>();
        set.add(head.val);
        ListNode dummyhead = head;
        while(dummyhead.next!=null){
            ListNode cur = dummyhead.next;//待删除节点
            if(set.add(cur.val)){
                dummyhead = dummyhead.next;
            }else{
                dummyhead.next = dummyhead.next.next;
            }
        }
        return head;
    }
}

(2) 排序链表

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode cur = head;
        if(head == null) return head;
        while(cur.next != null){
            if(cur.val == cur.next.val){
                cur.next = cur.next.next;
            }else{
                cur = cur.next;
            }
        }
        return head;
    }
}

1.4 删除倒数第N个节点——双指针

//双指针p,q,当q先行n步,p后行,当q到达null时,p就是应该删除的点的前一个结点
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode p = dummyHead;//p为head的前一个
        ListNode q = head;
        for(int i=1;i<=n;i++){//使p,q之间间隔n+1个节点
            q = q.next;
        }
        while(q!=null){//当q到达了null时,p在倒数第n个节点的前一个结点
            p = p.next;
            q = q.next;
        }
        p.next = p.next.next;
        //此处不要直接返回head,因为有可能删除掉的结点就是head
        return dummyHead.next;
    }
}

1.5 输出倒数第K个节点——双指针

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        if(head==null) return null;
        ListNode p = head;
        ListNode q = head;
        for(int i=0;i<k;i++){
            q = q.next;
        }
        while(q!=null){
            p = p.next;
            q = q.next;
        }
        return p;
    }
}

1.6 删除排序链表所有重复结点

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null) return null;
        ListNode dummyhead = new ListNode(-1);
        dummyhead.next = head;
        ListNode fast = head;
        ListNode slow = dummyhead;
        while(fast != null){
            if(fast.next != null && fast.val == fast.next.val){  // 3 1 1 1 1 2 2
                while(fast.next != null && fast.next.val == fast.val){
                    fast = fast.next;  //此时fast在最后一个重复的位置
                }
                slow.next = fast.next;//删除了所有重复结点 3 2 2
                fast = fast.next;//此时快指针在2,慢指针在3
            }else{
                fast = fast.next;
                slow = slow.next;
            }
        }
        return dummyhead.next;
    }
}

二、单链表反转

(1)全表反转

在这里插入图片描述

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while(cur != null){
            ListNode tmp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
}

在这里插入图片描述

(2)部分反转

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
在这里插入图片描述

class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pre = dummy;
        
        for(int i=1;i<m;i++){
            pre = pre.next;//将pre移动到m的前一个
        }
        head = pre.next;
        for(int i=m;i<n;i++){
            ListNode tmp = head.next;
            head.next = tmp.next;
            tmp.next = pre.next;
            pre.next = tmp;
        }
        return dummy.next;
    }
}

(3)旋转链表:将每个节点右移k个位置

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
示例 1:

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL

示例 2:

输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if(head == null || k==0) return head;
        ListNode cur = head;
        ListNode tail = null;
        int len=1;
        while(cur.next != null){//获取总长度
            cur = cur.next;
            len++;
        }
        tail = cur;//尾指针指向尾结点
        cur.next = head;//此时cur指向最后一个结点,现将其改为循环链表
        cur = head;
        int loop = len - (k%len);//难理解:循环链表移动次数,即将cur指针移动到末尾的k%len处
        for(int i=0;i<loop;i++){
            cur = cur.next;
            tail = tail.next;
        }
        tail.next = null;
        return cur;
    }
}

题目来源:LeetCode61

三、判断单链表是否有环 + 找到环的入口

在这里插入图片描述

【判断是否有环】
【方法一】用哈希表存储访问过的链表结点

public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode cur = head;
        Set<ListNode> nodeSet = new HashSet<>();
        while(cur != null){
            if(nodeSet.contains(cur)){    //或者: if(!nodeSet.add(cur))
                return true;
            }else{
                nodeSet.add(cur);
            }
            cur = cur.next;
        }
        return false;
    }
}

【方法二】快慢指针法

//判断是否有环
public class Solution {
    public boolean loopList(ListNode head){
		if(head == null) return false;
		ListNode fast = head;
		ListNode slow = head;
		while(fast != null && fast.next != null){
			fast = fast.next.next;
			slow = slow.next;
			if(fast == slow){
				return true;
			}
		}
		return false;
    }
}

【找到环的入口】
数学求证:假设一个简单的带环单链表,快慢指针相遇时,快指针走了x+y+z+y,慢指针走了x+y,x+y+z+y = 2(x+y),得:x = z,因而,从相遇开始,以相同速度从head开始走的距离等于从相遇点开始走的距离,即再次相遇时,相遇点为环的入口!
在这里插入图片描述

	public ListNode loopPoint(ListNode head){
		if(head==null){
			return null;
		}
		ListNode fast = head;
		ListNode slow = head;
		while(fast != null && fast.next != null){
			fast = fast.next.next;
			slow = slow.next;
			if(fast == slow){//此时快指针比慢指针多走了一圈环
				break;
			}
		}
		slow = head;//慢指针回到头节点
		//开始相同速度行进,当快慢指针再次相遇,快慢指针刚好在环的入口
		while(fast != slow){
			slow = slow.next;
			fast = fast.next;
		}
		return slow;
	}
}

四、在链表指定位置插入

	public void insertNode(int index,ListNode head,ListNode node)
	{
		ListNode cur = head;
		int count=1;
		while(cur.next!=null)
		{
			if(count==index)
			{
				node.next=cur.next;
				cur.next=node;
			}
			else
			{
				count++;
				cur=cur.next;
			}
		}
	}

五、合并排序链表

5.1 合并两个排序链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

【递归法】

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null) return l2;
        if(l2 == null) return l1;
        
        if(l1.val <= l2.val){
            l1.next = mergeTwoLists(l1.next,l2);
            return l1;
        }else{
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }
}

【非递归法】

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dum = new ListNode(0), cur = dum;
        while(l1 != null && l2 != null) {
            if(l1.val < l2.val) {
                cur.next = l1;
                l1 = l1.next;
            }else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        cur.next = l1 != null ? l1 : l2;
        return dum.next;
    }
}

5.2 合并k个排序链表

import java.util.*;
//合并 k 个已排序的链表并将其作为一个已排序的链表返回。分析并描述其复杂度。
public class Solution {
    public ListNode mergeKLists(List<ListNode> lists){
        if(lists==null || lists.size() == 0) return null;
        if(lists.size()==1) return lists.get(0);
        if(lists.size()%2 != 0) lists.add(null);//偶数才行
        List<ListNode> sum = new ArrayList<>();
        //两两合并的结果放进sum中
        for(int i=0;i<lists.size();i+=2){
            sum.add(mergeTwoLists(lists.get(i),lists.get(i+1)));
        }
        //递归,sum中的结果再两两合并
        return mergeKLists(sum);
    }
    
    //合并两个
    public ListNode mergeTwoLists(ListNode l1,ListNode l2){
        if(l1 == null) return l2;
        if(l2 == null) return l1;
        
        if(l1.val <= l2.val){
            l1.next = mergeTwoLists(l1.next,l2);
            return l1;
        }else{
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }
}

六、两个链表求和(反向+正向)

LeetCode 面试题 02.05:链表求和
给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。
在这里插入图片描述

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = null,rear = null;
        boolean upFlag = false;
        while (l1 != null || l2 != null) {
            int sum = 0;
            if(l1 != null) {
                sum += l1.val;
                l1 = l1.next;
            }
            if(l2 != null) {
                sum += l2.val;
                l2 = l2.next;
            }

            if(upFlag) {
                sum += 1;
                upFlag = false;
            }

            int nodeNum = sum % 10;
            if(sum > 9) {
                upFlag = true;
            }
            //挂节点
            if(head == null) {
                head = rear = new ListNode(nodeNum);
            }else {
                ListNode rearNode = new ListNode(nodeNum);
                rear = rear.next = rearNode;
            }
        }
        if(upFlag) {
            rear.next = new ListNode(1);
        }
        return head;
    }
}

进阶:

进阶:假设这些数位是正向存放的,请再做一遍。

示例:

输入:(6 -> 1 -> 7) + (2 -> 9 -> 5),即617 + 295 输出:9 -> 1 -> 2,即912

public ListNode reverseList(ListNode l1) {
		ListNode pre = null;
		ListNode next = null;
		while(l1 != null) {
			next = l1.next;
			l1.next = pre;
			pre = l1;
			l1 = next;
		}
		return pre;
}
//后面再加上上面的即可。	

七、链表的相交节点 / 公共节点

在这里插入图片描述
【思路】:
A和B同时走,走到头后再走对方的路,这样:
A链表走的路径是:A——D——C——B——D,长度L1 = AD+DC+BD
B链表走的路径是:B——D——C——A——D,长度L2 = BD+DC+AD
所以:L1 = L2,最终必然会在D点相遇!

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null || headB==null) return null;
        ListNode curA = headA;
        ListNode curB = headB;
        while(curA!=curB){ //直到找到相交点才结束循环
            if(curA == null){
                curA = headB;
            }else{
                curA = curA.next;
            }
            if(curB == null){
                curB = headA;
            }else{
                curB = curB.next;
            }
        }
        return curA;
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值