public class LinkedListProblem {
//1. 删除链表中等于给定值val的所有节点。
// OJ链接:https://leetcode-cn.com/problems/remove-linked-list-elements/description/
//203. 移除链表元素
//给你一个链表的头节点 head 和一个整数 val ,
// 请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
public ListNode removeElements(ListNode head, int val) {
//1.判定链表是否为空
if(head == null) {
return null;
}
//2.从第2个节点开始遍历链表,节点的值等于val,则删除该节点前节点,否则移动到下一个节点
ListNode cur = head;
while(cur.next!= null){
if(cur.next.val == val){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
//3.判断头节点是否等于val,如果等于val,则返回头节点的下一个节点,否则返回头节点
if(head.val == val){
head = head.next;
return head;
}else{
return head;
}
}
//2. 反转⼀个单链表。
//OJ链接:https://leetcode-cn.com/problems/reverse-linked-list/description/
//206. 反转链表
//给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
public ListNode reverseList(ListNode head) {
//1.判定链表是否为空
if(head == null) {
return null;
}
//2.如果链表只有一个节点,则直接返回该节点
if(head.next == null) {
return head;
}
//3.反转链表,定义三个指针,pre指向null,cur指向head,nex指向null
ListNode pre = null;
ListNode cur = head;
ListNode nex = null;
//4.遍历链表,将cur的next指针指向pre,pre指向cur,cur指向nex,nex指向null
while(cur!=null){
nex=cur.next;
cur.next=pre;
pre=cur;
cur=nex;
}
//5.返回反转后的链表
return pre;
}
//3.
// OJ链接:https://leetcode-cn.com/problems/middle-of-the-linked-list/description/
//876. 链表的中间结点
//给你单链表的头结点 head ,请你找出并返回链表的中间结点。
//如果有两个中间结点,则返回第二个中间结点。
public ListNode middleNode(ListNode head) {
//1.判定链表是否为空
if(head == null){
return null;
}
//2.求链表长度
int size = size(head);
//3.求中间节点的位置
ListNode middle = head;
for(int i = 0; i < size/2; i++){
middle = middle.next;
}
return middle;
}
//4. 输⼊⼀个链表,输出该链表中倒数第k个结点。
// OJ链接:https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/
//面试题 02.02. 返回倒数第 k 个节点
//实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
public int kthToLast(ListNode head, int k) {
//1.判定链表是否为空
if(head == null){
return 0;
}
//2.求链表长度
int size = size(head);
//3.判定k是否有效
if(k <= 0 || k > size){
return 0;
}
//4.根据k计算倒数第k个节点的下标
int index = size - k;
//5.遍历链表,移动引用到第index个节点
ListNode cur = head;
for(int i = 0; i < index; i++){
cur = cur.next;
}
//6.返回第index个节点的值
return cur.val;
}
//5.
// OJ链接:https://leetcode-cn.com/problems/merge-two-sorted-lists/description/
//21. 合并两个有序链表
//将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
//1.当出现链表为空的情况时
if(list1 == null){
return list2;
}
if(list2 == null){
return list1;
}
//2.准备两个指针,一个指向list1的头结点,一个指向list2的头结点
ListNode cur1 = list1;
ListNode cur2 = list2;
//3.定义一个新的链表,用于存储合并后的链表
//这里采用带傀儡节点的方式,后续会更加简单
ListNode newList = new ListNode(0);
//4.记录一个尾巴,指向newList的尾结点
ListNode tail = newList;
//5.进入循环,开始合并
while(cur1!= null && cur2!= null){
//判断cur1和cur2哪个节点的值更小
if(cur1.val < cur2.val){
//如果cur1的节点值更小,则将其添加到newList尾巴后
tail.next = cur1;
//更新cur1的位置
cur1 = cur1.next;
}else{
//如果cur2的节点值更小,则将其添加到newList尾巴后
tail.next = cur2;
//更新cur2的位置
cur2 = cur2.next;
}
//更新尾巴
tail = tail.next;
}
//6.上述循环完毕,意味着其中一个链表已经遍历完,将剩余的链表添加到newList尾巴后
if(cur1!= null){
tail.next = cur1;
}
if(cur2!= null){
tail.next = cur2;
}
//7.返回newList的头结点
//注意,这里返回的是newList的头结点的下一个节点,
// 因为newList的头结点是带傀儡节点
return newList.next;
}
//6.
//OJ链接:https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?tpId=8&&tqId=11004&rp=2&ru=/activity/oj&qru=/ta/cracking-the-coding-interview/question-ranking
//CM11 链表分割
//现有一链表的头指针 ListNode* pHead,给一定值x,
// 编写一段代码将所有小于x的结点排在其余结点之前,
// 且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
public ListNode partition(ListNode pHead, int x) {
//1.当链表为空时
if(pHead == null){
return null;
}
//2.当链表只有一个节点时
if(pHead.next == null){
return pHead;
}
//3.先创建两个链表,一个存储小于x的结点,一个存储大于等于x的结点
// 这里采用带傀儡节点的方式,后续会更加简单
//同时分别记录这两个链表的尾巴
ListNode smallHead = new ListNode(0);
ListNode smallTail = smallHead;
ListNode largeHead = new ListNode(0);
ListNode largeTail = largeHead;
//4.遍历原链表,将小于x的结点添加到smallTail后,大于等于x的结点添加到largeTail后
for(ListNode cur = pHead; cur!= null; cur = cur.next){
if(cur.val < x){
smallTail.next = cur;
smallTail = smallTail.next;
}else{
largeTail.next = cur;
largeTail = largeTail.next;
}
}
//4.将两个链表连接起来
smallTail.next = largeHead.next;
//5.稳妥起见,将尾巴的下一个引用指向空
largeTail.next = null;
//6.返回新的链表头结点
return smallHead.next;
}
//7.链表的回文结构
//OJ链接:https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&rp=1&ru=/activity/oj&qru=/ta/2016test/question-ranking
//OR36 链表的回文结构
//对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
//给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
public boolean chkPalindrome(ListNode A) {
// 1.当链表为空时
if (A == null) {
return true;
}
// 2.当链表只有一个节点时
if (A.next == null) {
return true;
}
// 3.定义两个指针,一个指向头结点,一个指向尾结点
ListNode slow = A;
ListNode fast = A;
// 4.快指针走两步,慢指针走一步
while (fast.next!= null && fast.next.next!= null) {
slow = slow.next;
fast = fast.next.next;
}
// 5.将链表分成两部分,前半部分和后半部分
//reverseList(slow.next)意思是反转了一下链表,然后返回反转后的链表的头结点
ListNode secondHalf = reverseList(slow.next);
ListNode firstHalf = A;
// 6.判断是否为回文结构
while (secondHalf!= null) {
if (firstHalf.val != secondHalf.val) {
return false;
}
firstHalf = firstHalf.next;
secondHalf = secondHalf.next;
}
return true;
}
//8.输入两个链表,找出它们的第一个公共结点。
//OJ链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/description/
//160. 相交链表
//给你两个单链表的头节点 headA 和 headB ,
// 请你找出并返回两个链表相交的起始节点
// 。如果两个链表没有交点,返回 null 。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//1.当两个链表都为空时
if(headA == null || headB == null){
return null;
}
//2.当两个链表只有一个节点时
if(headA.next == null || headB.next == null){
return null;
}
//3.定义两个指针,一个指向headA,一个指向headB
ListNode curA = headA;
ListNode curB = headB;
//4.遍历两个链表,当两个指针指向同一个节点时,返回该节点
while(curA != curB){
//如果curA指向null,则将curA指向headB
if(curA == null){
curA = headB;
}else{
curA = curA.next;
}
//如果curB指向null,则将curB指向headA
if(curB == null){
curB = headA;
}else{
curB = curB.next;
}
}
return curA;
}
//9.给定⼀个链表,判断链表中是否有环。
// OJ链接:https://leetcode-cn.com/problems/linked-list-cycle/description/
//141. 环形链表
//给你一个链表的头节点 head ,判断链表中是否有环。
//如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
//为了表示给定链表中的环,评测系统内部使用整数 pos 来表示
// 链表尾连接到链表中的位置(索引从 0 开始)。
// 注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
public boolean hasCycle(ListNode head) {
//1.当链表为空时
if(head == null){
return false;
}
//使用快慢引用的方法,判断链表是否有环
ListNode slow = head;
ListNode fast = head.next;
while(fast != null && fast.next != null){
if(slow == fast){
return true;
}
slow = slow.next;
fast = fast.next.next;
}
return false;
}
//10. 给定⼀个链表,返回链表开始⼊环的第⼀个节点。如果链表⽆环,则返回NULL
// OJ链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/description/
//142. 环形链表 II
//给定一个链表,返回链表开始入环的第一个节点。
// 如果链表无环,则返回 null 。
//为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
// 注意,pos 不作为参数进行传递。仅仅是为了标识链表的实际情况。
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
// 1. 使用快慢指针, 找到相遇位置. 如果不相遇, 说明无环.
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
break;
}
}
// 循环结束, 需要判定, 是因为 fast 和 slow 重合, 结束循环的, 还是因为 fast 为 null 结束循环的.
if (fast == null || fast.next == null) {
// 无环的情况, fast 已经到末尾了.
return null;
}
// 2. 设置一个 cur1 , 从 head 出发, 设置 cur2 从交汇位置出发. 两个引用同时往前走.
// 两个引用重合的位置, 就是环的入口点.
ListNode cur1 = head;
ListNode cur2 = slow;
// 这里的循环一定不会死循环的. 通过数学公式已经证明了, cur1 和 cur2 是必定会相遇.
// 而且相遇的位置就是环的入口.
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
// 循环结束, 说明 cur1 和 cur2 相遇了.
return cur1;
}
public int size(ListNode head){
int size = 0;
for(ListNode cur = head; cur!= null; cur = cur.next){
size++;
}
return size;
}
}