链表常见算法题总结

删除链表的一个结点

给定的核心代码函数有两种

ListNode* removeNode(ListNode* lists, ListNode* node);
void removeNode(ListNode** lists, ListNode* node);

这里我们假设被删除的节点在链表中
采用删除被删除节点下一个节点方式达到O(1)的时间复杂度

  • 第一种形式牛客上的题目常用。
  • 第二种形式是剑指offer 上链表操作的例题常用形式。

这两个函数区别产生的原因是 如果删除的节点是头节点,那么采用二重指针可以改变形参,采用一重指针方式个只能通过返回新的链表头指针。

链表节点可以分为 有前有后只有前只有后无前无后 四种

  • 删除本节点:需要知道前节点,然后将前节点next指向后节点。有前有后只有前代码相同。Time complexity O(n)
  • 删除后节点:复制后节点信息覆盖本节点,然后删除后节点, 有前有后只有后代码相同。Time complexity O(1)

一重指针代码

ListNode* removeNode1(ListNode* pListHead, ListNode* pToBeDeleted)//删除本节点
{
	if (!pListHead)
		return pListHead;
	ListNode* pNode = pListHead;
	if (pListHead == pToBeDeleted)
	{
		pNode = pNode->next;
		delete pToBeDeleted;
		return pNode;
	}
	while (pNode->next != pToBeDeleted)
	{
		pNode = pNode->next;
	}
	pNode->next = pNode->next->next;
	delete pToBeDeleted;
	return pListHead;
}

ListNode* removeNode2(ListNode* pListHead, ListNode* pToBeDeleted)//删除next节点
{
	if (!pListHead)
		return pListHead;
	ListNode* pNext = pToBeDeleted->next;
	if (pNext){									// 有后
		pToBeDeleted->val = pNext->val;
		pToBeDeleted->next = pNext->next;
		delete pNext;
	}else if(pListHead == pToBeDeleted) {		// 无前 无后
		delete pToBeDeleted;
		return nullptr;
	}else{
		ListNode* pNode = pListHead;
		while (pNode->next != pToBeDeleted) {	// 有前 无后
			pNode = pNode->next;
		}
		pNode->next = pToBeDeleted->next;
		delete pToBeDeleted;
	}
	return pListHead;
}

二重指针代码

void removeNode0(ListNode** ppListHead, ListNode* pToBeDeleted)//删除本节点
{
	if (!ppListHead || !*ppListHead)
		return;
	ListNode* pNode = *ppListHead;
	if (pNode == pToBeDeleted){
		*ppListHead = pNode->next;
		delete pToBeDeleted;
	}else{
		while (pNode->next != pToBeDeleted)
		{
			pNode = pNode->next;
		}
		pNode->next = pNode->next->next;
		delete pToBeDeleted;
	}
}
void removeNode2(ListNode** ppListHead, ListNode* pToBeDeleted)//删除next节点
{
	if (!ppListHead && !*ppListHead)
		return ;
	ListNode* pNext = pToBeDeleted->next;
	if (pNext) {							// 有后
		pToBeDeleted->val = pNext->val;
		pToBeDeleted->next = pNext->next;
		delete pNext;
	}else if (*ppListHead == pToBeDeleted) {	// 无前 无后
		delete pToBeDeleted;
		*ppListHead = nullptr;
	}else {
		ListNode* pNode = *ppListHead;
		while (pNode->next != pToBeDeleted) {// 有前 无后
			pNode = pNode->next;
		}
		pNode->next = nullptr;
		delete pToBeDeleted;
	}
}

结论

  • 删除本节点
    • 代码分支少,仅需要考虑是否删除的是头节点。
    • 和其他算法相关性高,尤其是需要遍历链表的算法,如找到倒数第K个节点并删除。
  • 删除后节点
    • 推荐记忆二重指针删除后节点形式,不用操心什么时候返回的问题。

创建一个链表

通常是由一个数组创建链表,那么由两种办法头插法、*虚拟头节点

