今天是秋招预备队算法篇打卡第十天,今天的任务是完成两道算法题
成功完成!
问题1:删除有序链表中重复的元素-I
描述:
解题方法:
1、双指针
因为链表元素是从小到大有序的,所以重复的节点值只会是相邻节点,设置两个指针,一个指向头节点,一个指向头节点的下一个节点,两个指针相邻遍历整个链表,若双指针指向的节点值相等,则删除后一个指针,并将它移动,依次比较,直到将链表遍历完全
1)判断链表是否为空
2)设置左右指针,分别指向头节点和头节点的下一个节点
3)一步一步移动左右指针,并且比较节点值
4)若相等,删除右指针指向的节点,将右指针指向下一个节点,若不等,则同时移动左右指针
5)直到将链表遍历完全,返回头节点
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode deleteDuplicates (ListNode head) {
// write code here
if(head == null){
return null;
}
//左右指针
ListNode left = head;
ListNode right = head.next;
//遍历链表
while(right != null){
//比较节点值
if(left.val == right.val){
//移动右指针到下一个节点
right = right.next;
//删除右指针指向的节点
left.next = right;
continue;
}
//不相等,则同时移动左右指针
left = right;
right = right.next;
}
return head;
}
}
时间复杂度:O(n),其中nnn为链表长度,遍历一次链表
空间复杂度:O(1),常数级指针变量使用,没有使用额外的辅助空
2、递归
采用递归的思想求解。每次递归则需要比较并删除重复的元素。本次的思想跟递归逆置链表很像,唯一的不同就是处理逻辑不同。删除重复链表的处理逻辑是每次都要删除当前节点(如果当前节点与下一个节点相同时候)
ListNode* deleteDuplicates(ListNode* head) {
//节点不存在或者只有一个节点时,递归结束
if (head == nullptr || head->next == nullptr)
return head;
//递归体,每次递归从下一个节点开始
head->next = deleteDuplicates(head->next);
//比较当前元素和下一个元素的值是否相等,相等则直接删除当前节点
if (head->val == head->next->val)
//head指针指向下一个节点,表明当前节点丢失,已经不再属于这个链表
head = head->next;
//返回本次递归的结果
return head;
}
时间复杂度:O(n),递归n次,因此时间复杂度为O(N)
空间复杂度:O(n),每次递归都需要一个额外的变量保存,因此空间复杂度为O(N)
3、外部存储
利用栈或者数组将整个链表存储,比较节点值,如果相等,则删除,只保留一个相同元素节点,若不等,则进行遍历数组或者出栈,直到所有节点都比较完全。
问题2:删除有序链表中重复的元素-II
描述:
解题方法:
1、双指针
链表为升序排序,所以重复元素只会是连续的,要删除重复的所有元素,那么就要保留重复元素的前一个节点,设置两个指针,遍历链表节点,如果出现重复元素,那么就将重复元素删除,直到将链表遍历完全,此方法的主要难点就是如何删除全部重复节点
1)判断链表是否为空或者只有一个节点
2)设置虚拟头节点,左右双指针和重复元素的前一个节点的指针
3)使用左右双指针一步一步遍历链表,比较节点元素值
4)若重复,则删除,将重复元素的前一个节点的指针指向最后一个重复元素的下一个节点
5)若无重复,则依次循环移动左右指针
6)返回虚拟头节点的下一节点
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode deleteDuplicates (ListNode head) {
// write code here
if(head == null || head.next == null){
return head;
}
//虚拟头节点
ListNode dummy = new ListNode(-1);
dummy.next = head;
//前指针
ListNode pre = dummy;
//左右双指针
ListNode left = head;
ListNode right = head.next;
//遍历链表
while(right != null){
//左右指针节点值相等,删除重复节点
if(left.val == right.val){
right = right.next;
//right节点存在,并且与left节点值相等,移动right节点,直到right为null或者与left不相等
while(right != null && left.val == right.val){
right = right.next;
}
//将left移动到节点值不等的right处,可以通过pre.next=left删除这段重复节点
left = right;
//如果是因为right节点值与left节点值不等,则同时移动right指针
if(right != null){
right = right.next;
}
}
else{
//当左右指针结点值不等时,将left指针的节点连接在链表上
pre.next = left;
pre = pre.next;
left = left.next;
right = right.next;
}
//此处要注意,如果链表最后的一段链表是重复的,那么通过上面的判断,right会来到尾结点的下一节点,即为null,那么while循环无法执行,无法通过else删除这段链表,所以直接将新链表的尾结点指向left以删除这段链表
pre.next = left;
}
return dummy.next;
}
}
代码较为冗余,可以进行一定的优化,优化后的代码如下:
public ListNode deleteDuplicates(ListNode head) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode prev = dummy;
ListNode curr = head;
while (curr != null && curr.next != null) {
//当遇到当前节点值和下一节点值相等的节点时,进行while循环找到下一个不相等的节点,挂到prev节点上
if (curr.val == curr.next.val) {
ListNode temp = curr.next;
while (temp != null && temp.val == curr.val) {
temp = temp.next;
}
//删除重复节点
prev.next = temp;
//移动节点,进入下一个判断循环
curr = temp;
} else {
//当节点值不重复时,将节点并入新链表
prev = prev.next;
curr = curr.next;
}
}
return dummy.next;
}
时间复杂度:O(n),遍历一遍链表,链表长度为n
空间复杂度:O(1),使用常数个辅助节点
2、使用辅助空间
遍历链表,将链表节点存入数组或者栈,判断节点是否有重复值,若有,则全部删除,若无,则并入新链表。