【剑指offer】数据结构——链表

24 阅读13分钟

数据结构——字符串

直接解

【剑指offer】06. 从尾到头打印链表

题目描述

力扣

牛客

// 输入一个链表的头节点,从尾到头反过来返回每个节点
// 的值(用数组返回)。

题解

牛客

// 数组翻转法
// 时间复杂度:14ms
// 空间复杂度:9524KB
import java.util.ArrayList;
import java.util.Collections;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> array = new ArrayList<>();
        while (listNode != null) {
            array.add(listNode.val);
            listNode = listNode.next;
        }
        Collections.reverse(array);
        return array;
    }
}



// 头插法
// 运行时间 12ms
// 占用内存 9692KB
import java.util.ArrayList;
import java.util.Collections;
import java.util.*;

public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> res = new ArrayList<>();
        while (listNode != null) {
            res.add(0, listNode.val);
            listNode = listNode.next;
        }
        return res;
    }
}


// 栈辅助
// 时间复杂度:13ms
// 空间复杂度:9528kB
public class Solution {    
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> res = new ArrayList<>();
        Stack<Integer> stack = new Stack<>();
        while (listNode != null) {
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        while (!stack.isEmpty()) {
            res.add(stack.pop());
        }
        return res;
    }
}

// 递归法
// 时间复杂度:12 ms
// 空间复杂度:9384 KB
public class Solution {
	public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
		ArrayList<Integer> ret = new ArrayList<>();
		if (listNode != null) {
			ret.addAll(printListFromTailToHead(listNode.next));
			ret.add(listNode.val);
		}
		return ret;
	}
}



// 运行时间 13ms
// 占用内存 9656KB
import java.util.ArrayList;
import java.util.Collections;
import java.util.*;

public class Solution {
    private ArrayList<Integer> res;
    
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        this.res = new ArrayList<>();
        recur(listNode);
        return res;
    }
    
    private void recur(ListNode listNode) {
        if (listNode == null) {
            return;
        }
        recur(listNode.next);
        res.add(listNode.val);
    }
}

力扣

// 力扣与牛客网不同的是,它需要你返回int[],而不是ArrayList
// 所以其实是更难了

// 栈存法
// 时间复杂度:2 ms , 在所有 Java 提交中击败了 40.69% 的用户
// 空间复杂度:39 MB , 在所有 Java 提交中击败了 91.29% 的用户
import java.util.*;

class Solution {
    public int[] reversePrint(ListNode head) {
		Stack<Integer> stack = new Stack<Integer>();
		ListNode temp = head;
		while (temp != null) {
			stack.push(temp.val);
			temp = temp.next;
		}
		int size = stack.size();
		int[] result = new int[size];
		for (int i = 0; i <= result.length - 1; i++) {
			result[i] = stack.pop();
		}
		return result;
    }
}

// 递归法
// 时间复杂度:2 ms , 在所有 Java 提交中击败了 40.69% 的用户
// 空间复杂度:40.4 MB , 在所有 Java 提交中击败了 6.12% 的用户
import java.util.Arrays;

class Solution {
	
	ArrayList<Integer> tmp = new ArrayList<Integer>();
    public int[] reversePrint(ListNode head) {
		recur(head);
		int size = tmp.size();
		int[] result = new int[size];
		for (int i = 0; i <= size - 1; i++) {
			result[i] = tmp.get(i);
		}
		return result;
    }
	
	void recur(ListNode head) {
		if (head != null) {
			recur(head.next);
			tmp.add(head.val);
		}
		else {
			return;
		}
	}
}



【剑指offer】24. 反转链表

题目描述

在这里插入图片描述 在这里插入图片描述

// 力扣
// 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

// 牛客
// 输入一个链表,反转链表后,输出新链表的表头。

题解

// 方法应该有很多

//////////////////////////////// 直接法 //////////////////////////////
// 笨办法,直接遍历所有元素,存起来,再倒叙提取出来放进新构造的链表中