找到链表倒是第K个节点

    ListNode* removeNthFromEnd(ListNode* pHead, int n) {//这里需要保证n小于链表长度
        ListNode* pFrontNode = pHead;
        for (int i = 0; i < n-1; i++){
            pFrontNode = pFrontNode->next;
            if (pFrontNode == nullptr){
                return nullptr;
            }
        }
        ListNode* pBehindMode = pHead;//kthR
        while (pFrontNode->next){
            pFrontNode = pFrontNode->next;
            pBehindMode = pBehindMode->next;
        }
       	return pBehindMode ;
    }

删除链表倒数第K个节点

这算法可以理解为上一题找到节点然后将对应的节点删除。变成 两个算法的组合 如下 ,这样时间复杂度是2*O(n)
删除算法选择 找到删除节点前节点,然后前节点指向后节点。带来一个问题如何删除的节点是头节点,那么就没有前节点,所以有了if逻辑分支。

    ListNode* removeNthFromEnd(ListNode* pHead, int n) {//这里需要保证n小于链表长度
        ListNode* pFrontNode = pHead;
        for (int i = 0; i < n-1; i++){//next了n-1次,完成后pFrontNode指向第n个节点
            pFrontNode = pFrontNode->next;
            if (pFrontNode == nullptr){
                return nullptr;
            }
        }
        ListNode* pBehindMode = pHead;//kthR
        while (pFrontNode->next){
            pFrontNode = pFrontNode->next;
            pBehindMode = pBehindMode->next;
        }
        // 这里开始组合删除算法,下面是删除对应节点
        if (pBehindMode == pHead){
            pBehindMode  = pBehindMode->next;
            delete pHead;
            return pBehindMode  ;
        }else {
            ListNode* pPreBehindMode = pHead;
            while (pPreBehindMode->next != pBehindMode){
                pPreBehindMode = pPreBehindMode->next;
            }
            pPreBehindMode->next = pBehindMode->next;
            delete pBehindMode;
            return pHead;
        }
    }

是否可以只扫描一遍呢?也就是说能否在第一便扫描时候就找到要删除节点的头一个节点呢?意思就是找到倒数K+1个节点,然后删除next节点。for循环中的n-1 如果变成 n 就可以了,但是有几个边界条件要考虑到:

  • 原来的代码考虑到了链表长度小于n的情况,返回了空指针。n-1 -> n后, 对于长度时n的链表会报错,需要处理。
    就有如下代码
    ListNode* removeNthFromEnd(ListNode* pHead, int n) {
        ListNode* pFrontNode = pHead;
        for (int i = 0; i < n - 1;i++) { //next了n-1次,完成后pFrontNode指向第n个节点
            pFrontNode = pFrontNode->next;
            if (pFrontNode == nullptr) {
                return nullptr;
            }
        }
        pFrontNode = pFrontNode->next;
        if (!pFrontNode) { //链表长度为n;
            ListNode* pNewHead = pHead->next;
            delete pHead;
            return pNewHead;
        }
        ListNode* pPreBehindNode = pHead;
        while (pFrontNode->next) {
            pFrontNode = pFrontNode->next;
            pPreBehindNode = pPreBehindNode->next;
        }
        auto pTemp = pPreBehindNode->next;
        pPreBehindNode->next = pPreBehindNode->next->next;
        delete pTemp;
        return pHead;
    }

反转链表

上面是我第一次写的,下面是参考别人代码后的标准答案。

    ListNode* ReverseList(ListNode* pHead) {
	/*
		if(!pHead)
			return nullptr;
	    if(!pHead->next)
	    	return pHead;
	    ListNode* pNewHead = pHead;
	    ListNode* pNode = pHead ->next;
	    pHead = pHead->next->next;
	    pNewHead->next = nullptr;
	    while (pHead) {
	    	pNode->next = pNewHead;
	    	pNewHead = pNode;
	    	pNode = pHead;
	    	pHead = pHead->next;
	    }
	    pNode->next = pNewHead;
	    pNewHead = pNode;
	*/

		ListNode* pNewHead = nullptr;
		ListNode* pNode = pHead;
		ListNode* pNext = nullptr;
		while(pNode)
		{
			pNext = pNode->next;
			pNode->next = pNewHead;
			pNewHead = pNode;
			pNode = pNext;
		}
		return pNewHead;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值