前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go 常见算法面试题篇(二):在 O(1) 时间内删除单链表结点

Go 常见算法面试题篇(二):在 O(1) 时间内删除单链表结点

作者头像
学院君
发布2023-03-03 14:12:30
3670
发布2023-03-03 14:12:30
举报
文章被收录于专栏:学院君的专栏学院君的专栏

题目

继续看一个来自《剑指 Offer》的链表题:

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

我们知道,单向链表删除一个结点,通常的做法是从链表的头结点开始,顺序查找所有结点,直到找到要删除的结点并删除,因此,长度为 n 的链表删除结点的整体时间复杂度是 O(n),但是题目要求时间复杂度为 O(1),该怎么实现呢?在继续往下看之前,你不妨先想一想,看看有没有思路。

如果你对单向链表的概念不太熟悉,可以翻到前面的链表教程去复习下。

核心思路

按照传统的思路,删除一个单链表的时间复杂度是 O(n),之所以要从头指针开始遍历,是为了找到被删除结点的前驱结点,然后将前驱结点的指针指向被删除结点的下一个结点,从而完成删除结点的工作。

但是细想一下,这个时间复杂度是可以优化的,在这个题目中,我们已经知道被删除结点的信息,这样就可以获知被删除结点的下一个结点,要完成删除工作,只需要把下一个结点的值复制到待删除结点,然后把待删除结点的指针指向下一个结点指针指向的结点,这样一来,也就达成了删除待删除结点的目的,这个思路是不是很巧妙?

其实,这个思路在前面介绍平衡二叉树节点删除实现的时候也用到过,知识积累到一定程度,都是触类旁通的。

显然,这种只需要获取待删除结点的下一个结点的时间复杂度是 O(1)。不过我们考虑问题还要全面:如果待删除结点是头结点或者中间结点,存在下一个结点的情况下可以这么做;如果待删除结点是尾结点,没有下一个结点呢?这个时候还是要按照遍历所有结点的思路找到前驱结点来做。在这种极端情况下,时间复杂度仍然是 O(n),不过这只是极端特殊情况,就整体平均而言,时间复杂度就是 O(1)。

实现代码

有了上面的思路,写起实现代码来也就简单了。为了提升代码复用率,我们将单链表反转教程中编写的单链表节点类和遍历方法抽取到独立的 Go 文件 linked_list.go

代码语言:javascript
复制
package main

import (
    "fmt"
)

// Node 单链表结点类
type Node struct {
    data interface{}
    next *Node
}

// 遍历单链表
func (head *Node) traverse() {
    node := head
    for node != nil {
        fmt.Printf("%v ", node.data)
        node = node.next
    }
}

然后在同级目录下创建一个 delete.go 定义结点删除方法:

代码语言:javascript
复制
// 在 O(1) 时间内删除单链表结点
// Author: 学院君
package main

import (
    "errors"
    "fmt"
)

// 删除指定单链表节点
func (head *Node) delete(node *Node) error {
    if head == nil || node == nil {
        return errors.New("链表或待删除结点为空")
    }
    // 如果待删除结点是尾结点,还是要遍历所有结点,此时时间复杂度是 O(n)
    if node.next == nil {
        preNode := head
        for preNode.next != node {
            preNode = preNode.next
        }
        // 找到待删除结点的前驱结点,将指针置空
        preNode.next = nil
        return nil
    }

    // 如果待删除结点是头结点,处理最简单,此时时间复杂度为 O(1)
    if node == head {
        head.next = nil
        return nil
    }

    // 既不是头结点也不是尾结点
    // 将后驱结点值和指针拷贝给待删除结点,再将下一个结点删除,此时时间复杂度为 O(1)
    nextNode := node.next
    node.data = nextNode.data
    node.next = nextNode.next
    return nil
}

最后编写一段测试代码,验证删除结点是否成功:

代码语言:javascript
复制
func main() {
    // 初始化链表结构
    firstNode := &Node{data: 0}
    secondNode := &Node{data: 1}
    firstNode.next = secondNode
    thirdNode := &Node{data: 2}
    secondNode.next = thirdNode
    thirdNode.next = nil

    // 测试删除某个结点并遍历打印删除结点后的单链表
    linkedList := firstNode  // 链表头节点指向 firstNode
    err := linkedList.delete(secondNode)
    if err != nil {
        fmt.Println(err)
    } else {
        linkedList.traverse()
        fmt.Println()
    }
}

执行测试代码,打印结果如下,表明删除成功:

(本文完)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 极客书房 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 题目
  • 核心思路
  • 实现代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档