// 牛客
// 运行时间:12ms
// 占用内存:9920k
import java.util.Stack;
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if (head == null)
            return head;
		// 新建一个栈stack存储链表元素val,弹出的val正好是反转顺序
        Stack<Integer> stack = new Stack<>();  
        ListNode index = head;  // 初始化链表索引,从头遍历链表
		// while循环直到index到达链表尾后一位才停止,
		// 所以循环结束后index会到达链表尾结点的下一个位置
		// 我们将这个位置作为新链表的头结点,开始创建新链表
        while (index != null) {  
            stack.push(index.val);  // 每遍历个结点就将val压入栈存储
            index = index.next;  // 所以index右移
        }
		// index位置创建新结点,弹出一次stack存的val
        index = new ListNode(stack.pop());  
		// 当前index位置作为新链表的头结点,创建一个指向记为result
        ListNode result = index;  
		// index右移创建新结点,弹出stack存储的元素作为新结点的元素,
        while (!stack.isEmpty()) {  
            index.next = new ListNode(stack.pop());  // 创建新结点
            index = index.next;  // index右移
        }
        return result;  // 返回刚刚新链表(片段)的头结点
    }
}


// 力扣
// 执行用时:2 ms, 在所有 Java 提交中击败了7.85%的用户
// 内存消耗:38.2 MB, 在所有 Java 提交中击败了71.94%的用户
import java.util.Stack;
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null)
            return head;
        Stack<Integer> stack = new Stack<>();  
        ListNode index = head; 
        while (index != null) {  
            stack.push(index.val);
            index = index.next; 
        }
        index = new ListNode(stack.pop());
        ListNode result = index;  
        while (!stack.isEmpty()) {  
            index.next = new ListNode(stack.pop());
            index = index.next;  
        }
        return result;
    }
}

//////////////////////////////// 头插法 /////////////////////////
// 这个方法画图就懂了,是比较推荐的方法

// 牛客
// 运行时间:10ms
// 占用内存:9780k
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if (head == null)
            return head;
		ListNode newList = new ListNode(-1);
		while (head != null) {
			ListNode next = head.next;
			head.next = newList.next;
			newList.next = head;
			head = next;
		}
		return newList.next;
    }
}


// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38 MB, 在所有 Java 提交中击败了91.31%的用户
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null)
            return head;
		ListNode newList = new ListNode(-1);
		while (head != null) {
			ListNode next = head.next;
			head.next = newList.next;
			newList.next = head;
			head = next;
		}
		return newList.next;
    }
}
////////////////////////////// 三指针法 ////////////////////////////
// 也是比较推荐的方法,我画一个示意图方便理解

// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38 MB, 在所有 Java 提交中击败了92.77%的用户

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

// 牛客
// 运行时间:11ms
// 占用内存:9876k
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if (head == null || head.next == null)
            return head;
		ListNode pre = null;
		ListNode nex = null;
		ListNode cur = head;
		while (cur != null) {
			nex = cur.next;
			cur.next = pre;
			pre = cur;
			cur = nex;
		}	
		return pre;
    }
}

三指针法示意图:

在这里插入图片描述

///////////////////////////// 递归法 /////////////////////////

// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38.5 MB, 在所有 Java 提交中击败了32.42%的用户
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null)
            return head;
        ListNode next = head.next;
        head.next = null;
        ListNode newHead = reverseList(next);
        next.next = head;
        return newHead;
    }
}

// 牛客
// 运行时间:12ms
// 占用内存:10048k
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if (head == null || head.next == null)
            return head;
        ListNode next = head.next;
        head.next = null;
        ListNode newHead = ReverseList(next);
        next.next = head;
        return newHead;
    }
}




【剑指offer】25. 合并两个排序的链表

题目描述

在这里插入图片描述 在这里插入图片描述

// 25. 合并两个排序的链表

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

// 输入两个单调递增的链表,输出两个链表合成后的链表,当然我
// 们需要合成后的链表满足单调不减规则。

