1.删除排序链表的重复元素(82-中)
题目描述:存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。返回同样按升序排列的结果链表。
示例:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
思路:本题通过递归和迭代实现,高频面试题。
法1:递归(自上而下):
- 递归函数:删除全部重复节点
- 终止条件:
head == null || head.next == null
(没有元素或只有一个元素,直接返回) - 递归逻辑:比较当前值和下一个节点的值,值相同,记录第一个重复的元素,找到下一个值不同的元素继续递归;值不同,直接连接,继续下一个节点。
法2:迭代实现,由于链表元素是升序的,所以我们进行一次遍历,就能删除链表中的重复元素。具体如下:
- 如果cur.next与cur.next.next对应的元素不同,那么直接移动cur指针
- 否则,记录cur.next这个值为x,从cur.next开始,将值等于x节点全部删除。注意:递归中我们是直接舍弃重复元素(从不重复的元素开始),这里重复元素应该从头结点开始遍历(虚拟节点下一个节点)!
注意:由于链表的头节点也可能被删除,所以我们可以使用一个虚拟节点指向链表的头结点(这与83题不同,节点元素唯一)
// 代码1:递归
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
if (head.val == head.next.val) {
ListNode temp = head.next;
while (temp != null && head.val == temp.val) {
// 删除重复节点
temp = temp.next;
}
return deleteDuplicates(temp);
} else {
head.next = deleteDuplicates(head.next);
}
return head;
}
// 代码2:迭代
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return head;
}
ListNode dummyHead = new ListNode();
dummyHead.next = head;
ListNode cur = dummyHead;
while (cur.next != null && cur.next.next != null) {
if (cur.next.val == cur.next.next.val) {
int x = cur.next.val;
while (cur.next != null && cur.next.val == x) {
// 删除重复节点
cur.next = cur.next.next;
}
} else {
cur = cur.next;
}
}
return dummyHead.next;
}
2.删除排序链表的重复元素(83-易)
题目描述:删除一个链表中所有重复的元素,保证出现元素的唯一性。与上一题不同的是本题相同元素保留1个。注意链表已经排序。
示例:
输入: 1->1->2
输出: 1->2
思路:本题通过递归和迭代实现。
法1:递归(自下而上):
- 递归函数:找到不重复(元素唯一)链表头结点
- 终止条件:
head == null || head.next == null
(没有元素或只有一个元素) - 递归逻辑:比较当前值和下一个节点的值,若相同则返回当前节点的下一个节点,否则返回当前节点。
法2:迭代实现:定义两个指针,找到重复元素的区间:
- pre指针记录重复节点区间的开始;
- cur指针记录重复节点区间的结尾,cur.next是下一个不重复的元素;
- 更新pre和cur(保证相邻节点值不同)。
// 代码1:递归
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) return head;
head.next = deleteDuplicates(head.next); //节点连接
return head.val == head.next.val ? head.next : head;
}
// 代码2:迭代
public ListNode deleteDuplicates(ListNode head) {
ListNode cur = head, pre = head;
while (cur != null) {
while (cur.next != null && cur.val == cur.next.val) {
cur = cur.next;
}
pre.next = cur.next;
cur = cur.next;
pre = cur;
}
return head;
}
3.删除链表元素(203-易/剑指18)
题目描述:给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
思路:本题通过递归和迭代实现。
法1:递归(自下向上,类似T83):
- 递归函数:获取不含指定元素的链表
- 终止条件:
head == null
(没有元素)! - 递归逻辑:比较当前节点是不是要删除的节点。如果是继续递归下一个节点,否则进行连接,并返回。
法2:迭代实现:
- 遍历链表,如果等于给定值,删除(重新连接);否则,更新cur指针。
- 注意迭代退出的条件,因为本题不是和当前节点的下一个节点进行比较,而是给定了目标值,所以只需要保证cur.next != null即可。
ps:因为头结点可能被删除,所以我们创建虚拟节点,保证每个节点都有前驱节点,这样不用单独考虑删除头结点的情况。(这一点与T82相同)
// 代码1:递归
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return null;
}
head.next = removeElements(head.next, val);
return head.val == val ? head.next : head;
}
// 代码2:迭代
public ListNode removeElements(ListNode head, int val) {
if (head == null) return head;
ListNode dummyHead = new ListNode(val - 1);
dummyHead.next = head;
ListNode cur = dummyHead;
while (cur.next != null) {
if (cur.next.val == val) {
// 删除节点并连接
cur.next = cur.next.next;
} else {
cur= cur.next;
}
}
return dummyHead.next;
}
4.删除链表元素(237-易)
题目描述:函数参数传入要删除的节点,即在O(1)时间复杂度删除该节点。,所以不能遍历链表查找元素。
示例:
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
思路:由于题目没有给出头结点,我们无法遍历链表(遍历也不满足时间复杂度要求),更不知道要删除节点的前一个节点,这样我们就不能使用传统的方式删除。
虽然不知道前驱节点,但我们知道要删除节点的下一个节点,解决方案:将要删除的节点赋值为下一个节点的值,然后转化为删除下一个节点。例如:
[4,5,1,9], node = 5 -> [4,1,1,9], 这时就可以删除当前节点的下一个节点。
代码实现:
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
5. 删除链表的倒数第 n 个节点(19-中/剑指22)
题目描述:删除链表的倒数第 n 个节点,返回链表的头结点。进阶:使用一次遍历!
剑指22要求输出链表中的倒数第n个节点,而不是删除,其实想一下,我们想一下我们在19中删除是先找到这个待删除的点,但是现在我们找的不是倒数第n个节点,而是他的前继节点,然后做返回(类似删除),代码大同小异,自行实现。
示例:
1.删除链表倒数第n个节点
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
2.输出链表中的倒数第n个节点
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
思路:
法1:两次遍历比较简单,第一次遍历计算链表长度,第二次遍历找到该点删除(找到删除节点的上一个节点)。注意:需要单独判断删除头节点的问题!
法2:在递归求解链表长度时删除,递归的组织方式是从后向前,即当递归遍历到第n个节点就表示链表倒数第n个节点,我们需要在递归时找到倒数第n+1个节点,改变指针就可以。
法3:进阶问题:可以使用快慢指针,将倒数第n个元素对称位置作为快指针起点!!当快指针到达终点,慢指针的坐标即为待删除节点!原理:快慢指针的行程相同。
ps:假设链表长度len,那么快指针位于第n个节点位置,再走len - n到达终点,这时慢指针也走了len - n,这样我们就省去了求链表长度的过程,一次遍历即可删除。
代码:
// 代码1:两次遍历
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode cur = head;
int len = 0;
while (cur != null) {
len++;
cur = cur.next;
}
cur = head;
int k = len - n;
if (k == 0) return head.next; // 删除头结点
while (k-- > 1) {
cur = cur.next; //获取待删除的前一个节点
}
cur.next = cur.next.next;
return head;
}
//代码2:递归求解
public ListNode removeNthFromEnd_1(ListNode head, int n) {
int pos = length(head, n);
if (pos == n) return head.next; //单节点时,即删除头结点
return head;
}
private int length(ListNode node, int n) {
if (node == null) return 0;
int pos = length(node.next, n) + 1;
// 遍历到倒数第n+1个节点,即待删除节点的前一个节点
if (pos == n + 1) {
node.next = node.next.next;
}
return pos;
}
// 代码3:快慢指针(推荐,很巧妙!!)
public ListNode removeNthFromEnd_3(ListNode head, int n) {
ListNode fast = head, slow = head;
// 快指针起点(对称位置)
while (n-- > 0) {
fast = fast.next;
}
if (fast == null) return head.next; //删除倒数第n节点!(链表长度n)
while (fast.next != null) {
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return head;
}
6.移除重复节点(面试题02.01)
题目描述:编写代码,移除未排序链表中的重复节点。保留最开始出现的节点(说明相对位置不能变),保证最后返回的结果中元素唯一。
进阶:如果不得使用临时缓冲区,该怎么解决?
示例:
输入:[1, 2, 3, 3, 2, 1]
输出:[1, 2, 3]
思路:注意不能改变节点的相对顺序,所以先排序后删除的方案不可取!
法1:可以使用set进行去重,遍历链表,遇到相同的元素过滤(类似删除链表中的指定节点,但这里不是全部删除,不需要虚拟节点),但使用了额外空间 。
法2:双重循环:双指针,一个指向一个固定的值比如m,另一个从m的下一个节点开始扫描,如果遇到和m相同的节点,直接删除,时间复杂度O(n^2),效率比较低。
// 代码1:哈希表
public ListNode removeDuplicateNodes(ListNode head) {
if (head == null) {
return head;
}
Set set = new HashSet<>();
ListNode cur = head;
while (cur != null && cur.next != null) {
set.add(cur.val);
if (set.contains(cur.next.val)) {
// 已经存在该元素,直接删除
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return head;
}
// 代码2:双重循环
public ListNode removeDuplicateNodes(ListNode head) {
if (head == null) {
return head;
}
ListNode p = head;
while (p != null) {
ListNode q = p;
while (q.next != null) {
if (p.val == q.next.val) {
// 删除重复元素
q.next = q.next.next;
} else {
q = q.next;
}
}
p = p.next;
}
return head;
}