转载自:https://blog.csdn.net/qq_25827845/article/details/76061004
本文总结常见面试题中关于删除和去除节点的相关问题。题目如下:
1、给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted: delete
2、去除重复节点,保留一个即可
3、去除重复节点,将重复节点全部删除
4、删除具有指定val值的节点
5、删除倒数第N个节点
我们先来看看第一题:
(1)给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted: delete
思路:通过复制下一个节点的val值到要删除的节点,而把下一个节点删除,修改了next指针,实现了O( 1 )复杂度删除指定节点。(该题的假设是要删除的节点一定存在该链表中,否则O(1)不可能实现)
/**
* 给出一单链表头指针head和一节点指针toBeDeleted,O(1)时间复杂度删除节点tBeDeleted
* 对于删除节点,我们普通的思路就是让该节点的前一个节点指向该节点的下一个节点
* ,这种情况需要遍历找到该节点的前一个节点,时间复杂度为O(n)。对于链表,
* 链表中的每个节点结构都是一样的,所以我们可以把该节点的下一个节点的数据复制到该节点
* ,然后删除下一个节点即可。要注意最后一个节点的情况,这个时候只能用常见的方法来操作,先找到前一个节点,但总体的平均时间复杂度还是O(1)
*/
public static void delete(Node head, Node toDelete){
if(toDelete == null){
return;
}
if(toDelete.next != null){ // 要删除的是一个中间节点
toDelete.val = toDelete.next.val; // 将下一个节点的数据复制到本节点!
toDelete.next = toDelete.next.next;
}
else{ // 要删除的是最后一个节点!
if(head == toDelete){ // 链表中只有一个节点的情况
head = null;
}else{
Node node = head;
while(node.next != toDelete){ // 找到倒数第二个节点
node = node.next;
}
node.next = null;
}
}
}
第二题:
(2)、去除重复节点,保留一个即可
思路:该题最后返回的结果要求保留一个重复的节点,这就比较easy了,直接判断,跳过与当前节点重复的节点即可。
public class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null||head.next==null)
return head;
ListNode cur = head;
while(cur.next!=null){
if(cur.val==cur.next.val)
cur.next = cur.next.next;
else
cur = cur.next;
}
return head;
}
}
第三题:(Hard)
(3)、去除重复节点,将重复节点全部删除
思路:将重复节点全部删除,一个不留,这就是本题的难点所在,我们必须保存当前节点的前一个节点,因为我们不知道当前节点是否是重复节点。
还有一个难点,当开头就是重复元素时,我们需要调整head,这时候需要一些判断条件。
public class Solution {
public ListNode deleteDuplication(ListNode pHead){
if(pHead==null||pHead.next==null)
return pHead;
ListNode cur = pHead;
ListNode preNode = null;
while(cur!=null){
if(cur.next!=null&&cur.val==cur.next.val){
int val = cur.val;
// 跳过重复节点
while(cur.next!=null&&cur.next.val==val)
cur = cur.next;
// 若开头即是重复元素,则更新pHead
if(preNode==null)
pHead = cur.next;
else // 反之更新preNode
preNode.next = cur.next;
}else{
preNode = cur;
}
cur = cur.next;
}
return pHead;
}
}
该代码中如下部分:
// 若开头即是重复元素,则更新pHead
if(preNode==null)
pHead = cur.next;
else // 反之更新preNode
preNode.next = cur.next;
将preNode和pHead刚开始指向同一个节点,之后将不再移动pHead,通过preNode的移动,将新的链表穿起来~
第四题:
(4)、删除具有指定val值的节点
思路:这个也不难,首先我们判断该节点是不是需要删除的节点,如果是的话,借助题目1中删除该节点的方法来搞定。判断该节点是中间节点?还是最后一个节点?如果是最后一个节点,则需要从前到后遍历链表。注意只有一个节点的情况,即要删除的节点是头结点同时也是尾节点。
public class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head==null)
return null;
if(head.next==null&&head.val==val)
return null;
if(head.next==null&&head.val!=val)
return head;
ListNode cur = head;
while(cur!=null){
// 如果是准备删除的元素
if(cur.val==val){
if(cur.next!=null){
// 要删除的不是最后一个元素
cur.val = cur.next.val;
if(cur.next.next!=null)
cur.next = cur.next.next;
else
cur.next = null;
// 直接continue
continue;
}else{
// 要删除的是最后一个元素
ListNode node = head;
// 防止出现即是第一个也是最后一个几点的情况
if(node==cur)
return null;
while(node.next!=cur){
node = node.next;
}
node.next = null;
}
}
cur = cur.next;
}
return head;
}
}
第五题:
(5)、删除倒数第N个节点
思路:链表中遇到倒数N,K 啥的问题一般都是双指针来搞定。本题,设立两个指针,一个先走N步之后,两个指针同时走,这样当前面的指针走到最后时,后面的指针走到了要删除的倒数第N个节点,我们调整该节点的指向,删除该节点即可。
public class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head==null)
return null;
ListNode slow = head;
ListNode fast = head;
while(n>0&&fast!=null){
fast = fast.next;
n--;
}
if(fast==null)
return head.next;
while(fast.next!=null){
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return head;
}
}
单链表反转
总结下面试题中常见的单链表反转:
- 常规的反转单链表
- 以K个为一组反转单链表,最后不足K个节点的部分也反转
- 以K个为一组反转单链表,最后不足K个节点的部分不反转
1、反转单链表:
代码如下:
/*
* 翻转链表(遍历)
* 从头到尾遍历原链表,每遍历一个结点,
* 将其摘下放在新链表的最前端。
* 注意链表为空和只有一个结点的情况。时间复杂度为O(n)
*/
public static ListNode reverseNode(ListNode head){
// 如果链表为空或只有一个节点,无需反转,直接返回原链表表头
if(head == null || head.next == null)
return head;
ListNode reHead = null;
ListNode cur = head;
while(cur!=null){
ListNode reCur = cur; // 用reCur保存住对要处理节点的引用
cur = cur.next; // cur更新到下一个节点
reCur.next = reHead; // 更新要处理节点的next引用
reHead = reCur; // reHead指向要处理节点的前一个节点
}
return reHead;
}
2、以K个为一组反转单链表,最后不足K个节点的部分也反转
/**
* 分组反转单链表,最后不足K个节点的部分也反转
* @param head
* @param k
* @return
*/
public static ListNode reverseKgroup(ListNode head, int k) {
if (head == null)
return head;
ListNode cur = head;
ListNode reHead = null;
int count = 0;
/* Reverse first k nodes of linked list */
while (count < k && cur != null) {
ListNode reCur = cur;
cur = cur.next;
reCur.next = reHead;
reHead = reCur;
count++;
}
/*
* cur is now a pointer to (k+1)th node Recursively call for the
* list starting from current. And make rest of the list as next of
* first node
*/
if (cur != null)
head.next = reverseKgroup(cur, k);
return reHead;
}
举例解释:
输入的原始单链表为3-5-6-9-7-2-1-12,其中K为3;
经过第一次while循环,单链表变为6-5-3-9-7-2-1-12。此时跳出while循环是因为count<k不成立了,cur节点指向了9,head节点指向了3。所以接着判断cur是否为null,若不是,则刚好递归求出head.next。
经过第二次while循环,单链表为6-5-3-2-7-9-1-12。此时跳出while循环是因为count<k不成立了,cur节点指向了1,head节点指向了9。接着判断cur,并且递归求head.next节点。
第三次循环,跳出while是因为cur==null了,直接返回reHead,此时reHead指向了12。
可以看出,K个为一组反转单链表,核心代码还是常规的如何反转单链表。
3、以K个为一组反转单链表,最后不足K个节点的部分不反转
这是一道LeetCode原题:https://leetcode.com/problems/reverse-nodes-in-k-group/#/description
思路:核心代码还是反转常规的单链表,不过此题需要加上节点数量的判断,当节点数目不足K个时,不进行反转操作,直接返回。
/**
* 分组反转单链表,最后不足K个节点的部分不反转
* @param head
* @param k
* @return
*/
public static ListNode reverseKgroups(ListNode head, int k) {
if (head == null)
return head;
ListNode cur = head;
ListNode reHead = null;
int count = 0;
if (getSize(cur) >= k) {
/* Reverse first k nodes of linked list */
while (count < k && cur != null) {
ListNode reCur = cur;
cur = cur.next;
reCur.next = reHead;
reHead = reCur;
count++;
}
/*
* cur is now a pointer to (k+1)th node Recursively call for the
* list starting from current. And make rest of the list as next of
* first node
*/
if (cur != null)
head.next = reverseKgroups(cur, k);
return reHead;
}
return cur;
}
统计节点数目函数如下:
/**
* 统计该节点之后的节点数量
* @param head
* @return
*/
public static int getSize(ListNode head) {
int count = 0;
ListNode curNode = head;
while (curNode != null) {
count++;
curNode = curNode.next;
}
return count;
}
AC结果如下:
总结:
各个形式的反转单链表,最重要的理清楚head、reHead和cur三个节点的关系,通过核心代码和方法的递归来实现分组反转。
附上完整代码:
public class Main {
public static void main(String[] args) {
ListNode head = new ListNode(3);
ListNode node1 = new ListNode(5);
ListNode node2 = new ListNode(6);
ListNode node3 = new ListNode(9);
ListNode node4 = new ListNode(7);
ListNode node5 = new ListNode(2);
ListNode node6 = new ListNode(1);
ListNode node7 = new ListNode(12);
head.next = node1;
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node6;
node6.next = node7;
printList(head);
// printList(reverseNode(head));
// printList(reverseKgroups(head, 3));
printList(reverseKgroup(head, 3));
}
// 打印链表的方法,方便test函数
public static void printList(ListNode head) {
while (head != null) {
System.out.print(head.val + " ");
head = head.next;
}
System.out.println();
}
/**
* 分组反转单链表,最后不足K个节点的部分不反转
* @param head
* @param k
* @return
*/
public static ListNode reverseKgroups(ListNode head, int k) {
if (head == null)
return head;
ListNode cur = head;
ListNode reHead = null;
int count = 0;
if (getSize(cur) >= k) {
/* Reverse first k nodes of linked list */
while (count < k && cur != null) {
ListNode reCur = cur;
cur = cur.next;
reCur.next = reHead;
reHead = reCur;
count++;
}
/*
* cur is now a pointer to (k+1)th node Recursively call for the
* list starting from current. And make rest of the list as next of
* first node
*/
if (cur != null)
head.next = reverseKgroups(cur, k);
return reHead;
}
return cur;
}
/**
* 分组反转单链表,最后不足K个节点的部分也反转
* @param head
* @param k
* @return
*/
public static ListNode reverseKgroup(ListNode head, int k) {
if (head == null)
return head;
ListNode cur = head;
ListNode reHead = null;
int count = 0;
/* Reverse first k nodes of linked list */
while (count < k && cur != null) {
ListNode reCur = cur;
cur = cur.next;
reCur.next = reHead;
reHead = reCur;
count++;
}
/*
* cur is now a pointer to (k+1)th node Recursively call for the
* list starting from current. And make rest of the list as next of
* first node
*/
if (cur != null)
head.next = reverseKgroup(cur, k);
return reHead;
}
/**
* 统计该节点之后的节点数量
* @param head
* @return
*/
public static int getSize(ListNode head) {
int count = 0;
ListNode curNode = head;
while (curNode != null) {
count++;
curNode = curNode.next;
}
return count;
}
/*
* 翻转链表(遍历) 从头到尾遍历原链表,每遍历一个结点, 将其摘下放在新链表的最前端。 注意链表为空和只有一个结点的情况。时间复杂度为O(n)
*/
public static ListNode reverseNode(ListNode head) {
// 如果链表为空或只有一个节点,无需反转,直接返回原链表表头
if (head == null || head.next == null)
return head;
ListNode reHead = null;
ListNode cur = head;
while (cur != null) {
ListNode reCur = cur; // 用reCur保存住对要处理节点的引用
cur = cur.next; // cur更新到下一个节点
reCur.next = reHead; // 更新要处理节点的next引用
reHead = reCur; // reHead指向要处理节点的前一个节点
}
return reHead;
}
}
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}