题解

////////////////////////////// 遍历法 ////////////////////////////
// 力扣
// 执行用时:1 ms, 在所有 Java 提交中击败了98.60%的用户
// 内存消耗:38.3 MB, 在所有 Java 提交中击败了95.77%的用户
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
		if (l1 == null)
			return l2;
		if (l2 == null)
			return l1;
		ListNode head = new ListNode(-1);
		ListNode cur = head;  // 单结点
		
		// l1和l2这两个给定的指针作为遍历指针,在cur的前面遍历
		while (l1 != null && l2 != null) {
			// 谁小,cur.next会指向谁
			if (l1.val <= l2.val) {  // 如果l1值比l2值小
				cur.next = l1;  // cur.next指向l1
				l1 = l1.next;  // l1指针右移
			}
			else {
				cur.next = l2;
				l2 = l2.next;
			}
			// 上面的if else判据里,
			// 如果l1值比l2值小,之前cur.next会指向l1,此时cur右移到之前l1位置
			// 如果l2值比l1值小,之前cur.next会指向l2,此时cur右移到之前l2位置
			cur = cur.next;  
		}
		// 如果有链表没遍历完(该链表比另一个链表长)
		// cur.next指向该链表当前指针,直接连上长链表未遍历的尾巴
		if (l1 != null) {
			cur.next = l1;
		}
		if (l2 != null) {
			cur.next = l2;
		}
		return head.next;  // 返回head.next
    }
}

// 牛客
// 运行时间:16ms
// 占用内存:9948k
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if (list1 == null)
            return list2;
        if (list2 == null)
            return list1;
        ListNode head = new ListNode(-1);
        ListNode cur = head;
        
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                cur.next = list1;
                list1 = list1.next;
            }
            else {
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        if (list1 != null) {
            cur.next = list1;
        }
        if (list2 != null) {
            cur.next = list2;
        }
        return head.next;
    }
}



// leetcode中的不同解法
// 运行时间:19ms 超过77.29%用Java提交的代码
// 占用内存:9916KB 超过70.27%用Java提交的代码
public class Solution {
    public ListNode Merge(ListNode l1,ListNode l2) {
        if (l1 == null)
            return l2;
        else if (l2 == null)
            return l1;

        ListNode temp = new ListNode(0);
        ListNode res;
        if (l1.val <= l2.val) res = l1;
        else res = l2;

        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                while (l1.next != null && l1.next.val <= l2.val) 
                    l1 = l1.next;
                temp = l1;
                l1 = l1.next;
                temp.next = l2;
            }
            else { // l1.val > l2.val
                while (l2.next != null && l1.val > l2.next.val) 
                    l2 = l2.next;
                temp = l2;
                l2 = l2.next;
                temp.next = l1;
            }
        }
		return res;
    }
}

遍历法图解(非leetcode中的解法)

在这里插入图片描述

///////////////////////////// 递归法 ////////////////////////
// 递归思路与遍历法是一样的,所以还是推荐遍历法,只是理解起来好看
// ,其资源消耗不可谓不小,因此该方法在牛客是无法通过的

// 力扣
// 执行用时:1 ms, 在所有 Java 提交中击败了98.60%的用户
// 内存消耗:38.8 MB, 在所有 Java 提交中击败了26.71%的用户
class Solution {
	public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
		if (l1 == null) {
			return l2;
		}
		if (l2 == null) {
			return l1;
		}
		ListNode head = new ListNode(-1);
		if (l1.val <= l2.val) {
			head = l1;
			head.next = mergeTwoLists(l1.next, l2);
		}
		else {
			head = l2;
			head.next = mergeTwoLists(l1, l2.next);
		}
		return head;
	}
}


在这里插入图片描述



【剑指offer】35. 复杂链表的复制

题目描述

在这里插入图片描述

在这里插入图片描述

// 35. 复杂链表的复制

