《剑指offer》面试题18:删除链表的节点

题目一:在O(1)时间内删除链表节点。

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

C++链表节点与函数的定义如下:

struct ListNode
{
    int m_nValue;
    ListNode* m_pNext;
};
void DeleteNode(ListNode** pListHead,ListNode* pToBeDeleted);

思路:

直接删除单链表某一节点,无法在o(1)时间得到该节点的前一个节点,因此无法完成题目要求。
可以将欲删节点的后一个节点的值拷贝到欲删节点之上,删除欲删节点的后一个节点,从而可以在o(1)时间内完成删除。(对于尾节点,删除仍然需要o(n),其他点为o(1),因此平均时间复杂度为o(1),满足要求)

基于以上思路,java参考代码如下:

java链表节点的定义如下:

package chapter3;


public class P119_DeleteNodeInList {
    public static class ListNode{
        int val;
        ListNode next;
        public ListNode(int val){
            this.val=val;
            this.next=null;
        }
    }
    public static ListNode deleteNode(ListNode head, ListNode node){
        if(head==null||node==null)//注意点1:特殊输入测试
            return head;

        if(node==head){//仅有一个节点(既是头也是尾)
            return head.next;
        }
        else if(node.next==null){//要删除的是尾节点
            ListNode cur=head;
            while (cur.next!=node){//需要在node前一个节点返回
                cur=cur.next;
            }
            cur.next=null;
            return head;
        }
        else{//node.next!=null,
            node.val=node.next.val;
            node.next=node.next.next;
            return head;
        }
    }
    public static void printNode(ListNode node){
        if(node==null)
            System.out.println("no node!");
        ListNode cur=node;
        while (cur.next!=null){
            System.out.print(cur.val+"->");
            cur=cur.next;
        }
        System.out.print(cur.val);
        System.out.println();
    }
    public static void main(String[] args){
        ListNode head = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        head.next = node2;
        node2.next = node3;
        printNode(head);
        head = deleteNode(head,node3);
        printNode(head);
        head = deleteNode(head,head);
        printNode(head);
    }
}

注意点见代码注解部分。

C++书上的参考代码如下:

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* pNode=*pListHead;
        while(pNode->m_pNext!=pToBeDeleted)
        {
            pNode=pNode->m_pNext;
        }
        
        pNode->m_pNext=nullptr;
        delete pToBeDeleted;
        pToBeDeleted=nullptr;
    }
}
测试用例:

a.功能测试(从有多个节点的链表的中间删除一个节点;从有多个节点的链表中删除头节点;从有多个节点的链表中删除尾节点;从只有一个节点的链表中删除唯一的节点)。
b.特殊输入测试(指向链表头节点的为nullptr指针;指向要删除节点的为nullptr指针)。

题目二:删除链表中重复的节点。

在一个排序的链表中,如何删除重复的节点?

思路:

(1)从头到尾遍历链表,如果当前结点与下一个结点值相同,它们就是重复结点,都被删除。(2)为了保证删除之后链表没有从中间断开,需要把当前结点的前一个结点(pPreNode)和后面不重复的第一个结点(pNext)相连。
(3)同时注意头结点有可能也会被删除。

参考代码如下:

struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :val(x), next(nullptr) {}
};
class Solution {
public:
	ListNode* deleteDuplication(ListNode* pHead)
	{
		if (pHead == nullptr)
			return nullptr;
		ListNode* pNewHead = new ListNode(-1);           //申请一个新的头结点,防止原头结点重复,被删除
		pNewHead->next = pHead;
		ListNode* pPreNode = pNewHead;                      //始终指向要删除结点的前一个结点
		ListNode* pNode = pHead;                            //用来寻找要删除的结点
		ListNode* pNext = nullptr;                             //始终指向删除结点的后一个结点,防止链表断开
		while (pNode != nullptr && pNode->next != nullptr)
		{
			pNext = pNode->next;
			if (pNode->val == pNext->val)                                     //如果有重复结点
			{
				while (pNext != NULL&&pNext->val == pNode->val)    //找到重复结点后的第一个不重复结点
					pNext = pNext->next;
				pPreNode->next = pNext;                                   //删除重复结点
				pNode = pNext;
			}
			else
			{
				pPreNode = pNode;
				pNode = pNode->next;
			}
		}
		return pNewHead->next;
	}
};
测试用例:

a.功能测试(重复的节点位于链表的头部/中间/尾部;链表中没有重复的节点)。
b.特殊输入测试(指向链表头节点的为nullptr指针;链表中所有节点都是重复的)。

参考:

https://www.bbsmax.com/A/VGzlW0rOdb/
https://blog.csdn.net/YF_Li123/article/details/70228721
https://www.jianshu.com/p/5e2275757a3d

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值