目录
5.3.1删除重复元素,重复元素只保留一个LeetCoede83
5.3.2删除重复元素,重复元素都不要LeetCoede82
1.两个链表的第一个公共子节点(看图二)
方法一:通过Hash辅助查找
public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) { // 如果其中一个链表为空,则不存在公共节点,直接返回null
return null;
}
ListNode current1 = pHead1; // 创建一个指针current1,指向链表pHead1的头节点
ListNode current2 = pHead2; // 创建一个指针current2,指向链表pHead2的头节点
HashMap<ListNode, Integer> hashMap = new HashMap<ListNode, Integer>(); // 创建一个HashMap用于存储链表节点和出现次数的映射关系
while (current1 != null) { // 遍历链表pHead1
hashMap.put(current1, null); // 将当前节点加入到HashMap中
current1 = current1.next; // 移动current1指针到下一个节点
}
while (current2 != null) { // 遍历链表pHead2
if (hashMap.containsKey(current2)) // 如果HashMap中包含了当前节点
return current2; // 返回当前节点,即为两个链表的第一个公共节点
current2 = current2.next; // 移动current2指针到下一个节点
}
return null; // 不存在公共节点,返回null
}
方法2:通过集合来辅助查找
public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
Set<ListNode> set = new HashSet<>(); // 创建一个HashSet用于存储链表节点
while (headA != null) { // 遍历链表headA
set.add(headA); // 将当前节点加入到HashSet中
headA = headA.next; // 移动headA指针到下一个节点
}
while (headB != null) { // 遍历链表headB
if (set.contains(headB)) // 如果HashSet中包含了当前节点
return headB; // 返回当前节点,即为两个链表的第一个公共节点
headB = headB.next; // 移动headB指针到下一个节点
}
return null; // 不存在公共节点,返回null
}
方法3:通过栈
public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
Stack<ListNode> stackA = new Stack(); // 创建一个栈stackA,用于存储链表headA的节点
Stack<ListNode> stackB = new Stack(); // 创建一个栈stackB,用于存储链表headB的节点
while (headA != null) { // 遍历链表headA
stackA.push(headA); // 将当前节点压入栈stackA
headA = headA.next; // 移动headA指针到下一个节点
}
while (headB != null) { // 遍历链表headB
stackB.push(headB); // 将当前节点压入栈stackB
headB = headB.next; // 移动headB指针到下一个节点
}
ListNode preNode = null; // 定义一个节点preNode,用于存储公共节点的前一个节点
while (stackB.size() > 0 && stackA.size() > 0) { // 当两个栈都非空时循环
if (stackA.peek() == stackB.peek()) { // 如果栈顶节点相同
preNode = stackA.pop(); // 将栈A的栈顶节点出栈并赋值给preNode
stackB.pop(); // 将栈B的栈顶节点出栈
} else { // 如果栈顶节点不同,说明上一个节点就是最后一个公共节点
break;
}
}
return preNode; // 返回preNode,即为两个链表的第一个公共节点的前一个节点
}
方法4:通过序列拼接
public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) { // 如果其中一个链表为空,直接返回null
return null;
}
ListNode p1 = pHead1; // 定义指针p1指向链表pHead1的头节点
ListNode p2 = pHead2; // 定义指针p2指向链表pHead2的头节点
while (p1 != p2) { // 当p1和p2不相等时循环
p1 = p1.next; // p1指针后移一位
p2 = p2.next; // p2指针后移一位
if (p1 != p2) { // 如果p1和p2不相等
if (p1 == null) { // 如果p1已经到达链表末尾
p1 = pHead2; // 将p1指针重置为链表pHead2的头节点
}
if (p2 == null) { // 如果p2已经到达链表末尾
p2 = pHead1; // 将p2指针重置为链表pHead1的头节点
}
}
}
return p1; // 返回p1,即为两个链表的第一个公共节点
}
2.LeeCode234题目
public boolean isPalindrome(ListNode head) {
ListNode temp = head; // 创建一个临时节点,指向链表的头节点
Stack<Integer> stack = new Stack(); // 创建一个栈,用于存储链表节点的值
// 将链表节点的值依次压入栈中
while (temp != null) {
stack.push(temp.val);
temp = temp.next;
}
// 从头节点开始遍历链表
while (head != null) {
// 如果当前节点的值与栈顶元素不相等,说明链表不是回文的,返回false
if (head.val != stack.pop()) {
return false;
}
head = head.next; // 继续遍历下一个节点
}
return true; // 当遍历完整个链表时,没有发现不相等的值,说明链表是回文的,返回true
}
3.1LeeCode21题目
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode newHead = new ListNode(-1); // 创建一个新的头节点,并初始化为-1
ListNode res = newHead; // 创建一个结果节点,用于保存合并后的链表
// 遍历两个链表,直到其中一个链表为空
while (list1 != null || list2 != null) {
if (list1 != null && list2 != null) { // 如果两个链表都不为空
if (list1.val < list2.val) { // 如果list1的值小于list2的值
newHead.next = list1; // 将list1连接到新链表的下一个节点
list1 = list1.next; // 移动list1指针到下一个节点
} else if (list1.val > list2.val) { // 如果list1的值大于list2的值
newHead.next = list2; // 将list2连接到新链表的下一个节点
list2 = list2.next; // 移动list2指针到下一个节点
} else { // 如果list1的值等于list2的值
newHead.next = list2; // 将list2连接到新链表的下一个节点
list2 = list2.next; // 移动list2指针到下一个节点
newHead = newHead.next; // 移动新链表的指针到下一个节点
newHead.next = list1; // 将list1连接到新链表的下一个节点
list1 = list1.next; // 移动list1指针到下一个节点
}
newHead = newHead.next; // 移动新链表的指针到下一个节点
} else if (list1 != null && list2 == null) { // 如果list1不为空而list2为空
newHead.next = list1; // 将list1连接到新链表的下一个节点
list1 = list1.next; // 移动list1指针到下一个节点
newHead = newHead.next; // 移动新链表的指针到下一个节点
} else if (list1 == null && list2 != null) { // 如果list1为空而list2不为空
newHead.next = list2; // 将list2连接到新链表的下一个节点
list2 = list2.next; // 移动list2指针到下一个节点
newHead = newHead.next; // 移动新链表的指针到下一个节点
}
}
return res.next; // 返回合并后的链表的头节点
}
3.2合并K个链表
public ListNode mergeKLists(ListNode[] lists){
ListNode res =null;
for (ListNode list:list){
res=mergeTwoList(res,list);
}
return res;
}
3.3LeeCode1699题目
class Solution {
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode pre1 = list1; // 第一个链表中的前一个节点
ListNode post1 = list1; // 第一个链表中的后一个节点
ListNode post2 = list2; // 第二个链表的头节点
int i = 0; // 计数器,用于记录pre1的移动次数
int j = 0; // 计数器,用于记录post1的移动次数
// 移动pre1和post1直到达到指定位置a和b
while (pre1 != null && post1 != null && j < b) {
if (i != a - 1) { // 如果i不等于a-1,则继续向后移动pre1
pre1 = pre1.next;
i++;
}
if (j != b) { // 如果j不等于b,则继续向后移动post1
post1 = post1.next;
j++;
}
}
post1 = post1.next; // 跳过第一个链表中的一段节点
// 移动post2到第二个链表的末尾
while (post2.next != null) {
post2 = post2.next;
}
pre1.next = list2; // 将第一个链表的pre1节点指向第二个链表的头节点
post2.next = post1; // 将第二个链表的末尾节点指向第一个链表的post1节点
return list1; // 返回合并后的链表
}
}
4.1LeeCode876题目
class Solution {
public ListNode middleNode(ListNode head) {
ListNode slow = head; // 慢指针,每次移动一步
ListNode fast = head; // 快指针,每次移动两步
while (fast != null && fast.next != null) {
slow = slow.next; // 慢指针向前移动一步
fast = fast.next.next; // 快指针向前移动两步
}
return slow; // 返回中间节点
}
}
4.2倒数第K个元素
要求:输入一个链表,输出该链表中倒数第k个结点,本题从1开始计数,即链表的尾结点是倒数第1个结点。
示例:
给定一个链表:1->2->3->4->5,和k=2
返回链表4->5.
这里也可以使用快慢指针,我们先将fast向后遍历到第k+1个结点,slow仍然指向链表的第一个结点,此时的指针fast与slow二者之间,刚好间隔k个结点,之后两个指针同步向后走,当fast走到链表的尾部空结点时,slow指针刚好指向链表的倒数第k个结点
public ListNode1.ListNode getKthFormEnd(ListNode1.ListNode head,int k){
ListNode1.ListNode fast=head;
ListNode1.ListNode slow=head;
while (fast != null && k > 0){
fast=fast.next;
k--;
}
while (fast != null){
fast=fast.next;
slow=slow.next;
}
return slow;
}
4.3旋转链表LeeCode61
示例
(1)思路一:将整个链表反转成{5,4,3,2,1},然后将前k和前N-k两个分别反转
(2)思路二:先用双指针策略找到倒数K的位置,也就是{1,2,3}和{4,5}两个序列,再将它们拼接成{4,5,6,1,2,3}就可以了
因为k有可能大于链表长度,所以首先获取一下链表长度len,如果k==0,则不用反转,直接返回头结点。否则:
1.快指针先走k步
2.慢指针和快指针一起走
3.快指针走到链表尾部时,慢指针所在位置刚好是要断开的地方。把快指针指向的结点练到头部,慢指针指向的结点断开和下一结点的联系。
4.返回结束时慢指针指向结点的下一结点。
代码:
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if (head == null || k == 0) {
return head;
}
ListNode temp = head;
int len = 1;
while (temp.next != null) {
temp = temp.next;
len++;
}
temp.next = head; // 将链表头尾相连,形成一个环
int stepsToNewHead = len - k % len;
for (int i = 0; i < stepsToNewHead; i++) {
temp = temp.next;
}
ListNode newHead = temp.next;
temp.next = null; // 断开环
return newHead;
}
}
5.1删除特定结点LeeCode203
public ListNode removeElements(ListNode head, int val) {
// 创建一个虚拟头节点,值为0
ListNode dummyHead = new ListNode(0);
dummyHead.next = head; // 将虚拟头节点指向原链表的头节点
ListNode cur = dummyHead; // 使用cur指针来遍历链表
// 遍历链表
while (cur.next != null) {
if (cur.next.val == val) { // 如果当前节点的下一个节点的值等于目标值val
cur.next = cur.next.next; // 则将当前节点的下一个节点跳过,指向下下个节点,即删除了目标值的节点
} else {
cur = cur.next; // 否则,继续遍历下一个节点
}
}
return dummyHead.next; // 返回处理后的链表(虚拟头节点的下一个节点即为新的头节点)
}
5.2删除特定结点LeeCode19
方法一:计算链表长度
首先从头结点开始对链表进行一次遍历,得到链表的长度L。随后我们再从头结点开始对链表进行一次遍历,当遍历到第L-n+1个结点,他就是我们需要删除的结点。代码如下:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 创建一个虚拟头结点,方便处理边界情况
ListNode dummy = new ListNode(0);
dummy.next = head;
// 获取链表长度
int length = getLength(head);
// 定义一个指针 cur,初始指向虚拟头结点
ListNode cur = dummy;
// 移动 cur 指针到倒数第 n+1 个节点
for (int i = 1; i < length - n + 1; ++i) {
cur = cur.next;
}
// 删除倒数第 n 个节点
cur.next = cur.next.next;
// 返回修改后的链表头结点
ListNode ans = dummy.next;
return ans;
}
// 计算链表的长度
public int getLength(ListNode head) {
int length = 0;
while (head != null) {
++length;
head = head.next;
}
return length;
}
}
方法二:双指针
定义first和second两个指针,firt先走N步,然后second再开始走,当first走到队尾的时候,second就是我们要的结点。代码如下:
public ListNode removeNthFromEnd(ListNode head, int n) {
// 创建一个虚拟头结点,方便处理边界情况
ListNode dummy = new ListNode(0);
dummy.next = head;
// 定义两个指针,分别为 first 和 second
ListNode first = head; // first 指针先向前移动 n 步
ListNode second = dummy; // second 指针初始化为虚拟头结点
// 移动 first 指针到第 n 个节点
for(int i = 0; i < n; i++){
first = first.next;
}
// 同时移动 first 和 second 指针,直到 first 到达链表末尾
while(first != null){
first = first.next;
second = second.next;
}
// 删除倒数第 n 个节点
second.next = second.next.next;
// 返回修改后的链表头结点
ListNode ans = dummy.next;
return ans;
}
5.3.1删除重复元素,重复元素只保留一个LeetCoede83
public ListNode deleteDuplicates(ListNode head) {
// 如果链表为空,直接返回
if (head == null) {
return head;
}
ListNode cur = head; // 使用cur指针遍历链表,从头节点开始
while (cur.next != null) { // 循环直到当前节点的下一个节点为空(即到达链表末尾)
if (cur.val == cur.next.val) { // 如果当前节点的值等于下一个节点的值,表示有重复元素
cur.next = cur.next.next; // 跳过当前节点的下一个节点,指向下下个节点,即删除了重复的节点
} else {
cur = cur.next; // 否则,继续遍历下一个节点
}
}
return head; // 返回处理后的链表
}
5.3.2删除重复元素,重复元素都不要LeetCoede82
public ListNode deleteDuplicates(ListNode head) {
// 如果链表为空,直接返回
if (head == null) {
return head;
}
ListNode dummy = new ListNode(0, head); // 创建一个虚拟头节点(dummy),值为0,并将其指向原链表的头节点
ListNode cur = dummy; // 使用cur指针来遍历链表
while (cur.next != null && cur.next.next != null) {
// 如果当前节点的下一个节点和下下个节点的值相同
if (cur.next.val == cur.next.next.val) {
int x = cur.next.val; // 记录重复的值
// 循环跳过所有值为x的节点
while (cur.next != null && cur.next.val == x) {
cur.next = cur.next.next; // 跳过当前节点,指向下一个节点
}
} else {
cur = cur.next; // 否则,继续遍历下一个节点
}
}
ListNode res = dummy.next; // 获取处理后的链表(虚拟头节点的下一个节点即为新的头节点)
return res;
}