// 力扣
// 请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中
// ,每个节点除了有一个 next 指针指向下一个节点,还有一个 ran
// dom 指针指向链表中的任意节点或者 null。


// 牛客
// 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下
// 一个节点,另一个特殊指针random指向一个随机节点),请对此链表进
// 行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参
// 数中的节点引用,否则判题程序会直接返回空)

题解

复制结点部分图解:

在这里插入图片描述

random指向图解:

在这里插入图片描述

// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:37.5 MB, 在所有 Java 提交中击败了98.32%的用户
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
		if (head == null)
			return null;
		// 每一个结点之后插入其复制结点
		Node cur = head;  // 初始化遍历指针
		while (cur != null) {
			Node clone = new Node(cur.val);  // 复制cur遍历的原链表结点,记为clone
			clone.next = cur.next;  // clone.next与cur.next一致
			cur.next = clone;  // 然后让cur.next指向clone本身
			cur = clone.next;  // 移动cur到下一个原链表结点
		}
		// 构建random指向
		cur = head;  // 重置cur回head
		while (cur != null) {  // 循环遍历链表
			// 令clone结点为cur的下一个结点(clone结点即为cur的复制结点)
			Node clone = cur.next;  
			if (cur.random != null)  // 如果cur.random不为null
				// 由于cur和clone的关系,clone是cur的下一个结点
				// clone.random即为cur.random的下一个结点
				clone.random = cur.random.next;  
			cur = clone.next;  // cur移动至下一个原链表结点
		}
		// 两个链表拆分
		cur = head;  // 重置cur回head
		Node cloneHead = head.next;  // 设定复制链表的头结点
		while (cur.next != null) {  // 循环遍历,直到cur.next为空
			// 令cur.next为next,到此我们有cur和next(cur.next)双指针
			Node next = cur.next;  
			// 令cur的next直接指向next.next(cur.next.next)
			// 原链表cur越过了cur的复制点,连上了原链表cur的下一个结点
			cur.next = next.next; 
			cur = next;  // cur右移
		}
		return cloneHead;  // 拆分结束,最后返回复制链表头结点
    }
}


// 牛客
// 运行时间:12ms
// 占用内存:9804k
public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        if (pHead == null)
            return null;
        // 每个结点复制
        RandomListNode cur = pHead;
        while (cur != null) {
            RandomListNode clone = new RandomListNode(cur.label);
            clone.next = cur.next;
            cur.next = clone;
            cur = clone.next;
        }
        // random指向部署
        cur = pHead;
        while (cur != null) {
            RandomListNode clone = cur.next;
            if (cur.random != null)
                clone.random = cur.random.next;
            cur = clone.next;
        }
        // 分离
        cur = pHead;
        RandomListNode cloneHead = pHead.next;
        while (cur.next != null) {
            RandomListNode next = cur.next;
            cur.next = next.next;
            cur = next;
        }
        return cloneHead;
    }
}




【剑指offer】52. 两个链表的第一个公共结点

题目描述 在这里插入图片描述

在这里插入图片描述

// 52. 两个链表的第一个公共结点

// 力扣
// 输入两个链表,找出它们的第一个公共节点。

// 牛客
// 输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,
// 所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)


题解

// HashSet法,
// 构建hashset用于存储遍历过的结点。
// 先让指针cur从headA移动,将遍历过的结点存入set。遍历完之后,
// 让cur回到headB,再遍历一次B链表,结点存入set,存不进的结点就是
// 相交的起点。
// 执行用时:10 ms, 在所有 Java 提交中击败了10.60%的用户
// 内存消耗:41.9 MB, 在所有 Java 提交中击败了10.07%的用户
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set<ListNode> set = new HashSet<>();
        ListNode cur = headA;
        while (cur != null) {
            set.add(cur);
            cur = cur.next;
        }
        cur = headB;
        while (cur != null) {
            if (!set.add(cur))
                return cur;
            cur = cur.next;
        }
        return null;
    }
}

