Python技巧之双指针

1. 引言

最近业务刷了一些​​leetcode​​上的题目,遇到好多可以用双指针技术来快速解决的题目。这里对双指针技术做个归纳,方便后续查漏补缺。

闲话少说,我们直接开始吧!

2. 双指针的引入

双指针技术是一种允许我们通过利用一些排序数据来优化算法运行时间和空间效率的技术。它通常应用于数组和链表。 该技术可以归纳为以下三个步骤:

  • 初始化:初始状态下我们的指针可以在任何位置,这主要取决于我们需要达到的目标。
  • 移动:这一步决定我们如何向解决方案靠拢。通常情况下双指针可以沿同一方向或相反方向移动。
  • 终止条件:这决定了我们何时停止指针的移动。

为了加深大家的理解,这里我们来看几个具体的例子吧!

3. 判断回文字符串

题目描述:

给我们一个字符串作为输入,如果该字符串是回文字符串,则返回true,否则返回false。回文是一个单词、数字、短语或其他字符序列,其前后读音相同。

解决方案:

def isPalindrome(str):
i = 0
j = len(str) -1
while i<j:
if str[i] != str[j]:
return False
i += 1
j -= 1
return True

我们使用了双指针的思想解决了上述问题,上述三个步骤如下:

  • 初始化:在前2行中,我们定义了两个指针的初始位置,其中​​i​​在开头,​​j​​在结尾。
  • 移动:在第6行和第7行中,我们的两个指针将朝相反的方向移动,第一个指针​​i​​朝前移动,第二个指针​​j​​朝相反的方向移动。
  • 终止条件:当​​i>j​​时,遍历将停止,因为当​​i​​递增,​​j​​递减时,​​i​​从起始处开始,​​j​​在结束处,在数组的中间位置时,​​i​​将大于​​j​​,此时遍历结束。

4. 链表中的中间元素

题目描述:

给定单链表的头指针,返回该链表的中间节点。

4.1 暴力解决方案

链表的缺点在于不能通过下标访问对应的元素。因此我们可以考虑对链表进行遍历,同时将遍历到的元素依次放入数组 A 中。如果我们遍历到了 N 个元素,那么链表以及数组的长度也为 N,对应的中间节点即为 A[N/2]。

代码如下:

def middleNode(self, head: ListNode) -> ListNode:
A = [head]
while A[-1].next:
A.append(A[-1].next)
return A[len(A) // 2]

复杂度分析:

  • 时间复杂度:O(N),其中 NN 是给定链表中的结点数目。
  • 空间复杂度:O(N),即数组 A 用去的空间。

4.2 快慢指针解决方案

解题思路:

我们可以使用两个指针​​slow ​​与 ​​fast​​ 一起来遍历链表。其中​​slow​​ 一次走一步,​​fast​​ 一次走两步。那么当 ​​fast​​ 到达链表的末尾时,​​slow​​ 必然位于中间位置。

代码如下:

def middleNode(self, head: ListNode) -> ListNode:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow

上述代码中,使用了往同一个方向移动的两个指针,同样涉及三个通用步骤。如下:

  • 初始化:两个指针将从同一位置开始,即链表的开头。
  • 移动:它们将朝着相同的方向移动,但第二个比第一个快。
  • 终止条件:当移动更快的指针到达链表的末尾时,遍历将停止。由于更快的指针的移动速度是慢指针的两倍,当它到达末端时,慢指针将位于中间。

具体过程,图示如下:

Python技巧之双指针

复杂度分析:

  • 时间复杂度:O(N),其中 NN 是给定链表的结点数目。
  • 空间复杂度:O(1),只需要常数空间存放 ​​slow ​​​和 ​​fast​​ 两个指针。

5. 总结

双指针技术可以帮助大家减少算法的运行时间,我们可以将其与数组和链表一起使用。指针不一定从同一位置开始,也不一定朝同一方向移动,停止条件需要根据查找的内容来进行定义。

嗯捏,就酱~

发表评论

相关文章