双指针主要用于遍历数组,通过两个指针指向不同的元素,从而可以协同完成任务。也可以多个数组多个指针。
若两个指针遍历一个数组,遍历方向相同并且不会相交(两个指针包围的区域)称为滑动窗口,用于区间搜索。
若两个指针遍历一个数组,遍历方向相反,可以用来搜索,待搜索的数组往往是排序好的(例如:二分查找)
对于C++,指针和常量,指针函数和函数指针也是常见操作。
题目描述
在一个增序的整数数组里找到两个数,使它们的和为给定值。已知有且只有一对解。
输入输出样例
输入是一个数组(numbers)和一个给定值(target)。输出是两个数的位置,从 1 开始计数。
Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
在这个样例中,第一个数字(2)和第二个数字(7)的和等于给定值(9)。
题解
因为数组已经排好序,我们可以采用方向相反的双指针来寻找这两个数字,一个初始指向最小的元素,即数组最左边,向右遍历;一个初始指向最大的元素,即数组最右边,向左遍历。
如果两个指针指向元素的和等于给定值,那么它们就是我们要的结果。如果两个指针指向元素的和小于给定值,我们把左边的指针右移一位,使得当前的和增加一点。如果两个指针指向元素的和大于给定值,我们把右边的指针左移一位,使得当前的和减少一点。
证明
对于排好序且有解的数组,双指针一定能遍历到最优解。证明方法如下:假设最优解的两个数的位置分别是 l 和 r。我们假设在左指针在 l 左边的时候,右指针已经移动到了 r;此时两个指针指向值的和小于给定值,因此左指针会一直右移直到到达 l。同理,如果我们假设在右指针在 r 右边的时候,左指针已经移动到了 l;此时两个指针指向值的和大于给定值,因此右指针会一直左移直到到达 r。所以双指针在任何时候都不可能处于 (l,r) 之间,又因为不满足条件时指针必须移动一个,所以最终一定会收敛在 l 和 r。
代码
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int 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 right--;
}
return {-1,-1};
}
};
题目描述
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c 。
输入输出样例
Input: c=5
Output: true
1* 1+2* 2=5
代码
class Solution {
public:
bool judgeSquareSum(int c) {
int l=0,r=sqrt(c);
while(l<=r){
long long sum=(long long)l*l+(long long)r*r;
if(sum==c) return true;
else if(sum<c) l++;
else r--;
}
return false;
}
};
题目描述
给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
输入输出样例
Input: s = "aba"
Output: true
代码
class Solution {
public:
bool check(const string& s,int low,int high){
for(int i=low,j=high;i<j;i++,j--){
if(s[i]!=s[j]) return false;
}
return true;
}
bool validPalindrome(string s) {
int low=0,high=s.size()-1;
while(low<high){
if(s[low]==s[high]){
low++;
high--;
}
else
return check(s,low,high-1)||check(s,low+1,high);
}
return true;
}
};
题目描述
给定两个有序数组,把两个数组合并为一个。
输入输出样例
输入是两个数组和它们分别的长度 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]
题解
因为这两个数组已经排好序,我们可以把两个指针分别放在两个数组的末尾,即 nums1 的m − 1 位和 nums2 的 n − 1 位。每次将较大的那个数字复制到 nums1 的后边,然后向前移动一位。因为我们也要定位 nums1 的末尾,所以我们还需要第三个指针pos,以便复制。如果 nums1的数字已经复制完,把 nums2 的数字继续复制;如果 nums2 的数字已经复制完,剩余nums1 的数字不需要改变,因为它们已经被排好序。
本题用了一个逆向思维,从后向前遍历。
代码
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int pos=m+n-1;
m--;
n--;
while(m>=0&&n>=0){
if(nums1[m]>=nums2[n]){
nums1[pos]=nums1[m];
--m;
}
else{
nums1[pos]=nums2[n];
n--;
}
--pos;
}
while(n>=0){
nums1[pos]=nums2[n];
pos--;
n--;
}
}
};
题目描述
给你一个字符串 s 和一个字符串数组 dictionary ,找出并返回 dictionary 中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。
输入输出样例
Input: s = "abpcplea", dictionary = ["ale","apple","monkey","plea"]
Onput: "apple"
代码
class Solution {
public:
string findLongestWord(string s, vector<string>& dictionary) {
string res="";
for(string& t:dictionary){
int i=0,j=0;
while(i<t.size()&&j<s.size()){
if(t[i]==s[j]){
++i;
}
++j;
}
if(i==t.size()){
if(t.size()>res.size()||(t.size()==res.size()&&t<res)){
res=t;
}
}
}
return res;
}
};
题目描述
给定一个链表,如果有环路,找出环路的开始点。
输入输出样例
输入是一个链表,输出是链表的一个节点。如果没有环路,返回一个空指针。
给定两个指针,分别为fast和slow,从起始位置开始,fast每次前进两步,slow每次前进一步。如果fast为空或者next为空,则说明没有环路。否则fast会一直走下去,说明有环路,两者一定会相遇。当fast和slow相遇时,再将fast重新移动到head,并让fast和slow每次都前进一步,当两者再次相遇时,相遇节点即为环路开始点。
代码
/**
* 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;
while(true){
if(fast==nullptr||fast->next==nullptr) return nullptr;
fast=fast->next->next;
slow=slow->next;
if(fast==slow) break;
}
fast=head; //fast重新移动到head
while(fast!=slow){
fast=fast->next;
slow=slow->next;
}
return fast;
}
};
题目描述
给定两个字符串 S 和 T,求 S 中包含 T 所有字符的最短连续子字符串的长度,同时要求时间
复杂度不得超过 O(n)。
输入输出样例
输入是两个字符串 S 和 T,输出是一个 S 字符串的子串。
Input: S = "ADOBECODEBANC", T = "ABC"
Output: "BANC"
在这个样例中,S 中同时包含一个 A、一个 B、一个 C 的最短子字符串是“BANC”。
题解
代码
class Solution {
public:
string minWindow(string s, string t) {
vector<int> chars(128, 0);
vector<bool> flag(128, false);
// 先统计t中的字符情况
for(int i = 0; i < t.size(); ++i) {
flag[t[i]] = true;
++chars[t[i]];
}
// 移动滑动窗口,不断更改统计数据
int cnt = 0, l = 0, min_l = 0, min_size = s.size() + 1;
for (int r = 0; r < s.size(); ++r) {
if (flag[s[r]]) {
if (--chars[s[r]] >= 0) {
++cnt;
}
// 若目前滑动窗口已包含t中全部字符,
// 则尝试将l右移,在不影响结果的情况下获得最短子字符串
while (cnt == t.size()) {
if (r - l + 1 < min_size) {
min_l = l;
min_size = r - l + 1;
}
//如果l位置的字符是t中的,数量加1,总数减1
if (flag[s[l]] && ++chars[s[l]] > 0) {
--cnt;
}
++l;
}
}
}
return min_size > s.size()? "": s.substr(min_l, min_size);
}
};
注:
substr 复制字符串,从指定位置开始,并有指定长度
string a = s.substr(0,5); //获得字符串s中从第0位开始的长度为5的字符串
++i>0 该语句先进行++运算,再进行比较运算