目录
双指针技巧再分为两类,一类是「
快慢指针
」,一类是「左右指针
」。
前
者解决主要解决链表中的问题
,比如典型的判定链表中是否包含环;
后
者主要解决数组(或者字符串)中的问题
,比如二分查找。
快慢指针
1 判定链表中是否含有环
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (fast == nullptr || fast->next == nullptr) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
2 已知链表中含有环,返回这个环的起始位置
相遇点到环起点 的 步数 与 链头部 到 环起点的步数相同(前提是 slow 单步,fast 两步)
ListNode detectCycle(ListNode head) {
ListNode fast, slow;
fast = slow = head;
while (fast != NULL&& fast.next != NULL) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) break;
}
// 上面的代码类似 hasCycle 函数
slow = head;
while (slow != fast) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
3 寻找链表的中点
当链表的长度是
奇数
时,slow恰巧停在中点位置;
如果长度是偶数
,slow最终的位置是中间偏右
通过判断 快指针fast->next是否为NULL 来看链表长度是偶数还是奇数;
为NULL是偶数,不为NULL为奇数
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode *slow ,*fast;
slow=fast=head;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
4 寻找链表的倒数第n个元素
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *slow,*fast;
fast=slow=head;
while(n-- > 0)
fast=fast->next;
if(fast==nullptr) return head->next; // 此时fast指向尾部,第n个即首部
// fast 指向 首部时,fast不为null fast->next 为空
while(fast!=nullptr&&fast->next!=nullptr){
fast=fast->next;
slow=slow->next;
}
slow->next=slow->next->next;
delete p;
return head;
}
};
java中new ListNode(0)常见用法详细区别(全)
左右指针
1 二分查找
2 两数之和 – 有序数组
只要
数组有序
,就应该想到双指针技巧
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int left,right;
left = 0; right=numbers.size()-1;
while(left < right){
int sum = numbers[left]+numbers[right];
if(sum == target) return {left+1,right+1};
else if(sum < target) left++;
else if(sum>target) right--;
}
return {-1,-1};
}
};
3 反转数组
class Solution {
public:
void reverseString(vector<char>& s) {
int n = s.size();
for (int left = 0, right = n - 1; left < right; ++left, --right) {
swap(s[left], s[right]);
}
}
};
C++容器的swap()函数的三点说明
C++中的swap(交换函数)
4 滑动窗口算法
以下四个问题:
1、当移动right扩大窗口
,即加入字符时,应该更新哪些数据
?
2、什么条件下
,窗口应该暂停扩大,开始移动left缩小窗口
?
3、当移动left缩小窗口
,即移出字符时,应该更新哪些数据
?
4、我们要的结果
应该在扩大窗口时还是缩小窗口时(何时)进行更新
?
针对 Leecode 76 题: 匹配对应字符及该字符个数
1、扩大窗口时,应更新窗口内包含目标子串的字符频数,每当完全匹配到一个字符时,更新一次当前窗口已经完全匹配到的字符数
2、当窗口已经完全包含所有目标子串字符时,开始缩小窗口
3、在收缩窗口前更新数据
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char,int> need,window; // 用于存储目标子串各字符频数 和 当前窗口中的字符频数
for(char c : t) need[c]++; // 初始化 目标子串 各 字符频数
// 搜索字符区间 (左开右闭) [left,right) 所以初始窗口为空
int left=0,right=0; // 左右指针
int valid = 0; // 标记已匹配到的字符数
int start=0,len=INT_MAX; // 记录最小子串起始位置及其长度,长度初始化为一个默认大值
// 当 右指针right 超出字符串索引,结束
while(right < s.size()){
char c = s[right]; // 移入窗口内的元素
right++; // 扩大窗口
// 窗口内数据更新
if(need.count(c)){ // 仅当与目标字符匹配时,更新
window[c]++;
if(window[c] == need[c]) // 仅当窗口内字符数与所需字符数相同时更新
valid ++ ;
}
// 收缩窗口 (判断何时收缩窗口)
while(valid == need.size()){ // 当窗口字符完全包含目标字符时,向左收缩窗口
// 记录当前子串起始位置及其长度,仅当比上一子串小时更新
if(right - left < len){ // 因为搜索区间为[left,right) ,所以 right-left 即为窗口子串长度,无须 +1
start = left;
len = right-left;
}
// 移出窗口元素
char d = s[left];
left ++; // 左移窗口
// 窗口内数据更新
if(need.count(d)){
if(window[d] == need[d])
valid --;
window[d]--;
}
}
}
// 判断len 是否更新 未更新表示未搜索到最小子串,返回空字符串
return len == INT_MAX ? "" : s.substr(start,len);
}
};
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char,int> need,window;
for(char c:s1) need[c]++;
int left=0,right=0;
int valid=0;
while(right < s2.size()){
char c=s2[right];
right++;
if(need.count(c)){
window[c]++;
if(window[c] == need[c])
valid++;
}
// 当窗口等于目标子串长度时收缩窗口
while(right - left >= s1.size()){
if(valid == need.size()) return true;
char d=s2[left];
left++;
if(need.count(d)){
if(window[d] == need[d])
valid --;
window[d]--;
}
}
}
return false;
}
};
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
unordered_map<char,int> need,window;
for(char c: p) need[c]++;
int left=0,right=0;
int valid=0;
vector<int> start;
while(right<s.size()){
char c=s[right];
right++;
if(need.count(c)){
window[c]++;
if(window[c] == need[c])
valid++;
}
// 窗口大小限制
while(right - left >=p.size()){
if(valid == need.size()) start.push_back(left);
char d=s[left];
left++;
if(need.count(d)){
if(window[d] == need[d])
valid --;
window[d]--;
}
}
}
return start;
}
};
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char,int> window;
int left=0,right=0;
int res =0 ;
while(right<s.size()){
char c=s[right];
right++;
// 更新窗口
window[c]++;
// 收缩窗口
while(window[c]>1){
char d=s[left];
left++;
window[d]--;
}
// 更新结果
res=max(res,right-left);
}
return res;
}
};