两种算法都有两个指针,但是滑动窗口关注两个点之间的区间,双指针主要关注两个点。然而,实质上,滑动窗口是双指针的子概念,甚至可以将滑动窗口归为双指针问题,因为双指针的概念范畴更大,可以更加灵活得解决问题。
使用双指针的情况:数组是可以遍历的情况,同时经过两个指针的移动可以将问题化为子问题缩小范围。
双指针关注左右两端的计算结果和目标的比较,通过条件,判断是否移动指针。
while 的条件通常是 L 小于 R,让指针遍历整个数组。
问题一:回文判断问题,一个字符串,判断是否是回文。代码如下。逻辑十分简单,从字符串的两端开始进行检查,当满足条件(左边指针在右边指针的左边,简单而符合逻辑的判断条件)的情况下,不断地进行判断两端的字符串是否相等,如果不想等就返回False,每次判断后,都对两侧指针进行一个步长的移动。所有的检查都能通过的情况,则返回True。
时间复杂度是O(n),因为整个代码过程,遍历了字符串一次。
def isPalindorme(word):
L, R = 0, len(word) - 1
while L < R:
if word[L] != word[R]:
return False
L += 1
R -= 1
return True
问题二:给定一个有序的数组,返回两个元素的索引位置,满足两个元素的和等于target数字。假设只有一个解答。代码如下。和上面的回文判断一样,初始化条件和判断条件一样,只有在处理过程上有小的不同,当左右两端指针的元素之和比目标大,则移动右指针,反之移动左指针。因为nums是一个有序的数组,所以可以很方便的进行和的缩放,如果不是有序数组,则另外需要处理。
时间复杂度也是O(n)。
def targetSum(nums, target):
L, R = 0, len(nums) - 1
while L < R:
if nums[L] + nums[R] > target:
R -= 1
elif nums[L] + nums[R] < target:
L += 1
else:
return [L, R]
问题三:单链表,移除倒数第n个node,返回head的问题
很经典:步骤如下
from linked_list import LinkedList
from print_list import print_list_with_forward_arrow
def remove_nth_last_node(head, n):
right = head
left = head
for i in range(n):
right = right.next
if not right:
return head.next
while right.next:
right = right.next
left = left.next
left.next = left.next.next
return head
# Driver code
def main():
lists = [[23, 89, 10, 5, 67, 39, 70, 28], [34, 53, 6, 95, 38, 28, 17, 63, 16, 76], [288, 224, 275, 390, 4, 383, 330, 60, 193],
[1, 2, 3, 4, 5, 6, 7, 8, 9], [69, 8, 49, 106, 116, 112, 104, 129, 39, 14, 27, 12]]
n = [4, 1, 6, 9, 11]
for i in range(len(n)):
input_linked_list = LinkedList()
input_linked_list.create_linked_list(lists[i])
print(i+1, ". Linked List:\t", end='')
print_list_with_forward_arrow(input_linked_list.head)
print()
print("n = ", n[i])
result = remove_nth_last_node(input_linked_list.head, n[i])
print("Updated Linked List:\t", end='')
print_list_with_forward_arrow(result)
print()
print("-"*100)
if __name__ == '__main__':
main()
附录:LinkedList.py
class LinkedListNode:
# __init__ will be used to make a LinkedListNode type object.
def __init__(self, data, next=None):
self.data = data
self.next = next
# Template for the linked list
class LinkedList:
# __init__ will be used to make a LinkedList type object.
def __init__(self):
self.head = None
# insert_node_at_head method will insert a LinkedListNode at head
# of a linked list.
def insert_node_at_head(self, node):
if self.head:
node.next = self.head
self.head = node
else:
self.head = node
# create_linked_list method will create the linked list using the
# given integer array with the help of InsertAthead method.
def create_linked_list(self, lst):
for x in reversed(lst):
new_node = LinkedListNode(x)
self.insert_node_at_head(new_node)
# __str__(self) method will display the elements of linked list.
def __str__(self):
result = ""
temp = self.head
while temp:
result += str(temp.data)
temp = temp.next
if temp:
result += ", "
result += ""
return result
附录:print_list_with_forward_arrow.py
# Template for printing the linked list with forward arrows
def print_list_with_forward_arrow(linked_list_node):
temp = linked_list_node
while temp:
print(temp.data, end=" ") # print node value
temp = temp.next
if temp:
print("→", end=" ")
else:
# if this is the last node, print null at the end
print("→ null", end=" ")
一条字符串是一条英文语句,踢掉里面除了数字,和字母的其他字符,然后进行判断,是非常典型的双指针题目。
class Solution(object):
def isPalindrome(self, s):
"""
:type s: str
:rtype: bool
"""
# 对字符串进行处理,去掉除了字母和数字以外的字符
s = "".join(char for char in s if char.isalpha()
or char.isdigit()).lower()
# 设定左右指针在头尾
L, R = 0, len(s) - 1
# 判断当左指针小于右边的指针的时候,while循环
while L < R:
# 当发现左指针的字符和右边的不一样的时候,返回false
if s[L] != s[R]:
return False
# 当条件尚且满足的时候,移动左右指针,缩小范围
L += 1
R -= 1
# 一切可以顺利结束的话,返回true
return True
给定一个 target,和一个有序数组,返回和为这个 target 的数字的 index。典型的双指针题目。解决起来应该很丝滑。
class Solution(object):
def twoSum(self, numbers, target):
"""
:type numbers: List[int]
:type target: int
:rtype: List[int]
"""
80 题的简单版本,去重并且按照原先的顺序排列,返回长度 k 和前 k 是有效元素的有效数组。 两个指针同时从 0 出发,左指针控制答案顺序,负责插入,右指针负责遍历。
class Solution(object):
def removeDuplicates(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
是上面 26 题的复杂版本,可以保留一个重复,多出的就不可以了。
class Solution(object):
def removeDuplicates(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
感觉题目描述不如这张图片来的实在:
给定的数组长度想象为容器的底座,数组中的数字,是分割容器的墙壁。
根据示例:height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
那么第一个 8 的 index 是 1,最后一个 7 的 index 是 8,底边长就是 8-1,很明显。
如何确定 8 和 7 呢。看到是两个端点,自然可以想到双指针。设定一个最大初始化的容量,不断移动指针更新容量,最终找到最大容量。一开始会以为两个两个隔板之间的数字有没有什么关系,其实没有,只需要关注两个隔板的高度就可以了。
hint:如果使用双层 forloop 会被力扣网友跑出超时结果。更新左右指针的时候,如果那个数字比原本当前的高度最短的还短就没有计算的必要了。
其实我觉得这个题更像是一个滑动窗口!
class Solution(object):
def maxArea(self, height):
"""
:type height: List[int]
:rtype: int
"""
这道题真的有趣,依然是看图就能明白题意:
比方说给的这个高度数组 height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
实质上给的是一个地形图,那么就是问这地形能兜住多少的雨水。例题如图答案是 6。这也是一道很棒的题了。
hint: min(maxL, maxR) - height[i]
class Solution(object):
def trap(self, height):
"""
:type height: List[int]
:rtype: int
"""