Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TwoPointers #27

Open
wants to merge 1 commit into
base: docsify
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions TwoPointers/01-introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 双指针

作者:Junyi Li;审核:

与其说是算法,双指针是一种比较好用的工具,没有空间消耗,用好的话可以解决遇到的很多问题。同时经典的快速排序,归并排序,或者字符串翻转经常会用到它。
DukeEnglish marked this conversation as resolved.
Show resolved Hide resolved

此外,链表相关的题目由于不像数组那样可以直接访问到每一个节点,而且题目经常要求不使用外部空间来处理,在这种情况下,就经常性需要使用双指针啦~
DukeEnglish marked this conversation as resolved.
Show resolved Hide resolved

# 分类

1. 背向双指针:就是两个指针朝着相反的方向走

Longest Palindromic Substring 的中心线枚举算法

2. 相向双指针:两个指针面对面走
Two Sum 的一大类题(两位数的相关变形题)
Partition 的一大类题(quicksort,quickselect,partition)

3. 同向双指针:两个指针朝着一个方向走
滑动窗口类 Sliding Window
DukeEnglish marked this conversation as resolved.
Show resolved Hide resolved
快慢指针类 Fast & Slow Pointers

# 模版

针对以上的三种类型,都给出了模版。可是就像weiwei大佬说的[link](https://ojeveryday.github.io/AlgoWiki/#/BinarySearch/01-introduction?id=%e4%ba%8c%e5%88%86%e6%9f%a5%e6%89%be%e7%9a%84%e4%b8%89%e4%b8%aa%e6%a8%a1%e6%9d%bf),仅供参考哈。模版的意义在于可以帮助减少对某些边界条件的考虑以及对于某些问题的多想,节约脑细胞。
DukeEnglish marked this conversation as resolved.
Show resolved Hide resolved

一般来说我们都需要用到两个变量,分别表示两个idx,一般是数组或者字符串中的idx。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idx 这种缩写不知道是不是规范,可以说两个下标(索引),或者就说两个变量,问题都不大。

因为双指针就是在解决问题的过程中,使用的两个变量。


!同时要注意的是,因为双指针是一种比较基础的操作,所以很容易有一些小变种,归并排序应该就算是一个简单的变种了吧。大家掌握基础,灵活使用。
DukeEnglish marked this conversation as resolved.
Show resolved Hide resolved



NOTE:

滑动窗口应该算是双指针的一种应用,同时还可以用queue来实现。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 同上,「应该算」建议删去;
    +「同时还可以用queue来实现。」建议删去。


双指针的题目的时间复杂度都是一般On
DukeEnglish marked this conversation as resolved.
Show resolved Hide resolved





325 changes: 325 additions & 0 deletions TwoPointers/02-同向双指针.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
+ # 同向双指针

## 快慢指针类型:

快慢指针类型的题目只要想到这个思路就比较好理解。其难点一般是边界条件的判断。建议使用case来分析一下。一般我们输入测试的时候是空,1个节点,2个节点,3个节点,4个节点,5个节点。一般来说就差不多了。

#### [链表的中间结点](https://leetcode-cn.com/problems/middle-of-the-linked-list/)
DukeEnglish marked this conversation as resolved.
Show resolved Hide resolved

```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution:
def middleNode(self, head: ListNode) -> ListNode:
if not head:
return None
if not head.next:
return head
if not head.next.next:
return head.next

slow = head
quick = head
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

前面快慢指针命名用 fast,这里命名也需要统一用成 fast。


while quick.next:
slow = slow.next
quick = quick.next
if not quick.next:
return slow
quick = quick.next
if not quick.next:
return slow

return slow
```



#### [移动零](https://leetcode-cn.com/problems/move-zeroes/submissions/)
DukeEnglish marked this conversation as resolved.
Show resolved Hide resolved

这道题目有个小点要注意一下,我们将右边不为0的移动到左指针指着的位置,当且仅当这个位置和right不一致。因为这样可以避免重复写这个操作。最后再遍历left指针到最后,将值变为0。

```python
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
right = left = 0
# for left in range(len(nums)):
while right < len(nums):
if nums[right] != 0:
if right != left:
nums[left] = nums[right]
left += 1
right += 1

while left < right:
if nums[left] != 0:
nums[left] = 0
left += 1
```



## 普通的同向双指针

DukeEnglish marked this conversation as resolved.
Show resolved Hide resolved
只要满足两个指针,都只能向一个方向走,就可以用同向双指针的方法来做

- 同向双指针
- 每次删除左指针左边的数字
- 只要当前和小于s,右指针继续向右移动
- 时间复杂度O(N)

```python
特殊case

for left in range(len(nums)):
DukeEnglish marked this conversation as resolved.
Show resolved Hide resolved
# while left in len(nums) - 1: # 用这个要记得给left做变化,容易忘记。但是这个对left的操作可以更加灵活
while right<len(nums) and condition:
condition change
right+=1
if confition: # 一定要做这个判断,可能会造成一点时间浪费,但是可以在上面的取值范围内做prune
store or action
action to del left
```

难点一般在于 condition,条件判断部分。

[1456. 定长子串中元音的最大数目](https://leetcode-cn.com/problems/maximum-number-of-vowels-in-a-substring-of-given-length/)

```python
class Solution:
def maxVowels(self, s: str, k: int) -> int:

"""
给你字符串 s 和整数 k 。

请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。

英文中的 元音字母 为(a, e, i, o, u)。
"""

if not s:
return 0

letters = {"a", "e", "i", "o", "u"}
right = 0
res = 0
cnt = 0
for left in range(len(s)):
while right < len(s) and right - left + 1 <= k:
if s[right] in letters:
cnt += 1
right += 1
res = max(res, cnt)
if s[left] in letters:
cnt -= 1

return res

```



#### [最长无重复字符的子串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/)

```python
class Solution:
"""
@param s: a string
@return: an integer
"""
def lengthOfLongestSubstring(self, s):
# write your code here
"""
首先 所有这种 类似于在判断 最长或者最短的 符合某个条件的 子串 都可以使用双指针的方法来解决
DukeEnglish marked this conversation as resolved.
Show resolved Hide resolved
其难点或者说trick点在于怎么判断满足题目要求的那个条件
1. 首先是bad case和edge的判断
2. 其次要考虑到不一定是字母的输入,所以使用数组进行处理的时候其实没有字典通用
3. 本身副指针负责添加数据,主指针负责删除数据。谨记这一点
4. 边界条件一定要划分清楚,基本使用几种边界case测试一下就知道了
"""
if len(s)<=2:
return len(s)
left = 0
right = 0
res = 1

from collections import defaultdict
judge = defaultdict(int)
# "abcabcbb"
# print(ord(s[0])-97)

for left in range(len(s)):
#
while right < len(s) and judge[s[right]]==0:
judge[s[right]]=1
right+=1 # 切记
# if judge[ord(s[right])-97]==0
res = max(res, right-left)
# print(res)
judge[s[left]]=0

return res
```

#### [和大于S的最小子数组](https://www.lintcode.com/problem/minimum-size-subarray-sum/description)

```python
class Solution:
"""
@param nums: an array of integers
@param s: An integer
@return: an integer representing the minimum size of subarray
"""
def minimumSize(self, nums, s):
# write your code here
"""
是一个同向双指针的题目。
因为他需要使用的其实是两个边界来框定几个数字,然后比较这个数字和给定正整数s的大小。

第二个问题是如何确定这个框内的和,求解的过程,我们肯定不能每次都求和一次。
我们使用一个临时值保存结果,进行结果的判断。如果不满足当前条件,那么右指针就不停右移。
一旦满足,就判断当前结果是否需要满足。更换左指针进行移动。不断的判断当前的结果是否依然满足条件,满足的话就进行判断。
注意 右边的负责添加数据,左边的负责删除数据。牢记
"""
if not nums:
return -1
res = float("inf")
left = 0
right = 0
tmp = 0
for left in range(len(nums)):
while tmp <s and right<len(nums): #
tmp+=nums[right]
right+=1
if tmp>=s:
res=min(res, right-left)
tmp-=nums[left]

return res if res!=float("inf") else -1
```

#### [最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/)

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。

示例:

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:

如果 S 中不存这样的子串,则返回空字符串 ""。
如果 S 中存在这样的子串,我们保证它是唯一的答案。



```python
class Solution:
"""
@param source : A string
@param target: A string
@return: A string denote the minimum window, return "" if there is no such a string
"""
def minWindow(self, source , target):
# write your code here
"""
所有涉及到求字符串 的符合某个条件的 子串,所以对应的 长度等 都可以使用双指针
考点依然是如何判断是否满足题目要求的那个条件
1. 以及对应的边界条件如何进行判断的问题
2. 如何判断是否满足条件呢?可以使用字典进行,因为我们不能确定包含的内容是否只有字母
3. 我认为可以使用一个flag来进行判断看是否当前的值满足要求
"""

if not source or not target:
return ""
from collections import defaultdict
dict = defaultdict(int)
for i in target:
dict[i] += 1

dict2 = defaultdict(int)
right = 0
res = float("inf"), source
flag = 0
flag_t = len(dict)
for left in range(len(source)):
while right < len(source) and flag != flag_t:
cur = source[right]
if cur in dict:
dict2[cur]+=1
if dict2[cur]==dict[cur]: flag+=1
right+=1
if flag==flag_t:
if right-left<res[0]:
print(source[left:right])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print 这样的语句需要从示例代码中删除。

res=right-left, source[left:right]
del_c = source[left]
if del_c in dict:
dict2[del_c]-=1
if dict2[del_c]<dict[del_c]: flag-=1
return res[1] if res[0]!=float("inf") else ""
```



#### [340. 至多包含 K 个不同字符的最长子串](https://leetcode-cn.com/problems/longest-substring-with-at-most-k-distinct-characters/)

给定一个字符串 s ,找出 至多 包含 k 个不同字符的最长子串 T。

示例 1:

输入: s = "eceba", k = 2
输出: 3
解释: 则 T 为 "ece",所以长度为 3。
示例 2:

输入: s = "aa", k = 1
输出: 2
解释: 则 T 为 "aa",所以长度为 2。

```python
class Solution:
"""
@param s: A string
@param k: An integer
@return: An integer
"""
def lengthOfLongestSubstringKDistinct(self, s, k):
# write your code here
if not s:
return 0
if k<1:
return 0
res = 0, ""
right = 0
# from collections import defaultdict
judge = {}
# 套路如下,依然是一个负责进一个负责出
for left in range(len(s)):
# print(right, left, judge)
while right<len(s) and len(judge)<=k:
cur = s[right]
if cur in judge:
judge[s[right]]+=1
elif cur not in judge and len(judge)<k:
judge[s[right]]=1
else:
break
right+=1

if len(judge)<=k and right-left>res[0]:
res = right-left, s[left:right]

judge[s[left]]-=1
if judge[s[left]]==0:
del judge[s[left]]
# print(res)
return res[0]
```
Loading