"""
two-pointers && slide-window
Medium:
到了Medium和Hard之后,有些题已经不单纯是双指针问题了。
可能有其他算法思想在其中,只不过是借用双指针/窗口的移动来解决问题
3. 无重复字符的最长子串
15. 三数之和
11. 盛最多水的容器 (这个其实不算双指针或窗口,这个本质是贪心)
"""
question_list = [3,]
"""
3. 无重复字符的最长子串
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
"""
def lengthOfLongestSubstring(s):
"""
滑动窗口题
右侧遍历移动
左侧遇到窗口内相同元素移动
左侧移动距离需要靠窗口的map来查找
"""
ret = 0
left, right = 0, 0
hm = {}
while right < len(s):
char = s[right]
if char in hm and hm[char] >= left:
left = hm[char] + 1
else:
if right - left + 1 > ret:
ret = right - left + 1
hm[char] = right
right += 1
return ret
"""
15. 三数之和
# 隐藏target=0
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
"""
def threeSum(nums):
"""
1. 先排序!!,后固定第一个元素,用左右双指针求其余两个的和
2. 注意跳过重复元素 外层第一个元素要跳,内层左右指针也要跳
"""
nums.sort()
ret = []
for i, first in enumerate(nums):
if i > 0 and first == nums[i - 1]:
continue # 第一个元素排重
target = 0 - first
left = i + 1
right = len(nums) - 1
while left < right:
sumtwo = nums[left] + nums[right]
if sumtwo < target:
left += 1
elif sumtwo > target:
right -= 1
else:
ret.append([first, nums[left], nums[right]])
left += 1
right -= 1
# 左右指针排重
while left < right and nums[left] == nums[left - 1]:
left += 1
while left < right and nums[right] == nums[right + 1]:
right -= 1
return ret
"""
11. 盛最多水的容器
"""
def maxArea(self, height):
"""
本质是贪心:面积 = (j - i) * min(height_i, height_j)。
实际是靠双指针对撞移动,每次移动短的那个(因为只有移动短的,下次的结果才有可能得到优化,这就是“贪心算法”)
"""
i, j = 0, len(height) - 1
ret = 0
while i < j:
if height[i] > height[j]:
area = (j - i) * height[j]
j -= 1
else:
area = (j - i) * height[i]
i += 1
if area > ret:
ret = area
return ret
"""
5. 最长回文子串
双指针****特殊****的一道题:双指针都起始在中间,向两个方向扩散移动。
这个题也适合DP[1][2]处理,完美符合模版,但空间复杂度不如双指针
"""
def longestPalindrome(self, s):
"""
双指针都在中心点,向两个方向移动
注意边界,切片左开右闭
"""
# 辅助方法
def expand(left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return s[left + 1:right]
ret = ""
for i in range(len(s)):
a = expand(i, i) # 奇数中心扩散的情况
b = expand(i, i + 1) # 偶数中心扩散的情况
if len(a) > len(ret):
ret = a
if len(b) > len(ret):
ret = b
return ret
"""
LCR 026. 重排链表
1. 可以先把链表装数组里之后用双指针对撞重新组合。时间O(n),空间O(n)
2. 也可以使用链表骚操作之:链表找中点(快慢指针)+ 后半段链表翻转 + 两个半链表合并, 这样的话空间复杂度是O(1),
"""
def reorderList(self, head):
"""
1. 先把链表装数组里
2. 数组左右指针对撞
"""
if not head:
return
nodes = []
cur = head
while cur:
nodes.append(cur)
cur = cur.next
i, j = 0, len(nodes) - 1
while i < j:
if i == j - 1:
nodes[i].next = nodes[j]
nodes[j].next = None
elif i == j - 2:
nodes[i].next = nodes[j]
nodes[j].next = nodes[i + 1]
nodes[i + 1].next = None
else:
nodes[i].next = nodes[j]
nodes[j].next = nodes[i + 1]
i += 1
j -= 1
return
"""
19. 删除链表的倒数第 N 个结点
这跟双指针没啥关系啊,纯纯链表操作。
"""
def removeNthFromEnd(self, head, n):
"""
先装列表,然后操作。
边界case穷举处理。
"""
nodes = []
cur = head
while cur:
nodes.append(cur)
cur = cur.next
if n > len(nodes):
return None
nxt = nodes[-n].next
if n == len(nodes):
return nxt
pre = nodes[-(n + 1)]
pre.next = nxt
return nodes[0]
# 这才是双指针解法:快慢指针。
# 1. 倒数第N个,就是还差N个到终点。
# 2. 如果a先走N个,b在开始走,a到终点的时候,b正好差N个
# 3. 注意下边界case(n可能是走了n+1, 仔细想想)
def removeNthFromEnd2(head, n):
dummy = ListNode(0, head)
i = 0
slow, fast = dummy, dummy
while i < n + 1:
fast = fast.next
i += 1
while fast:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy.next