双指针算法 实用i,j两个变量,不会退的扫描一个数组 常规写法
for(int i=0,j=0,i<n;i++){ while(j<i&&check(i,j)) j++; }这是i,j从0开始扫,j<i的扫法
for(int i=0,j=n-1;i<j;i++){ while(check()) j--; }这是i,j分别两端的写法 或者也能这样写
int i=0,j=-1; while(i<j){ if(check(i,j)) i++; else j--; }选择leetcode上7条题目为例 //: # (推荐题解模板,请替换blablabla等内容 ^^)
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
今天刚学习了双指针的模板,来练一下手,对于排序的数组,可以使用双指针来压缩搜索空间,起点分别设置在原数组两端,当l右移动可以时候,numbers[i]+numsbers[j]变大,r左移时候,和减少,不停的调整知道找到,l,r由于下标从1开始,l,r需要+1
这么写可以得到结果,但是不够规范,对于要返回的值最好放在一个变量里面,变量j,i可读性不强,换成l,r
class Solution { public: vector<int> twoSum(vector<int>& numbers, int target) { vector<int> res; int n=numbers.size(); for(int l=0,r=n-1;l<r;l++){ while(numbers[l]+numbers[r]>target) r--; if(numbers[l]+numbers[r]==target) { res.push_back(l+1),res.push_back(r+1); return res; } } return res; } };给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a 2 + b 2 a^2+b^2 a2+b2 = c
接167题,也是双指针的模板题,条件是平方和相同,改变判断移动的条件即可, ####遇到的坑点: #####首先平方和等于c,a,b都不可能大于 s q r t ( c ) sqrt(c) sqrt(c) ,因此上边界可以定义位c的开根号再转换为整数, #####其次,a,b的数值是可以相同的,因此i,j不再是模板的i<j,而是i<=j; #####还有, a 2 + b 2 a^2+b^2 a2+b2仍然可能超出int的范围,因此i,j最好一开始就定义为int
#leetcode 345 //: # (推荐题解模板,请替换blablabla等内容 ^^)
编写一个函数,以字符串作为输入,反转该字符串中的元音字母。
接着前面的167, 633题,依然是双指针题,这次的模板类似于快排的写法,快排就是找到两个左右侧大于和小于枢纽的进行交换,这道题则是找到两个元音开始比较,非常类似,i,j指针分别两端开始,指向中间,直到i<j不成立
###坑点
题目并没有告诉原因字母包括大小写,所以元音有10种而不是五种再初始代码种,所有的字母分别比较,没有用一个数据存储下来,代码写起来很长,也容易出错,修改也不方便,改进方法参考yxc的版本,将字母放于数组,并使用哈希表来判断 class Solution { public: string reverseVowels(string s) { vector<char> vowels = {'a','e','i','o','u','A','E','I','O','U'}; unordered_set<char> S;//建立一个集合,用来判断当前元素是否在集合中,来判断是否元音 for(auto c:vowels){ S.insert(c); } int l = 0,r=s.size()-1; while(l<r){ while(!S.count(s[l])&&l<=r) l++; while(!S.count(s[r])&&l<=r) r--; if(l<r) swap(s[l],s[r]); l++,r--; } return s; } };给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
这道题依然可以用双指针来做,非常类似于归并排序中将两个数组合并的那一步,需要开辟一个新的变量存放合并后的数组,最后再将数组传回nums1中即可
在上面的仿归并算法中,需要临时一个构建一个数组,空间复杂度不是常数,通过观察题,没有充分利用题目所给的条件,nums1已经开够了足够大,如果直接在nums1上合并,便不需要额外的空间,而如果从前往后合并,要么会覆盖元素得到错误结果,要么元素整体后移导致时间复杂度上升n^2,再通过观察,如果从后往前合并的方式,则不会覆盖,也不会时间复杂度退化,是理想的解法,时间O(n),空间常数
class Solution { public: void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) { int p=m-1,q=n-1,cur=m+n-1; while(p>=0&&q>=0) nums1[cur--]=nums1[p]>nums2[q]?nums1[p--]:nums2[q--]; while(q>=0) nums1[cur--] = nums2[q--]; } };给定一个链表,判断是否存在环。
进一步:能否只使用额外 O(1)O(1) 的空间?
经典的快慢指针,类似于操场跑圈,如果两人一块一慢,最终肯定会相遇,如果不是环形的,最终都会到达终点。
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: bool hasCycle(ListNode *head) { if(!head||!head->next) return 0;//如果空链表或者只有一个元素的链表,必然不成环 ListNode *first = head,*second = first->next;//first是块指针,一次移动1,second是慢指针,一次移动2 while(first&&second){//快慢都到达终点停止 if(first==second) return true;//相遇,成环 first = first->next;//快指针移动 second = second->next; if(second) second = second->next;//慢指针移动 } return false; } };在判断是否成环时候除了找到成立情况退出外,也可以反过来写,找到不成了情况退出,找不到就是成立
class Solution { public: bool hasCycle(ListNode *head) { if(!head||!head->next) return 0; ListNode *l = head,*r = l->next; while(l!=r){ if(r==NULL||r->next ==NULL) return false; l=l->next,r=r->next->next; } return true; } };给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串
依然是双指针算法,裸的判断回文串的进阶版本,对于裸的回文串,直接套双指针模板即可,而对于要删除一个字母的情况,当碰到不想等情况时候,可以删除i,也可以删除j因此有两种情况,而且只能删除一次,因此我们可以分别判断删除i和j,只要有一个变成回文串,就成立,而删除i,j结构基本类似,只是i,j起点不同,因此创建一个函数专门用来比较剩余部分是否是回文串。
给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。
改进
class Solution { public: string findLongestWord(string s, vector<string>& d) { //if(d.size()==0) return ""; string res=""; for(auto sub:d) if(issubstr(s,sub)==true) if(sub.size()>res.size()||sub.size()==res.size()&&sub<res) res = sub; return res; } bool issubstr(string s,string d){ int i,j; for( i=0,j=0;i<s.size();i++) if(s[i]==d[j]) j++; return j==d.size(); } };