算法核心思想:
双指针多用于遍历数组,可以同时遍历一个数组或分别遍历不同的数组。常见的设计思想如下:
对于排序后的数组,双指针分别指向数组的头尾,遍历方向相反,用于搜索。
双指针指向两个数组,分别遍历
快慢指针
滑动窗口
两数之和
167. Two Sum II - Input array is sorted (Easy)
题目描述
在一个增序的整数数组里找到两个数,使它们的和为给定值。已知有且只有一对解。
输入输出样例
输入是一个数组(numbers)和一个给定值(target)。输出是两个数的位置,从 1 开始计数。
Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
在这个样例中,第一个数字(2)和第二个数字(7)的和等于给定值(9)。
核心思路:两个指针分别指向数组的头尾,相向而行遍历,若判断结果大于则增加左指针,若小于则减小右指针
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target)
{
vector<int>::iterator p_front = numbers.begin();
vector<int>::reverse_iterator p_behind = numbers.rbegin();
int first = 1;
int end = numbers.size();
while (1<end)
{
if (((*p_front )+( *p_behind)) == target)
{
return vector<int>{ first,end };
}
if ((*p_front + *p_behind) < target)
{
p_front++;
first++;
}
if ((*p_front + *p_behind) > target)
{
p_behind++;
end--;
}
}
}
};
int main()
{
vector<int> numbers{ 2,3,5,8,10,11 };
Solution s;
vector<int> ret;
ret= s.twoSum(numbers, 8);
cout << ret[0] << " " << ret[1] << endl;
}
归并两个有序数组
- Merge Sorted Array (Easy)
题目描述
给定两个有序数组,把两个数组合并为一个。
输入输出样例
输入是两个数组和它们分别的长度 m 和 n。其中第一个数组的长度被延长至 m + n,多出的
n 位被 0 填补。题目要求把第二个数组归并到第一个数组上,不需要开辟额外空间。
Input: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
Output: nums1 = [1,2,2,3,5,6]
核心思想:两个指针分别遍历两个数组进行比较,每次将较大的放到第一个数组的末尾,并自减,第三个指针用来管理复制,如果第二个数组先复制完了,则剩下的数组不需要改变,若第一个数组先复制完,则将第二个数组剩余的元素放到第一个数组的头部
#include <iostream>
#include <vector>
#include<algorithm>
#include <numeric>
using namespace std;
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n)
{
int pos = m-- + n-- - 1;
while (m >= 0 && n >= 0)
{
// nums1[pos--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];
nums1[pos--] = (nums1[m] > nums2[n]) ? nums1[m--] : nums2[n--];
}
while (n >= 0)
{
nums1[pos--] = nums2[n--];
}
}
};
int main()
{
vector<int> nums1{ 1,2,3,0,0,0 };
vector<int> nums2{ 2,5,6 };
Solution s;
s.merge(nums1, 3, nums2,nums2.size());
for (int i = 0; i < nums1.size(); i++)
{
cout << nums1[i] << " ";
}
}
快慢指针
142. Linked List Cycle II (Medium)
题目描述
给定一个链表,如果有环路,找出环路的开始点。
输入输出样例
输入是一个链表,输出是链表的一个节点。如果没有环路,返回一个空指针。
在这个样例中,值为 2 的节点即为环路的开始点。
如果没有特殊说明, LeetCode 采用如下的数据结构表示链表。
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
核心思想:如果两个人同时出发,如果赛道有环,那么快的一方总能追上慢的一方。进一步想,追上时快的一方肯定比慢的一方多跑了几圈,即多跑的路的长度是圈的长度的倍数。
原理:设起点到环的起点距离为m,已经确定有环,环的周长为n,(第一次)相遇点距离环的起点的距离是k。那么当两者相遇时,慢指针移动的总距离为i,i = m + a * n + k,因为快指针移动速度为慢指针的两倍,那么快指针的移动距离为2i,2i = m + b * n + k。其中,a和b分别为慢指针和快指针在第一次相遇时转过的圈数。我们让两者相减(快减慢),那么有i = (b - a) * n。即i是圈长度的倍数。利用这个结论我们就可以理解Floyd解法为什么能确定环的起点。将一个指针移到链表起点,另一个指针不变,即距离链表起点为i处,两者同时移动,每次移动一步。当第一个指针前进了m,即到达环起点时,另一个指针距离链表起点为i + m。考虑到i为圈长度的倍数,可以理解为指针从链表起点出发,走到环起点,然后绕环转了几圈,所以第二个指针也必然在环的起点。即两者相遇点就是环的起点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution
{
public:
ListNode *detectCycle(ListNode *head)
{
ListNode *fast=head;
ListNode *slow=head;
do {
if (!fast || !fast->next) return nullptr;
fast = fast->next->next;
slow = slow->next;
} while (fast != slow);
fast=head;
while(fast!=slow)
{
fast=fast->next;
slow=slow->next;
}
return fast;
}
};
滑动窗口
76. Minimum Window Substring (Hard)
题目描述
给定两个字符串 S 和 T,求 S 中包含 T 所有字符的最短连续子字符串的长度,同时要求时间
复杂度不得超过 O„n”。
输入输出样例
输入是两个字符串 S 和 T,输出是一个 S 字符串的子串。
Input: S = “ADOBECODEBANC”, T = “ABC”
Output: “BANC”
在这个样例中, S 中同时包含一个 A、一个 B、一个 C 的最短子字符串是“BANC”。
核心思想:维护一个动态变化的滑动窗口,寻找包含字符串T中所有元素的最小解。
滑动规则:1.使右边界增大,直到包含所有的T中所有的元素。2.缩小左边界,直到不够包含所有元素。此时为局部最优解。
3.让左边界加1,使其不满足条件,然后右边界继续增加直到又再次满足条件。4.重复123步,直到右边界越界。
这个题有点难写,先放一个别人的题解
class Solution {
public:
string minWindow(string s, string t) {
// 滑动窗口,首先将字符存储到哈希表中,然后再开始标准滑动窗口
// 1: 字符存储, 标记存储
// 为何要存储标记? 以 s = "ADOBECODEBANC", t = "ABC" 为例,如果s[left] = 'A' , 那么left右移的时候计数要归位
vector<int> chars(128, 0);
vector<int> flags(128, 1);
for (char& c : t) {
++chars[c];
flags[c] = 1;
}
// 2: 开滑!
int left = 0, right = 0, n = s.size(), m = t.size(), cnt = 0; // 滑动窗口基本变量
int min_left = 0, min_size = INT_MAX; // 返回string的要求变量
while (right < n) {
if (--chars[s[right++]] >= 0) // right指针右移
++cnt;
while (cnt >= m) { // 滑动窗口判断+left左移
if (right - left < min_size) {
min_left = left;
min_size = right - left;
}
if (flags[s[left]] && ++chars[s[left]] > 0) // 存储标记发挥作用的时候
--cnt;
left++;
}
}
return min_size > n ? "" : s.substr(min_left, min_size);
}
};
int main()
{
string s = { "ADOBECODEBANC" };
string t = { "ABC" };
string out;
Solution output;
out = output.minWindow(s, t);
for (const auto& p : out)
{
cout << p << endl;
}
}