// 牛客
// 双指针
// 假设俩链表A B,俩链表的公共长度是P,
// A链表的总长度是 A(私有链)+P(公共链)
// B链表的总长度是 B(私有链)+P(公共链),那么肯定有A+P+B = B+P+A。
// 我们让两个指针p1, p2分别从A B链表头出发,p1指针从A表头出发,
// 走A->P->B的路线,p2指针从B表头出发,走B->P->A的路线,最后两个指针
// 一定会在第一个公共结点相遇。
// 运行时间:12ms,超过95.24%用Java提交的代码
// 占用内存:9816KB,超过74.81%用Java提交的代码
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
		ListNode p1 = pHead1;
		ListNode p2 = pHead2;
		while (p1 != p2) {
			if (p1 != null)
				p1 = p1.next;
			else 
				p1 = pHead2;
			
			if (p2 != null)
				p2 = p2.next;
			else 
				p2 = pHead1;
		}
		return p1;
    }
}



// 牛客
// 运行时间:16ms,超过74.58%用Java提交的代码
// 占用内存:9988KB,超过73.19%用Java提交的代码
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
		ListNode p1 = pHead1;
		ListNode p2 = pHead2;
		while (p1 != p2) {
			p1 = (p1 == null) ? pHead2 : p1.next;
			p2 = (p2 == null) ? pHead1 : p2.next;
		}
		return p1;
    }
}


// 力扣
// 执行用时:1 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:41.1 MB, 在所有 Java 提交中击败了82.68%的用户
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1 = headA, p2 = headB;
        while (p1 != p2) {
            p1 = (p1 == null) ? headB : p1.next;
            p2 = (p2 == null) ? headA : p2.next;
        }
        return p1;
    }
}


特殊解——双指针

【剑指offer】18. 删除链表的节点

题目描述

在这里插入图片描述

// 力扣
// 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
// 返回删除后的链表的头节点。
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */

题解

////////////////////////////// 双指针法 //////////////////////////////
// 对数据结构有印象的就知道,删除一个链表结点,需要找待删除结点的前一个结点
// 令前一个结点直接指向待删除结点的next,这个待删除结点就从链表结构中被去除了。


// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:37.9 MB, 在所有 Java 提交中击败了71.42%的用户
class Solution {
	public ListNode deleteNode(ListNode head, int val) {
        if (head == null)
            return null;
        // 假定val是一定找得到的,head.next不存在,那待删除结点是head本身,删除后返回null
        if (head.next == null)  
            return null;
        // 如果待删除节点是头结点,直接返回head.next
        if (head.val == val && head.next != null)
            return head.next;
		
		// new一个链表引用pre,它的next指向head,即head前面多了一个节点
		// 这个引用指针用来遍历待删除节点的前一个结点
		ListNode pre = new ListNode(0);  // pre.val初始化为0(无影响)
		pre.next = head;
		
		// 定义链表引用cur,它直接指向head,
		// 这个引用指针用于遍历整个链表
		ListNode cur = head;
		while (cur != null) {
			// 非尾结点
			// 若cur指针找到了val所在的结点
			if (cur.val == val && cur.next != null) {
				// 令pre.next直接越过待删除结点cur
				// 直接指向cur的下一个结点cur.next
				pre.next = cur.next;  
				cur = null;  // 规范操作,令cur元素清空
				break;
			}
			// 尾结点
			if (cur.val == val && cur.next == null) {
                pre.next = null;  // 直接令pre.next指向null
				break;
			}
			pre = pre.next;  // 没找到val,右移一位
			cur = cur.next;  // 没找到val,右移一位
		}
		return head;  // 题目要求返回删除后的链表,返回头引用即可
	}
}
////////////////////////////// 递归法 //////////////////////////////

class Solution {

