#双指针专题
LeetCode 633 平方数之和#双指针专题
-
1.分析题意:对于题目本身的描述,一些边界条件或者非法输入,提出自己的问题。像面试官表明你对问题已经有了一定的思考
-
给定一个非负整数
c,你要判断是否存在两个整数a和b,使得 a2+b2=c -
0 <= c <= 2^31 - 1
-
首先需要注意a^2超int的问题 可以使用double
-
判断一个数是否为可开方数
-
-
2.描述一个大体思路:通常我们可以先给出一个最基本的暴力解法,再进一步去思考优化方法;
-
暴力解法
-
从前往后枚举++i,j=i*i<=c
-
判断sqrt(c-j)是否是可开方数 sqrt(c-j)==(int)(sqrt(c-j))
-
-
优化解法-双指针
- i,j同时操作 时间复杂度<sqrt(n)
-
-
3.写代码:如果第二步做好了,那么这里其实就是把伪代码或者框图填充完;
class Solution { public: bool judgeSquareSum(int c) { for(double i=0,j=0;j<=c;j=i*i) { //cout<<" "<<j<<" "<<(int)(sqrt(c-j))*(int)(sqrt(c-j))<<endl; if(sqrt(c-j)==(int)sqrt(c-j)) { return true; } i=i+1; } return false; } }; //双指针解法 class Solution { public: bool judgeSquareSum(int c) { unsigned int i=0,j=(int)sqrt(c);//注意sqrt以后是浮点型数据 必须强转 for(;i<=j;) { if(i*i+j*j==c)//这里可能超int 2 31-1 return true; else if(i*i+j*j<c) i++; else j--; } return false; /* for(double i=0,j=0;j<=c;j=i*i) { //cout<<" "<<j<<" "<<(int)(sqrt(c-j))*(int)(sqrt(c-j))<<endl; if(sqrt(c-j)==(int)sqrt(c-j)) { return true; } i=i+1; } return false; */ } }; -
测试:通过自己给的测试用例发现代码的潜在问题,也是面试考察中的一环。通常我们给出一个边界用例,再结合一个常规用例,一步一步地通过语言描述你的算法做了什么,并且可以在白板旁边标注当前的程序运行状态,来检验最后的答案是否正确;
- 普通的样例走一遍流程 11
- 边界值测试是否存在细节错误 2,147,483,648
-
简单分析一下算法的时间和空间复杂度。
-
暴力解法
- 时间:o(sqrt(n))
- 空间:o(1)
-
双指针
- 时间:o(sqrt(n))
- 空间:o(1)
-
LeetCode 167 两数之和 II - 输入有序数组#双指针专题
-
分析题意:
-
给定一个已按照*升序排列* 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
- 返回的下标值(index1 和 index2)不是从零开始的。//从1开始
- 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。//不可以重复使用一个元素
示例:
输入: numbers = [2, 7, 11, 15], target = 9 输出: [1,2] 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。 -
-
解法:
-
暴力解法
- o(n2)找一遍
-
技巧解法
- 先标记一遍整个数组,记录值为其下标,然后从头往后找 target-a[i]是否存在标记,存在即可返回记录下标
- 复杂度o(n)
-
双指针解法
-
-
代码:
-
//技巧解法 class Solution { public: vector<int> twoSum(vector<int>& numbers, int target) { map<int,int>mp; vector<int>ans; for(int i=0;i<numbers.size();i++) { mp[numbers[i]]=i+1; } for(int i=0;i<numbers.size();i++) { if(mp[target-numbers[i]]&&mp[target-numbers[i]]!=i) { ans.push_back(i+1); ans.push_back(mp[target-numbers[i]]); break; } } return ans; } }; //双指针解法 class Solution { public: vector<int> twoSum(vector<int>& numbers, int target) { int i=0,j=numbers.size()-1; for(;i<=j;) { if(numbers[i]+numbers[j]==target) break; else if(numbers[i]+numbers[j]<target) i++; else j--; } vector<int>temp; temp.push_back(i+1); temp.push_back(j+1); return temp; } };
-
LeetCode 345 反转字符串中的元音字母 #双指针专题
-
题目描述
-
编写一个函数,以字符串作为输入,反转该字符串中的元音字母。注意大写和小写都要包括
示例 1:
输入:"hello" 输出:"holle"示例 2:
输入:"leetcode" 输出:"leotcede"提示:
- 元音字母不包含字母 "y" 。
-
-
解题思路
-
双指针
-
但是一定要注意分为4种情况
-
i为元音 j为元音
- 替换之后 i++ j++
-
i为元音 j不为元音
- j++
-
i不为元音 j为元音
- i++
-
i j 都不为元音
- i++,j--
-
-
-
-
代码
-
class Solution { public: string reverseVowels(string s) { map<char,bool>mp; char t; int i=0,j=s.length()-1; mp['a']=mp['e']=mp['i']=mp['o']=mp['u']=mp['A']=mp['E']=mp['I']=mp['O']=mp['U']=true; //字母记得大小写的坑点 //if else if 条件判断充分了 for(;i<=j;) { if(mp[s[i]]&&mp[s[j]]) { //cout<<i<<" "<<j<<endl; t=s[i];//string的单个字符可以和char进行交换 大胆使用 s[i]=s[j]; s[j]=t; i++,j--; continue; } else if(mp[s[i]]&&!mp[s[j]])//这种的以后大胆的使用就行了 j--; else if(mp[s[j]]&&!mp[s[i]]) i++; else i++,j--; } return s; } };
-
-
时间复杂度
- o(n)
LeetCode 680 验证回文字符串 Ⅱ #双指针专题
-
题目描述:
-
给定一个非空字符串
s,最多删除一个字符。判断是否能成为回文字符串。示例 1:
输入: "aba" 输出: True示例 2:
输入: "abca" 输出: True 解释: 你可以删除c字符。注意:
- 字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。
-
-
思路
- 双指针方法
- i=0 j=length-1
- 从前往后筛选 只能换一次 使用flag标记次数
- 如果不能匹配
- 首先查看flag是否标记
- 未标记
- 左+1 和右 左和右-1都可
- 需要判断谁的下一个移动还满足条件
- 注意 都不满足 也有有一个操作 来跳出循环
- 左+1可
- 右-1可
- 左右都不可
- 左+1 和右 左和右-1都可
- 标记了
- 未标记
- 首先查看flag是否标记
- 能匹配
- 左+1 右-1
- 双指针方法
-
代码:
-
//换几次 第一个坑 //怎么换 第二个坑 //第三个坑 没有枚举全 因为第一中两种都可以的情况可以单独考虑成一种成立即可 if(s[i+2]==s[j-1])i++ else if(s[j-2]==s[i+1]) j--; else i++; //第四个坑 i+1 i+2 j-1 j-2 没考虑越界的情况 //总结:记住各个条件的组合 想全了 class Solution { public: bool validPalindrome(string s) { int i=0,j=s.length()-1; bool flag=false;//避免第一个坑 if(j==1||j==0) return true; for(;i<=j;) { if(s[i]==s[j]) i++,j--; else { if(!flag) { flag=true;//避免第二个坑 if(s[i+1]==s[j]&&s[j-1]==s[i]) { if(s[i+2]==s[j-1]) i++; else if(s[j-2]==s[i+1]) j--; else i++; } else if(s[i+1]==s[j]) i++; else if(s[j-1]==s[i]) j--; else return false; } else return false; } } return true; } };
-
-
时间复杂度
- o(n)
LeetCode 88 合并两个有序数组 #双指针专题
-
题目描述
给你两个有序整数数组
nums1和nums2,请你将nums2合并到nums1中,使nums1成为一个有序数组。初始化
nums1和nums2的元素数量分别为m和n。你可以假设nums1有足够的空间(空间大小等于m + n)来保存nums2中的元素。示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3 输出:[1,2,2,3,5,6]示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0 输出:[1]提示:
0 <= m, n <= 2001 <= m + n <= 200nums1.length == m + nnums2.length == n-109 <= nums1[i], nums2[i] <= 109
-
思路分析
-
直观排序
直接先把nums1的m后的数pop_back()掉,然后把nums2 push_back()进去
最后把nums1进行排序
-
双指针
- 开始思考的从前往后找出后一个数组的各位数位置,但是一直卡在这个交换覆盖的位置,需要如何记录,往第二个数组里存 还不知道如何去放,实际上思考到是死路一条,就应该去换一个思路
- 找出两个数组中最小的放在第一个数组的前方 需要三个指针 但存在覆盖问题
- 找出两个数组中最大的 就是指针从最后往前扫描 放在第一个数组后方 正确思路
-
-
代码
//暴力200 nlogn 排序可以 //LeetCode不需要我们来输出 //双指针 没想起来思路 //找最小值 往nums1中放 会覆盖数据 //找最大值 谁大谁放 然后往前移动 //谁移动完 对方移动 直接往里放 class Solution { public: void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) { //双指针解法 int i=m-1,j=n-1,k=m+n-1; for(;i>=0||j>=0;)//i,j的下标先行判断 且应该为有一个条件满足就可以 { if(i<0) nums1[k--]=nums2[j--]; else if(j<0) nums1[k--]=nums1[i--]; else if(nums1[i]>=nums2[j]) nums1[k--]=nums1[i--]; else if(nums2[j]>nums1[i]) nums1[k--]=nums2[j--]; } /*暴力解法 for(int i=0;i<n;i++) nums1.pop_back(); for(int i=0;i<n;i++) { nums1.push_back(nums2[i]); } sort(nums1.begin(),nums1.end()); */ } }; -
时间复杂度
- 双指针 o(n)
- 暴力 o(nlogn)
LeetCode 141 环形链表 #双指针专题
-
题目描述
-
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪
next指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数pos来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果pos是-1,则在该链表中没有环。注意:pos不作为参数进行传递,仅仅是为了标识链表的实际情况。如果链表中存在环,则返回
true。 否则,返回false。//注意实例里的输入 pos 只是为了我的输入方便
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
示例 1:
![img]()
输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。示例 2:
![img]()
输入:head = [1,2], pos = 0 输出:true 解释:链表中有一个环,其尾部连接到第一个节点。示例 3:
![img]()
输入:head = [1], pos = -1 输出:false 解释:链表中没有环。提示:
- 链表中节点的数目范围是
[0, 104] -105 <= Node.val <= 105pos为-1或者链表中的一个 有效索引 。
- 链表中节点的数目范围是
-
-
方法解析
- 开始暴力思路 是死循环 考虑不周 从某点开始
- 双指针思路
- i每次走一步 j每次走两步 这样存在环的话 他们一定会相遇 证明省略 这个是参考github上的思路
-
代码展示
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ //一开始的暴力思路>n^2 有时候还是死循环 //双指针思路 i每次走一步 j每次走两步 这样存在环的话 他们一定会相遇 证明省略 class Solution { public: bool hasCycle(ListNode *head) { if(head==NULL)//i不能指向空最好 return false; ListNode *i=head,*j=head->next;//避免一开始 他们俩就假相等 while(i!=NULL&&j!=NULL&&j->next!=NULL) { if(i==j) return true; else i=i->next,j=j->next->next; } return false; } }; -
时间复杂度
LeetCode 524 通过删除字母匹配到字典里最长单词 #双指针专题
-
题目描述
-
给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入: s = "abpcplea", d = ["ale","apple","monkey","plea"] 输出: "apple"示例 2:
输入: s = "abpcplea", d = ["a","b","c"] 输出: "a"说明:
- 所有输入的字符串只包含小写字母。
- 字典的大小不会超过 1000。
- 所有输入的字符串长度不会超过 1000。
-
-
方法解析
-
排序+剪支
- 排序完了以后 进行子串匹配 第一个匹配的就是结果
-
不排序
-
拿target记录被匹配的结果
-
后续的匹配项先和他进行比较进行剪枝
-
-
-
代码展示
class Solution { public: static bool cmp(string s1,string s2) { if(s1.length()!=s2.length()) { return s1.length()>s2.length(); } else return s1<s2; } string findLongestWord(string s, vector<string>& d) { /*不排序 + 剪枝 string target=""; for(int ii=0;ii<d.size();ii++) { if(target.length()>d[ii].length()||(target.length()==d[ii].length()&&target<d[ii])) continue; int i=0,j=0; while(i<s.size()&&j<d[ii].size()) { if(s[i]==d[ii][j]) i++,j++; else i++; } if(j==d[ii].size()) target=d[ii]; } return target;*/ //排序剪纸 方法 效率不太行 sort(d.begin(),d.end(),cmp); //我的暴力代码 时间复杂度是o(n^2) //此题可以不排序 也可以 但是需要对比完所有的可能结果 //剪枝是通用方法来优化 for(int ii=0;ii<d.size();ii++) { if(s.length()<d[ii].length()||(s.length()==d[ii].length()&&(s<d[ii]||s>d[ii]))) continue; int i=0,j=0; for(;i<s.length()&&j<d[ii].length();) { if(s[i]==d[ii][j]) i++,j++; else i++; } if(j==d[ii].size()) return d[ii]; } return ""; } }; -
时间复杂度
- 排序+剪支
- o(n^2)
- 双指针解法
- o(n^2)
- 排序+剪支



浙公网安备 33010602011771号