举一反三:三种问题,两个指针,一种方法

简介: 举一反三:三种问题,两个指针,一种方法

摄影:产品经理下厨:kingname

在我们做算法题的时候,如果大家多总结解题方法,就会发现很多题目的解题方法实际上是完全一样的。今天我们就来看三道链表相关的题目。可以使用同一种方法来解决。

虽然题目中用到了指针,但我们知道,Python 是没有指针的,所以在 Python 里面,这里实际上指的是引用。不过由于“指针”这个词更加形象,所以下文我们还是会用指针来表示对一个对象的应用。

先来看我们将要解决的三道题目:

题目1:只扫描一次链表,O(1)空间复杂度,返回链表倒数第 k 个节点。

题目2:只扫描一次链表,O(1)空间复杂度,返回链表中间的节点。

题目3:空间复杂度(1),查询链表是否有环。

其中前两道题要求只能扫描链表一次。但是大家可能会有疑问,例如对于第2题,都不知道链表一共有多少个节点,怎么可能知道中间的节点是哪个?但如果提前把链表扫描一遍,知道一共有多少个节点了,又不能再次扫描链表,那么就必须把每个节点和序号都存下来,这样空间复杂度就不可能是 O(1)了。

我们先来看看第2题,找到链表中间的节点。

从下图的两个链表可以看到:

链表有 n 个节点,如果 n 为奇数,那么中间的节点在第 (n + 1) / 2个节点。如果 n 为偶数,那么中间的节点在第n / 2个节点。

这个信息怎么使用呢?我们看下面一个表格:


既然如此,如果我们在链表里面有两个指针(引用),其中一个每次移动2个节点,另一个每次移动一个节点。这样当快的指针移动到了末尾,慢的指针刚刚好指向中间的节点。

用代码来表示:

def find_mid(node):
    if not node:
        return None
    if not node.next:
        return node
    fast = slow = node
    while fast.next and fast.next.next:
        slow = slow.next
        fast = fast.next.next
    return slow

返回的 slow 就是最中间的节点。

再来看第3道题。跟第二题一样,也是一快一慢两个指针,如果链表有环,那么快的指针会绕到慢的指针的后面,然后追上来。只要看快的指针是否跟慢的指针重合,就知道是否有环了:

def find_cycle(node):
    if not node:
        return False
    slow = fast = node
    while fast:
        fast = fast.next
        if not fast:  # 快的指针到了链表末尾,说明没有环
            return False
        if fast is slow:  # 快的指针追上了慢的指针,说明有环
            return True
         fast = fast.next
         if fast is slow:
            return True
         slow = slow.next
    return False

再来看第一题。跟第二题实际上也是一样的。只不过,这次两个指针是移动速度是一样的。但是,一种一个指针先移动 k 个节点,然后两个指针再开始同时移动。这样两个指针中间始终会间隔 k 个节点。这样一来,当先走的指针到了None,后走的指针刚刚好走到倒数第 k 个节点。

不过,在解决这道题的时候,需要考虑,k 如果大于链表长度的时候,应该要返回错误信息。对应的代码如下:

def find_reverse_k(node, k):
    if not node or k == 0:
        return None
    front = behind = node
    window = 0
    while front:
        window += 1
        front = front.next
        if window == k:
            break
    else:  # while ... else 语法,如果循环正常结束,就会进入 else
        raise Exception('k 比链表长度还长!')
    while front:
        front = front.next
        behind = behind.next
    return behind

如果大家观察上面三个问题的解决代码,会发现他们都是使用了两个指针,通过两个指针之间的节点差来解决问题的。

目录
相关文章
|
4月前
|
JavaScript 前端开发 开发者
改变this指针的三个方法?
改变this指针的三个方法?
16 0
|
7月前
|
存储 搜索推荐 Serverless
用指针和动态内存分配的方法输入10,2,30, 4,5,按输入顺序逆置排序,输出排序后的元素,即输出5,4,30,2,10
用指针和动态内存分配的方法输入10,2,30, 4,5,按输入顺序逆置排序,输出排序后的元素,即输出5,4,30,2,10
30 0
|
2月前
|
存储 安全 编译器
C++智能指针:更简单、更高效的内存管理方法
C++智能指针:更简单、更高效的内存管理方法
16 0
|
7月前
|
C语言
C语言之字符串的连接使用指针和调用函数两种方法
C语言之字符串的连接使用指针和调用函数两种方法
206 0
|
3月前
|
编译器 C语言 C++
C++类和对象的细节原理:this指针、构造函数和析构函数、深浅拷贝、运算符重载、初始化列表、类的各种成员和方法
C++类和对象的细节原理:this指针、构造函数和析构函数、深浅拷贝、运算符重载、初始化列表、类的各种成员和方法
38 0
|
3月前
利用指针方法
利用指针方法。
17 1
|
4月前
|
存储 搜索推荐 Serverless
用指针和动态内存分配的方法输入10,2,30, 4,5,按输入顺序逆置排序,输出排序后的元素,即输出5,4,30,2,10
用指针和动态内存分配的方法输入10,2,30, 4,5,按输入顺序逆置排序,输出排序后的元素,即输出5,4,30,2,10
18 0
|
8月前
|
编译器 程序员 Go
Go指针VS值的方法规则
Go指针VS值的方法规则
|
9月前
|
搜索推荐
[数据结构 -- 手撕排序算法第六篇] 递归实现快速排序(集霍尔版本,挖坑法,前后指针法为一篇的实现方法,很能打)2
[数据结构 -- 手撕排序算法第六篇] 递归实现快速排序(集霍尔版本,挖坑法,前后指针法为一篇的实现方法,很能打)2
|
9月前
|
搜索推荐
[数据结构 -- 手撕排序算法第六篇] 递归实现快速排序(集霍尔版本,挖坑法,前后指针法为一篇的实现方法,很能打)1
[数据结构 -- 手撕排序算法第六篇] 递归实现快速排序(集霍尔版本,挖坑法,前后指针法为一篇的实现方法,很能打)1