举一反三:三种问题,两个指针,一种方法
在我们做算法题的时候,如果大家多总结解题方法,就会发现很多题目的解题方法实际上是完全一样的。今天我们就来看三道链表相关的题目。可以使用同一种方法来解决。
虽然题目中用到了指针
,但我们知道,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
如果大家观察上面三个问题的解决代码,会发现他们都是使用了两个指针,通过两个指针之间的节点差来解决问题的。
- Tarjan--LCA算法的个人理解即模板
- spark sql编程之实现合并Parquet格式的DataFrame的schema
- Oracle压缩黑科技(一)—基础表压缩
- 12 条用于 Linux 的 MySQL/MariaDB 安全最佳实践
- hdu----(4545)魔法串(LCS)
- Oracle压缩黑科技(二)—压缩数据的修改
- 在Pivotal Web Service上发布Spring Boot应用
- hdu---(1325)Is It A Tree?(并查集)
- spark2 sql编程样例:sql操作
- hdu----(1599)最大子矩阵(几何/dp)
- Go语言简单的TCP编程
- hdu---(1054)Strategic Game(最小覆盖边)
- Swagger Starter 1.4.0发布:新增swagger功能开源与全局参数的配置。
- Go语言语法汇总
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【tensorflow2.0】评价指标metrics
- 数据库(二)--多对多相关操作
- 【tensorflow2.0】优化器optimizers
- 数据库(三)--多对多,一对多,一对一
- 【tensorflow2.0】回调函数callbacks
- 用C++跟你聊聊“观察者模型”
- 【tensorflow2.0】构建模型的三种方法
- django实战(一)--dango自带的分页(极简)
- 线程池 -- 动态链接库
- 【tensorflow2.0】训练模型的三种方法
- 用C++跟你聊聊“建造者模式”
- 【tensorflow2.0】使用TPU训练模型
- 用C++跟你聊聊“外观模式”
- 【tensorflow2.0】使用tensorflow-serving部署模型
- 用C++跟你聊聊“模板方法模式”