	// 解题函数,也是递归函数
	// 递归函数检查head.next是否为待删除结点
	public ListNode deleteNode(ListNode head, int val) {
		// 以下三个if语句跟双指针法是一样的,但是意义不一样
		
		// 若head第一个结点就不存在,返回null
		if (head == null)
            return null;
        // 1.当deleteNode还没被递归调用时:
		// 此时head还指向头结点,如果head.next为null,说明整个链表就一个结点,删除之后为null
		// 2.当deleteNode被递归调用后:
		// 由于递归调用的语句是head.next = deleteNode(head.next, val);
		// 因此字段head实际表示head.next,那字段head.next实际表示head.next.next,
		// 若head.next.next为null,说明head.next是待删除尾结点,直接返回null,
		// 这时候head.next会被指向这个返回的null,删除完成
		// (注:如果这句if能触发,说明在head.next到达尾结点之前一直都没return过
		//   说明尾结点必为待删除结点)
        if (head.next == null)  
            return null;
        // 1.当deleteNode还没被递归调用时:
		// 如果待删除节点是头结点,直接返回head.next
		// 2.当deleteNode被递归调用后:
		// head实际表示head.next,所以如果head.next.val == val
		// 说明head.next是待删除结点,返回head.next,也就是返回
		// head.next.next,这时head.next会指向这个返回的head.next.next
		// 原来的head.next从链表中被移除。
        if (head.val == val && head.next != null)
            return head.next;
		// 将head.next指向deleteNode的返回值
		// deleteNode将被递归调用,输入为head.next和val
		head.next = deleteNode(head.next, val);
		// 递归完成,返回删除好的链表头索引head
		return head;
	}
}



【剑指offer】18.2 删除链表中重复的结点

题目描述

在这里插入图片描述

// 18.2 删除链表中重复的结点

// 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,
// 重复的结点不保留,返回链表头指针。例如,链表1->2->3->3->4->4->5
// 处理后为 1->2->5

题解


//////////////////////////////// 双指针 ////////////////////////////
// 牛客


// 运行时间:14ms
// 占用内存:9916k
public class Solution{
	public ListNode deleteDuplication(ListNode pHead) {
		if (pHead == null || pHead.next == null)
			return pHead;
		ListNode dummy = new ListNode(-1);
		dummy.next = pHead;
		
		ListNode pre = dummy;
		ListNode cur = pHead;
		cur = cur.next;  // 先让cur右移一格,我们比较的是pre.next和cur
		while (cur != null) {
			// 如果发现重复结点
			if (pre.next.val == cur.val) {
				if (cur.next == null) {  // 判断cur右移后是否到达链表尾
					pre.next = null;  // 到达链表尾,就直接让pre.next指向null
					break;  // 打断循环,返回结果
				}
				
				cur = cur.next;  // cur继续右移(直到重复串的尾部)
				// 如果右移后cur到达重复子串的尾部
				if (pre.next.val != cur.val) { 
					pre.next = cur;  // 直接令pre.next指向cur
					cur = cur.next;  // cur右移一格
				}
			}
			// 未发现重复结点,一起右移
			else {  
				pre = pre.next;
				cur = cur.next;
			}
		}
		return dummy.next;  // 不要返回pHead头节点了
	}
}


// 简化版
// 运行时间:14ms
// 占用内存:9984k
public class Solution{
	public ListNode deleteDuplication(ListNode pHead) {
		if (pHead == null || pHead.next == null)
			return pHead;
		ListNode dummy = new ListNode(-1);
		dummy.next = pHead;
		
		ListNode pre = dummy;
		ListNode cur = pHead;
		while (cur != null) {
			// 如果发现重复结点
			if (cur.next != null && cur.next.val == cur.val) {
				while (cur.next != null && cur.next.val == cur.val) {
					cur = cur.next;  // cur继续右移(直到重复串的尾部)
				}
				// 再前进一格
				cur = cur.next;
				pre.next = cur;
			}
			// 未发现重复结点,一起右移
			else {  
				pre = pre.next;
				cur = cur.next;
			}
		}
		return dummy.next;
	}
}

