1.对撞指针
在头部和尾部分别设置一个指针,一个指针向后移动一个指针向前移动,如果指针对撞则停止循环
1.1.LeetCode 相关习题
2.LeetCode 题目
2.1_27移除元素
2.1.1 算法描述
这个题是比较典型的快慢指针,一个赋值指针 i 一个赋值指针 j ,j 是不断向前移动的,只有当 j 满足某个条件后将 nums[j] 的值赋给 nums[i] i 才会移动
2.1.2 代码实现
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int i = 0; // 赋值指针
int j = 0; // 移动指针
while(j<nums.size()){
if(nums[j]==val) j++;
else nums[i++] = nums[j++];
}
return i;
}
};
2.1.3 时空复杂度
时间复杂度:O(N)
空间复杂度:O(1)
2.2_344 反转字符串
2.2.1 算法描述
start 从前向后, end 指针从后向前,交换两个指针指向的元素
2.2.2 代码实现
class Solution {
public:
void fanzhuan(vector<char>&s,int start,int end){
while(start<end){
swap(s[start++],s[end--]);
}
}
void reverseString(vector<char>& s) {
fanzhuan(s,0,s.size()-1);
}
};
2.2.3 时空复杂度
时间复杂度:O(N)
空间复杂度:O(1)
1.1.1 167.两数之和-输入有序数组
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
n = len(numbers)
i = 0
j = n - 1
while i < j:
if numbers[i] + numbers[j] == target:
return [i+1, j+1]
elif numbers[i] + numbers[j] > target:
j -= 1
else:
i += 1
return [i+1,j+1]
1.1.2 125 判断字符串是否回文
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
n = len(numbers)
i = 0
j = n - 1
while i < j:
if numbers[i] + numbers[j] == target:
return [i+1, j+1]
elif numbers[i] + numbers[j] > target:
j -= 1
else:
i += 1
return [i+1,j+1]
字符串涉及方法:
# 转换成大写
upper()
# 转换成小写
lower()
# 判断是否是字母或者数字
isalnum()
# 转换成大写 upper() # 转换成小写 lower() # 判断是否是字母或者数字 isalnum()
1.1.3 11 盛最多水的容器
class Solution:
def maxArea(self, height: List[int]) -> int:
max_v = 0
i = 0
j = len(height)-1
while i<j:
# 先计算当前面积
volumn = (j-i)*min(height[i],height[j])
max_v = max(max_v,volumn)
#移动那根较短的柱子
if height[i]<height[j]:
i+=1
else:
j-=1
return max_v
先计算再移动
判断大小涉及方法
# 判断两个数谁更小 min(a,b) # 判断两个数谁更大 max(a,b)
1.1.4 344-翻转字符串
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
i = 0
j = len(s)-1
while i<j :
s[i],s[j] = s[j],s[i]
i+=1
j-=1
1.1.5 345-翻转字符串的元音字母
class Solution:
def reverseVowels(self, s: str) -> str:
array = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'] # 定义元音
s_list = list(s) # str 2 list ,因为 str 没有 not in 和 in 方法
i, j = 0, len(s)-1
while i < j:
if s_list[i] in array and s_list[j] in array:
s_list[i], s_list[j] = s_list[j], s_list[i]
i += 1
j -= 1
if s_list[j] not in array:
j -= 1
if s_list[i] not in array:
i += 1
return ''.join(s_list) # list 2 str
字符串转换相关方法
# 字符串转换成 list list(s) # list 转换成字符串 ‘’.join(s_list)
1.1.6 633 平方数之和
1
2.快慢指针
2.1 快慢指针原理
当一个跑得快的人和一个跑的慢的人在一个环形的赛道上赛跑,最后跑的快的人会赶上跑的慢的人
所以快指针一次走两步,慢指针一次走一步,最终会相遇在环的某个位置
这个方法用来判断一个链表中是否出现环
2.2 LeetCode 相关习题
2.2.1 142 环形链表
2.2.2解题思路
主要判断:
1.链表是否有环
①存在环:
如果 fast 指针和 slow 指针相遇了,则代表存在环
他们相遇的地方肯定是在环内的某个位置
相遇时 fast 比 slow 多走了 n 圈
②不存在环
fast 最后走向了 None
2.如果有环,环的入口 在哪
①现在假设存在环:
x = 从 head 节点到环的入环口的 step 数
y = 入环口到 fast 和 low 相遇的地方的 step 数
z = 相遇节点剩余部分再到入环口为 z
②相遇时两个节点走的步数
slow : x+y
fast:x+n(y+z)+y
因为快指针是慢指针的两倍:
fast = 2 slow
相关数学公式推导:
等式:2*(x+y)=x+y+n(y+z)
两边消掉一个(x+y): x + y = n (y + z)
因为求的是入口点的举例,所以求得x 的值:x = n (y + z) - y
在从n(y+z)中提出一个 (y+z)来,整理公式之后:x = (n - 1) (y + z) + z
这里的 n 是大于等于1 的,因为 fast 要至少走完 1 圈才会和 slow 相遇
当 n=1 时: x=z
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了
当 n>1 时这个等式仍然成立,因为我们所求的是位移,不是路程,所以上面等式和 fast 多绕了几圈是没有关系,不妨碍成立的
③代码设计思路
在存在闭环的情况下,在找到相遇的节点之后,**分别定义一个指针从原始出发,然后再定义一个指针从相遇节点出发,走相同的次数后,如果两个指针相遇则相遇的节点就是环的入口 **
2.2.3 C++ & Python 代码实现
(1)C++ 代码实现
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
// 判空做操作
if(head==NULL||head->next==NULL) return NULL;
ListNode* slow = head;
ListNode* fast = head;
fast = fast->next->next; // 先走两步
slow = slow->next;
while(fast!=NULL&&fast->next!=NULL){
// 判断是否相等:开始放 p,q 指针
if(slow==fast){
ListNode* p = head;
ListNode* q = fast;
// x,z 相遇的地方就是初始节点
while(p!=q){
p = p->next;
q = q->next;
}
return p;
}else{
fast = fast->next->next;
slow = slow->next;
}
}
return NULL;
}
};
1.定义闭环链表(代码直接给出了)
Definition for singly-linked list.
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
2.方法主体
def detectCycle(self, head: ListNode) -> ListNode:
fast,slow = head,head
while fast and fast.next :
fast = fast.next.next # 向前移动一步
slow = slow.next # 向前移动两步
# 如果两个指针指向的值相等,代表存在闭环
if fast == slow: # 这里直接判断的是节点,不是节点里的值
p = head
q =slow
# 当两个值相等的时候就要执行 x=z 的步骤了
while p!=q:
p = p.next
q = q.next
return p # 很奇怪这里返回 q 的结果会是错的
2.2_287寻找重复数
2.2.1 算法描述
1.快慢指针法
特殊性:数组中有 n 个位置,能放 1~n+1 的数字,那么根据 “生日原理”,至少有一个数一定是重复的,那么就可以组成一个环。
将这个特殊的数组当做一个双链表来看,即,将下标 index 和 元素值都看做是指针,就形成了 index->nums[index] 的映射,在这个映射上进行构图,就是一个环形链表
然后这个题就会转换成上个题,现在就只需要找到环形链表的入口节点即可
2.2.2 C++ 代码实现
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = 0;
int fast = 0;
while(true){ // 这里当做循环链表处理
fast = nums[nums[fast]];
slow = nums[slow];
if(fast==slow){
int p = 0;
int q = slow;
while(p!=q){
p = nums[p];
q = nums[q];
}
return p;
}
}
}
};
2.2.3 时空复杂度
时间复杂度:O(N)
空间复杂度:O(1)
3.滑动窗口
1.基本思想
滑动窗口中用到了左右两个指针,它们移动的思路是:以右指针作为驱动,拖着左指针向前走。右指针每次只移动一步,滑动窗口会变大,右指针走啊走直到滑动窗口的值不满足某个要求了。这时候开始移动左指针,使用 while 循环一直移动直到 left 满足为止。满足后 right 指针可以继续探索新的区域,寻找下一个满意的区间
left 指针的移动是在内部 while 循环中 ; right 指针的移动是当 left 指针满足要求了 right 在 while 循环的最外层移动
2.模板
1.定义两个指针 left ,right ,一起指向起始位置
2.第一重 while 循环让 right 进行移动,相当于对每个字符进行判断
3.第二重循环让 left 移动,将 left 移动到符合题意的那个 index 上
4.right 指针再向前一步探索新的区间
3.2_3无重复的最长子串
3.3.1 算法描述
对于例子 abcabcbb 来说,有一个指针 left 需要不断的在字符串上滑动,滑滑滑滑到 abca 时发现产生了重复,
出现重复需要做两件事:
①将最左侧的 a 移走
②left++,也就是将下一个元素作为最长子串的起始位置
用什么判断是否产生了重复:set 判断。
这里需要双指针,一个指针指向非重复字符串的起始位置,一个指针不断的滑动。
3.1.2 代码实现
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int left = 0;
int right = 0;
int res = 0;
unordered_set<char> uset;
while(right<s.size()){
// 判断 set 中是否有重复的元素
while(uset.find(s[right])!=uset.end()){
uset.erase(s[right]);
left++;
}
// 符合条件了,判断符合条件的结果
res = max((right-left+1),res);
uset.insert(s[right]);
right++;
}
return res;
}
};
3.1.3 时空复杂度
时间复杂度:O(N)
空间复杂度:O(∣Σ∣) Σ:字母 ASCII 码的合集