Leetcode刷题:剑指offer【面试题18 删除链表的节点】

文章目录

  • 思路 1:双指针
  • 思路 2:单指针
  • 剑指 offer 原题 1:删除链表的节点
  • 剑指 offer 原题 2:删除链表中重复的节点

【面试题18 删除链表的节点】

难度: 简单
说明: 题目保证链表中节点的值互不相同

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

Leetcode题目对应位置: 面试题18:删除链表的节点

思路 1:双指针

删除单链表节点分 3 种情况:

  • 删除第一个节点,即头节点,此时直接将 head 指向链表第二个节点即可
  • 删除最后一个节点,此时链表倒数第二个节点指向 null
  • 删除中间节点,需要将被删除节点的前一个节点的指针指向被删除节点的下一个节点

其实只有删除头节点需要进行特殊处理,其他两种情况都可以进行统一解决。另外,由于题设条件是所有节点值互不相同,所以只要碰到与目标值相同的节点,即为需要删除的节点。

对于中间节点被删除的情况,需要让其前一个节点的指针指向其后一个节点。对于单链表,则需要有一个指针指向当前节点的前一个节点,来辅助删除操作。

代码逻辑:

  • 特殊情况处理:若需要被删除的是头节点,则直接将 head 指向链表第二个节点,即 head->next
  • 初始值设置:让指针 pre 指向头节点,指针 cur 指向链表第二个节点
  • 循环结束条件:当指针 cur 为空 或 指针 cur 指向的节点值就是目标值

Python 代码:

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def deleteNode(self, head: ListNode, val: int) -> ListNode:
        if head == val: return head.next
        pre, cur = head, head.next
        while cur and cur.val != val:
            pre, cur = cur, cur.next
        if cur:
            pre.next = cur.next
        return head

C++ 代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(!head) return NULL;
        if(head->val == val) return head->next;
        ListNode* pre = head;
        ListNode* cur = head->next;
        while (cur && (cur->val != val)){
            pre = cur;
            cur = cur->next;
        }
        if(cur) pre->next = cur->next;
        return head;
    }
};

思路 2:单指针

其实双指针是不必要的,单指针即可。

C++ 代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(!head) return NULL;
        if(head->val == val) return head->next;
        ListNode* p = head;
        while(p->next && p->next->val != val){
            p = p->next;
        }
        if(p->next) p->next = p->next->next;
        
        return head;
    }
};

注意一个细节,while(p->next && p->next->val != val) 循环中的 p->next 不能省,否则对于特殊测试用例(要删除的目标值不在链表中)通不过。


剑指 offer 原题 1:删除链表的节点

在剑指 offer 上的原题要求是这样的:

在 O(1) 时间内删除链表节点。给定单向链表的头指针和一个节点指针,定义一个函数在 O(1) 时间内删除该节点。链表结点与函数定义如下:

struct ListNode
{
	int m_nValue;
	ListNode* m_pNext;
}

void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted);

按照上面的思路,如果从头节点开始顺序查找并删除,时间复杂度是 O(n),不符合本题要求。之所以需要顺序查找,是因为对于单链表 ...->h->m->n->... 来说,要删除 m,就要知道 h,并将 h 的指针指向 n,这样才能安全地删除 m。

但是!h 并不是一定得知道的,介绍一下 “乾坤大挪移” 删除法:

已知一个当前要删除的节点和其下一个节点,只需要将下一个节点的内容复制到当前节点上,然后将当前节点的指针指向下一个节点的下一个节点,就完成了目标值的删除。实际上就是用删除下一个节点来替代删除当前节点。

代码逻辑:

  • 特殊情况处理:1)若需要被删除的是头节点,直接将 head 指向 head 的下一个节点即可;2)若需要被删除的是尾节点,也就是没有下一个节点,此时必须顺序遍历单链表;3)若链表只有一个节点(既是头节点也是尾节点),则需要将头节点置为空指针;4)对于非法输出的处理。从宏观上,可以分成 3 种情况,i)只有一个节点(即删除的既是头节点也是尾节点),ii)多个节点删除尾节点,iii)删除非尾节点。
  • 只有在删除尾节点时,才需要循环遍历单链表

时间复杂度: 对于 n 个节点的单链表,前 n-1 个节点都可以在 O(1) 时间内把下一个节点的内存复制到要删除的节点并删除下一个节点;只有最后一个节点需要 O(n) 来顺序查找,因此总的时间复杂度 = [(n - 1) * O(1) + O(n)] / n,结果是 O(1)

C++ 代码:

#include 

struct ListNode
{
	int m_nValue;
	ListNode* m_pNext;
};

void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted);

void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted)
{
	/*非法输入*/
	if (!pListHead || !pToBeDeleted) return;

	/*删除非尾节点*/
	if (pToBeDeleted->m_pNext != nullptr) {
		
		ListNode* pNext = pToBeDeleted->m_pNext;
		pToBeDeleted->m_nValue = pNext->m_nValue;
		pToBeDeleted->m_pNext = pNext->m_pNext;

		delete pNext;
		pNext = nullptr;
	}
	/*只有一个节点*/
	else if (*pListHead == pToBeDeleted) {
		delete pToBeDeleted;
		pToBeDeleted = nullptr;
		*pListHead = nullptr;
	}
	/*多个节点删除尾节点*/
	else {
		ListNode* pNext = *pListHead;
		while (pNext->m_pNext != pToBeDeleted) {
			pNext = pNext->m_pNext;
		}
		pNext->m_pNext = nullptr;
		delete pToBeDeleted;
		pToBeDeleted = nullptr;
	}
}

在 O(1) 时间内删除的要求,需要基于被删除的节点一定在单链表中这个前提,解答前应该跟面试官讨论清楚这个问题。


剑指 offer 原题 2:删除链表中重复的节点

在一个排序的链表中,如何删除重复的节点?(这里意思是只要出现了重复,就将所有值为重复值的节点都删除,比如 1->2->3->3->4->4->5 删除后为 1->2->5

测试用例:

  • 功能测试:重复节点位于链表头部、中部、尾部
  • 特殊输入测试:指向链表头节点的指针为 nullptr;链表为空

根据上面列出的测试用例,可以发现头节点也可能被删除,所以函数声明时传入的参数应该是 ListNode* pHead 而不是 ListNode* pHead

#include 
#include 

struct ListNode
{
	int m_nValue;
	ListNode* m_pNext;
};

void DeleteRepeatNode(ListNode** pListHead);

void DeleteRepeatNode(ListNode** pListHead)
{
	/*非法值处理*/
	if (pListHead == nullptr || *pListHead == nullptr) return;
	ListNode* preNode = nullptr;
	ListNode* curNode = *pListHead;
	bool repeat = false;

	while (curNode != nullptr)
	{
		while (curNode->m_pNext != nullptr && curNode->m_pNext->m_nValue == curNode->m_nValue) {
			repeat = true;
			curNode->m_pNext = curNode->m_pNext->m_pNext;
		}
		
		if (repeat) {
			//链表中间重复
			if (preNode != nullptr) 
				preNode->m_pNext = curNode->m_pNext;
			//链表头重复
			else {
				*pListHead = curNode->m_pNext;
				curNode = *pListHead;
			}
			repeat = false;
		}
		else {
			preNode = curNode;
			curNode = curNode->m_pNext;
		}
	}
}

测试用例:

1234567 -> 1234567
1114567 -> 4567
1234447 -> 1237
1234577 -> 12345
1133577 -> 5
1133377 -> /

你可能感兴趣的:(今天刷题了吗)