删除链表的一个结点
给定的核心代码函数有两种
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;
}