/////////////////////////////////// 递归法 //////////////////////////
// 牛客
// 运行时间:11ms
// 占用内存:9948k
public class Solution {
	public ListNode deleteDuplication(ListNode pHead) {
		// 未开始递归时,pHead是头结点,开始递归后,pHead为遍历结点

		if (pHead == null || pHead.next == null) {
			return pHead;
		}
		// 1.未开始递归时,取头结点next记为next
		// 2.开始递归后,pHead为遍历节点,且实际被pHead.next指向,
		// next为pHead.next,所以实际被pHead.next.next指向
		ListNode next = pHead.next;  
		// 1.未开始递归时,若头结点出现重复,则if成立,
		// 直接开始while循环找重复子串尾结点
		// 2.开始递归后,如果pHead和next的val相等
		// 开始while循环找重复子串尾结点
		if (pHead.val == next.val) {
			// while循环继承了if的判断,同时保证next(pHead.next.next)不为null
			while (next != null && pHead.val == next.val) 
				next = next.next;  // next右移,直到遍历到重复子串的下一个结点
			// 返回deleteDuplication递归调用
			// 递归到底并返回之后,由于next被pHead.next指向,所以
			// next循环右移就已经剔除了重复结点
			return deleteDuplication(next);  
		}
		// 若头结点未出现重复,else成立
		else {
			// 令pHead.next指向eleteDuplication的递归调用,输入pHead.next
			// 假设链表一直没有重复节点,函数输入pHead实际是被上一个递归
			// 的pHead.next指向,再递归取pHead.next,对于上一次递归
			// 就是pHead.next.next,一直这样下去,实际就是遍历了整个
			// 链表一次。
			pHead.next = deleteDuplication(pHead.next);
			return pHead;
		}
	}
}



【剑指offer】22. 链表中倒数第k个节点

题目描述 在这里插入图片描述 在这里插入图片描述

// 力扣
// 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,
// 本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有
// 6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的
// 倒数第3个节点是值为4的节点。



// 牛客
// 输入一个链表,输出该链表中倒数第k个结点。

题解

//////////////////////////// 双指针滑窗 //////////////////////////
// 由于需要返回以倒数第k个结点为头结点。第k结点往后的链表。
// 假如1->2->3->4->5,k = 2。我们需要返回的就是4->5。
// 那么我们就从链表头构建两个指针pre和end,end先右移扩大窗口,
// 直到pre到end的窗口的长度就等于第k结点往后的链表长度为止。
// 再以这个窗口滑动遍历链表,窗口右顶点到达链表尾即可返回。


// 力扣
// 力扣的版本比较简单,给定的k不会为0,也不会超过链表的长度,比较容易理解
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        if (head == null)  // 特殊情况判定
            return head;
        ListNode pre = head;  // 滑窗前指针
        ListNode end = head;  // 滑窗后指针
        int i = 1;  // 
		// 将end右移,直到pre到end的窗口长度等于k到链表尾的长度
        while (i < k) {  
            end = end.next;
            i += 1;
        }
        while (end.next != null) {  // 窗口右移,直到右顶点到达链表尾
            end = end.next;
            pre = pre.next;   
        }
        return pre;  // 此时的窗口左顶点位置即为倒数第k结点位置,直接返回
    }
}

// 牛客
// 运行时间:14ms
// 占用内存:9976k
public class Solution {
    public ListNode FindKthToTail(ListNode head, int k) {
        if (head == null)
            return head;
        if (k == 0)  // 如果k是0,返回null
            return null;
        
        ListNode pre = head;
        ListNode end = head;
        int i = 1;
		// 保证不要超过链表长度范围,把end指针向后,移动次数记为i
        while (i != k && end.next != null) {  
            end = end.next;  
            i += 1;  // end右移一次,i计数一次
        }
		// 如果k大于整个链表长度,i的大小是达不到k的,所以直接返回null
        if (i < k)  
            return null;
		// 如果k没有大于整个链表长度,滑窗
        while (end.next != null) {
            end = end.next;
            pre = pre.next;   
        }
        return pre;
    }
}