例题:(1)Acwing 799. 最长连续不重复子序列
比较简单的一个例题,思路是利用双指针从头开始遍历,每次记录该数字出现次数,如果发现有重复的就用副指针往前遍历,找到重复位置。每次主指针移动时更新最长子串长度(主指针-副指针+1)。
#include
#include
#include
using namespace std;
const int N = 1e5+10;
int a[N],s[N];
int main()
{
int n,ans = -2e9;
scanf("%d", &n);
for(int i = 0,j = 0;i1){
s[a[j]]--;
j++;
}
ans = max(ans,i-j+1);
}
printf("%d",ans);
return 0;
}
(2) AcWing 800. 数组元素的目标和
由于两个数组都是升序的,因此利用两个指针分别从两个数组的头尾开始遍历,直到某一个指针遍历到数组的另一端为止。注意边界,一定要到另一端才行。
#include
#include
#include
#include
using namespace std;
typedef pair pii;
const int N = 1e5+10;
int a[N],b[N];
vector ans;
int main()
{
int n,m,x;
scanf("%d%d%d", &n, &m,&x);
for(int i = 0;i=0){
if(a[l]+b[r]==x){
ans.push_back({l,r});
l++;r--;
}
else if(a[l]+b[r]>x) r--;
else l++;
}
for(auto c:ans){
printf("%d %d",c.first,c.second);
}
return 0;
}
(3)AcWing 2816. 判断子序列
利用双指针对两个数组进行遍历,在父数组中每次向前移一个长度,如果这一位与子数组指针指向的数字相同,则子数组的指针也向前一位。看是子数组指针先到末尾还是父数组指针先到末尾。
#include
#include
#include
using namespace std;
const int N = 1e5+10;
int a[N],b[N];
int main()
{
int n,m;
scanf("%d%d", &n, &m);
for(int i = 0;i
练习:(1)P1102 A-B 数对
没做出来。。一开始的想法是一个从左一个从右开始维护,后来发现有些问题。学习了之后了解到应该是维护一个r1,一个r2,让r1是第一个a[r1]-a[l]大于等于x的,而a[r2]-a[l]是第一个大于x的,因此中间这一段就都是满足a[r1]-a[l]等于x的。不开ll见祖宗。。。。
#include
#include
using namespace std;
const int N = 2e5+10;
int a[N];
int main(int argc, char** argv) {
int n,x;
scanf("%d%d",&n,&x);
for(int i = 0;i=0 ) sum+=r1-r2;
}
printf("%lld",sum);
return 0;
}
(2) Leetcode 905.按奇偶排序数组
思路很简单,左右各一个指针,不断找奇数偶数然后交换。。不过还是挺坑的。。一开始处理的时候老是出现越界问题,后来才知道要在指针动完就判断一次是否已经过了。
class Solution {
public:
vector sortArrayByParity(vector& nums) {
int i = 0,j = nums.size()-1;
while(i0) j--;
if(i>=j) break;
swap(nums[i],nums[j]);
i++;j--;
}
return nums;
}
};
(3) Leetcode 5.最长回文子串
一开始没做出来。。。思路是枚举每一个字符串中心,这个中心可能有两种情况,一种是以一个字母为中心,需要枚举从1到第n-1个的位置,然后用两个指针往字符串的两边扩展,直到其两侧字母不相同,然后返回字符串长度和头位置。 另一种是以两个字母为中心,从1到n枚举每一个字母,判断其与前面一个字母是否相同,如果相同则用两个指针往外扩展。最后利用string类的substr函数进行截取,substr函数的参数是(起始位置的下标,截取长度)。
class Solution {
public:
string longestPalindrome(string s) {
int sum = 1,ed = 0,st = 0;
for(int i = 1;i0&&r0&&r
(4)Leetcode 11.乘最多水的容器
也是想了一下没怎么想出来,不过大概看了下。。一个容器能盛水的多少取决于左右两块板中较短的那块板以及两块板间的距离,正因此我们可以用两个指针从左右开始遍历,只动较短的那块板的指针并更新结果,如果两块板长度相同则随便更新一块板,要记得先更新体积再移动指针。
class Solution {
public:
int maxArea(vector& height) {
int l = 0,r = height.size()-1,sum = min(height[l],height[r])*r;
while(l
(5)Leetcode 15.三数之和
本质上是两数之和的翻版,只不过需要考虑的内容变多了,比如怎么保证三元组不重复之类的。要求三个数的和等于0,首先可以对其进行排序,如果数组全部大于0或者全部小于0则必然为空集合,接下来进行进一步讨论。从数组的第一个元素开始枚举,要求三数之和为0,可以转换成从目前枚举元素的后面找到两个元素,相加和为目前枚举元素的相反数。因此如果这个数已经大于0则可以返回,如果这个数不是第一次出现则跳过,否则利用双指针开始寻找,为了保证不存在重复三元组,我们需要在每一次成功找到三元组后跳过两个元素前面/后面的重复元素。
class Solution {
public:
vector> threeSum(vector& nums) {
unordered_map cnt;
vector> x;
sort(nums.begin(),nums.end());
int n = nums.size();
if(nums[0]>0||nums[n-1]<0) return x;
for(int i = 0;i0) return x;
if(cnt[nums[i]]) continue;
else cnt[nums[i]]++;
int l = i+1,r = n-1;
while(l0&&nums[r]==nums[r+1]) r--;
}
else if(nums[l] + nums[r] + nums[i] < 0) l++;
else r--;
}
}
return x;
}
};
(6)Leetcode 31.下一个排列
好难啊。。。完全没想到这个方法。。
求取下一个排列的过程,就是求取比当前这个排列大的最少的一个排列的过程。也正因此,我们需要让排列变大(把后面的大数与前面的小数互相交换),但又不能让他大的过多。一个排列的右端必然有一段降序区间,这段降序区间内我们交换任何数都无法使其变大。因此我们需要把降序区间左边的第一个数和降序区间内比该数大的最小的一个数交换,从而使得数字变大的最少,且右边区间仍然是降序。然后再把降序反转改为升序,这样使得这个排列的右半部分是最小的。从而使得这个排列时最小的大于已知排列的排列。如果已经是完全降序则完全翻转。
class Solution {
public:
void nextPermutation(vector& nums) {
int n = nums.size()-1,l=0,r=0,flag = 0;
for(int i = n;i>=1;i--){
if(nums[i-1]=0;i--){
if(nums[i]>nums[l]){
r = i;
swap(nums[l],nums[r]);
break;
}
}
if(!flag) l = -1;
reverse(nums.begin()+l+1,nums.end());
}
};
(7)Leetcode 160.相交链表
看都看不懂。。。什么链表。。在说什么啊。。。期末C语言还要考链表。。也是不会的。。
两个链表可能具有相同的一部分,因此可以利用双指针,一个指针遍历一个链表,遍历完就到另一个链表接着遍历,可以证明其一定会在重合节点相遇,本质原因是在重合节点前pa走了一个pa的长加pb到重合节点的长,也就是pa到第一个重合节点的长+pb到第一个重合节点的长+重合节点的总长。同理pb也走了相同的长度,因此他们一定会在重合节点相遇。假如没有重合则都走了a链表长+b链表长。还是会同时走完。
nullptr是c++中为了用来和null区分而新定义的。主要区别在于nullptr是指针类型的空,而null是宏定义的0,在函数重载时可能会被默认为int型而非int*型。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA == NULL || headB == NULL){
return NULL;
}
ListNode *pa = headA,*pb = headB;
while(pa!=pb){
pa = pa == nullptr ? headB : pa->next;
pb = pb == nullptr ? headA : pb->next;
}
return pa;
}
};
(8)Leetcode .26删除有序数组中的重复项
思路是有的,但是在改的过程中才发现问题很多。学习了一下,本质上就是用两个双指针,一个用来指向目前不重复元素在前面已经排到的位置,另一个用来遍历数组,如果遍历数组指针指向的元素不等于前面已经排上队的不重复元素,则在已排上队的不重复元素后面放这个不重复的新元素,直到遍历结束。
class Solution {
public:
int removeDuplicates(vector& nums) {
int l = 0,r = 1,n = nums.size();
if(n==0) return 0;
while(r
(9)Leetcode 27.移除元素
跟上一题的思考方式相同,一样是用两个指针,一个用来指向新创造出的数组,另一个用来指向原数组,如果原数组里面的元素不等于val则放到新数组里,新数组长度就是原指针指向的下标。
class Solution {
public:
int removeElement(vector& nums, int val) {
int l = 0,r = 0;
while(r
(10)洛谷P1638 逛画展
没做出来。。尺缩法???
用两个指针,首先先找到一个包含m个画家的区间,然后左边界不断缩小,直到区间内只剩m-1个画家,这时候再动右区间直到又包含m个画家,每次记录左右边界,最后比较所有满足情况的左右边界。取最短的一段区间。
#include
#include
using namespace std;
const int N = 1e6+10,M = 2e3+10;
typedef pair pii;
vector x;
int a[N],s[M];
int main(int argc, char** argv) {
int n,m,ll,rr;
scanf("%d%d",&n,&m);
for(int i = 1;i<=n;i++){
scanf("%d",&a[i]);
}
int l = 1,r = 1,cnt = 0;
for(r;r<=n;r++){
if(!s[a[r]]) cnt++;
s[a[r]]++;
if(cnt == m){
x.push_back({l,r});
break;
}
}
for(l=2;l<=n;l++){
if(s[a[l-1]]==1) cnt--;
s[a[l-1]]--;
while(cnt c.second - c.first + 1||(sum == c.second - c.first + 1)&&(c.first < l)){
l = c.first,r = c.second;
sum = c.second - c.first + 1;
}
}
printf("%d %d",l,r);
return 0;
}