双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
指针与常量:
int x;
int *p1 = &x;//指针可以被修改,值也可以被修改
const int *p2 = &x;//指针可以被修改,值不可以被修改(const int)
int *const p3 = &x;//指针不可以被修改(*const),值可以被修改
const int *const p4 = &x;//指针不可以被修改,值也不可以被修改
指针函数与函数指针:
// addition是指针函数,一个返回类型是指针的函数
int* addition(int a, int b) {
int* sum = new int(a + b);
return sum;
}
int subtraction(int a, int b) {
return a - b;
}
int operation(int x, int y, int (*func)(int, int)) {
return (*func)(x,y);
}
// minus是函数指针,指向函数的指针
int (*minus)(int, int) = subtraction;
int* m = addition(1, 2);
int n = operation(3, *m, minus);
给你一个下标从 1 开始的整数数组numbers
,该数组已按非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数target
的两个数。如果设这两个数分别是numbers[index1]
和numbers[index2]
,则1 <= index1 < index2 <= numbers.length
。
以长度为2的整数数组[index1, index2]
的形式返回这两个整数的下标index1
和index2
。
你可以假设每个输入 只对应唯一的答案 ,而且你不可以重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
示例 3:
输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
思路:
采用相反方向的双指针来寻找这两个数字:
l
指向最小的元素,即数组最左边,向右遍历;r
指向最大的元素,即数组最右边,向左遍历;代码:
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int l = 0;
int r = numbers.size() - 1;
while(l < r)
{
if (numbers[l] + numbers[r] == target) break;
else if (numbers[l] + numbers[r] < target) l++;
else r--;//注意这里需要r指针左移,所以是--,不是++
}
return vector<int>{l+1,r+1};
}
};
给你两个按非递减顺序排列的整数数组nums1
和nums2
,另有两个整数m
和n
,分别表示nums1
和nums2
中的元素数目。
请你合并nums2
到nums1
中,使合并后的数组同样按非递减顺序排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组nums1
中。为了应对这种情况nums1
的初始长度为m + n
,其中前m
个元素表示应合并的元素,后n
个元素为0
,应忽略。nums2
的长度为n
。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
思路1:双指针法
分别用两个指针指向两个数组,每次取出较小的数字放到新开辟的数组中。(需要额外开辟空间)
思路2:逆向双指针法
nums1
的后半部分是空的,可以直接覆盖而不会影响结果。因此可以指针设置为从后向前遍历,每次取两者之中的较大者放进nums1
的最后面。需要注意的是,如果指向数组1的指针小于0,还需要将数组2的数字一一赋值给数组1,但是如果指向数组2的指针小于0,我们不需要再管数组1剩余的数字,它们已经按照顺序排布好了。
代码1:
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1 = 0;
int p2 = 0;
int sorted[m + n];
int cur;
while(p1 < m || p2 < n)
{
if(p1 == m)
{
cur = nums2[p2++];
}
else if (p2 == n)
{
cur = nums1[p1++];
}
else if(nums1[p1] < nums2[p2])
{
cur = nums1[p1++];
}
else
{
cur = nums2[p2++];
}
sorted[p1 + p2 - 1] = cur;
}
for(int i = 0; i < m + n; i++)
{
nums1[i] = sorted[i];
}
}
};
代码2:
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1 = m - 1;
int p2 = n - 1;
int len = m + n - 1;
while(p1 >= 0 || p2 >= 0)
{
if (p1 == -1) nums1[len--] = nums2[p2--];
else if (p2 == -1) break;//可直接break
else if (nums1[p1] > nums2[p2]) nums1[len--] = nums1[p1--];
else nums1[len--] = nums2[p2--];
}
}
};
对于使用++i
还是i++
,i++
和++i
都是将i
加1
,但是i++
返回值为i
,而++i
返回值为i+1
。如果只是希望增加i
的值,而不需要返回值,则推荐使用++i
,其运行速度会略快一些。
思路:
通用解法——快慢指针(Floyd 判圈法):
给定两个指针,分别命名为slow
和fast
,起始位置在链表的开头。每次fast
前进两步,slow
前进一步。如果fast
可以走到尽头,那么说明没有环路;如果fast
可以无限走下去,那么说明一定有环路,且一定存在一个时刻slow
和fast
相遇。当slow
和fast
第一次相遇时,我们将fast
重新移动到链表开头,并让slow
和fast
每次都前进一步。当slow
和fast
第二次相遇时,相遇的节点即为环路的开始点。
fast | slow | 备注 |
---|---|---|
3 | 3 | |
0 | 2 | |
2 | 0 | |
-4 | -4 | 快慢指针第一次相遇 |
3 | -4 | 快指针回到原点,慢指针仍指向-4 |
2 | 2 | 快慢指针第二次相遇,相遇的节点即为环路开始点 |
如果存在环路,为什么快慢指针一定会相遇?
参考该篇文章:快慢指针一定会相遇的精髓在于当“快指针出现在慢指针后面”之后,每一次“快指针往前走两步、慢指针往前走一步”,相当于快指针和慢指针之间的相对距离减少1步。
假如说,当快指针刚刚绕到慢指针后面时,快指针离慢指针有n步。那么,对于接下来的每一次“快指针往前走两步、慢指针往前走一步”,快指针和慢指针之间的距离由n步变成n-1步、由n-1步变成n-2步、……、由3步变成2步、由2步变成1步、由1步变成0步。
为什么第二次相遇的节点是环路的开始点?
参考力扣题解,个人觉得讲得清晰易懂。
代码:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *slow = head, *fast = head;
//判断是否存在环路
do{
if(!fast || !fast->next) return nullptr; //fast只要不指向NULL即证明有环
fast = fast->next->next;
slow = slow->next;
}while(fast != slow);
fast = head;
while(fast != slow){
slow = slow->next;
fast = fast->next;
}
return fast;
}
};
给你一个字符串s
、一个字符串t
。返回s
中涵盖t
所有字符的最小子串。如果s
中不存在涵盖t
所有字符的子串,则返回空字符串 ""
。
注意:
对于t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。
如果s
中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
示例 2:
输入:s = “a”, t = “a”
输出:“a”
示例 3:
输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
以下为滑动窗口解法思路模板,参考B站视频:
初始化left,right,result,bestResult
while(右指针没有指到结尾){
窗口扩大,加入right对应元素,更新当前result
while(result不满足要求){
窗口缩小,移除left对应元素,left右移
}
更新最优结果bestResult
right++;
}
返回bestResult;
while(右指针没有指到结尾){
窗口扩大,加入right对应元素,更新当前result
while(result满足要求){
更新最优结果bestResult
窗口缩小,移除left对应元素,left右移
}
right++;
}
返回bestResult;
目前还无法完全理解,先暂时跳过这道题,后续弄懂再更新。
代码:
class Solution {
public:
string minWindow(string s, string t) {
//把t中的字符全部放到map中
unordered_map<char,int> m;
for(char c : t) m[c]++;
int count = t.size();
int left = 0;//窗口的左边界
int right = 0;//窗口的右边界
//满足条件的窗口开始位置
int strStart = -1;
//满足条件的窗口的长度
int len = INT_MAX;
while(right < s.size()){
if(m[s[right++]]-- > 0) count--;//如果右指针扫描的字符存在于map中,就减1,记录之后右指针要往右移
//检查窗口是否把t中字符全部覆盖了,如果覆盖了,要移动窗口的左边界
//找到最小的能全部覆盖的窗口
while(count == 0){
//如果现在窗口比之前保存的还要小,就更新窗口的长度
//以及窗口的起始位置
if(right - left < len){
len = right - left;
strStart = left;
}
//移除窗口最左边的元素,也就是缩小窗口,左指针往右移(这块不是很懂!!)
if(m[s[left++]]++ == 0) count++;
}
}
if(strStart == -1) return "";
else return s.substr(strStart,len);
}
};
给定一个非负整数c
,你要判断是否存在两个整数a
和b
,使得a^2
+b^2
=c
。
示例 1:
输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5
示例 2:
输入:c = 3
输出:false
提示:
0 <= c <= 2^31 - 1
代码:
class Solution {
public:
bool judgeSquareSum(int c) {//0是整数 没说两个整数不能一样
long left = 0;
long right = (int)sqrt(c);
long sum;
while(left <= right){
sum = pow(left,2) + pow(right,2);
if(sum < c) left++;
else if(sum > c) right--;//注意是--
else return true;
}
return false;
}
};
给你一个字符串s
,最多可以从中删除一个字符。
请你判断s
是否能成为回文字符串:如果能,返回true
;否则,返回false
。
示例 1:
输入:s = “aba”
输出:true
示例 2:
输入:s = “abca”
输出:true
解释:你可以删除字符 ‘c’ 。
示例 3:
输入:s = “abc”
输出:false
思路:
我的思路是采用相反方向的双指针遍历字符串:
l
初始化指向字符串首,向右遍历;r
初始化指向字符串尾,向左遍历;cnt
计数加1
,左指针右移或右指针左移。最后判断cnt
是否小于等于1
。备注
:这里不能单单只让右指针左移或者左指针右移,因为有可能出现如deeee
或者eeeed
的情况,单独的判断会出错。最终运行的结果还可以,代码应该还有简化的空间,但不管黑猫白猫能抓住老鼠的都是好猫,通过即可:
代码:
class Solution {
public:
bool validPalindrome(string s) {
int n = s.length();
int l1 = 0, l2 = 0;
int r1 = n-1, r2 = n-1;
int cnt1 = 0, cnt2 = 0;
while(l1 <= r1){
if (s[l1] == s[r1])
{
l1++;
r1--;
}
else{
cnt1++;
r1--;
}
}
while(l2 <= r2){
if (s[l2] == s[r2])
{
l2++;
r2--;
}
else{
cnt2++;
l2++;
}
}
return cnt1 <= 1 || cnt2 <= 1;
}
};
给你一个字符串s
和一个字符串数组dictionary
,找出并返回dictionary
中最长的字符串,该字符串可以通过删除s
中的某些字符得到。
如果答案不止一个,返回长度最长且字母序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:s = “abpcplea”, dictionary = [“ale”,“apple”,“monkey”,“plea”]
输出:“apple”
示例 2:
输入:s = “abpcplea”, dictionary = [“a”,“b”,“c”]
输出:“a”
思路:双指针
指针i
指向字符串s
,指针j
指向dictionary
中每一个字符串d
:
d[j] == s[i]
时,i++
,j++
;d[j] != s[i]
时,只有i++
;最后判断指针j
是否指向字典中某字符串的末尾,如果是,则判断字符串的长度和存放结果的字符串res
的长度,若前者长度更长,则存放进res
,若相等,则判断两字符串首字母序哪个更小,更小的存放进res
。
代码:
class Solution {
public:
string findLongestWord(string s, vector<string>& dictionary) {
string res;
for (string d : dictionary){
int j = 0;
for (int i = 0;i < s.size() && j < d.size();i++){
if (d[j] == s[i]){
j++;
}
}
if (j == d.size()){
if (d.size() > res.size()){
res = d;
}
else if (d.size() == res.size()){
res = d < res ? d : res;
}
}
}
return res;
}
};
更新时间:2022/10/03