前言
欢迎大家积极在评论区留言发表自己的看法,知无不言,言无不尽,养成每天刷题的习惯,也可以自己发布优质的解题报告,供社区一同鉴赏,吸引一波自己的核心粉丝。
今天是七月集训第五天:双指针🔥🔥🔥
一、练习题目
二、算法思路
- 1、541. 反转字符串 II:🔥这题之前做过,分阶段去翻转就好了,到2k个了如果剩余字符不足k就全部翻转,如果满足大于k小于2k的就翻转前k个即可。
- 2、392. 判断子序列:🔥双指针。
- 3、面试题 16.24. 数对和:🔥排序后,利用双指针计算两数之和。
- 4、696. 计数二进制子串:🔥🔥🔥这题挂着easy的标签,实则想明白是需要花点时间的,想明白了就会发现这是个有意思的问题。
三、源码剖析
// 541. 反转字符串 II
class Solution {
public:
string reverseStr(string s, int k) {
int n = s.length();
for (int i = 0; i < n; i += 2 * k)
{
reverse(s.begin() + i, s.begin() + min(i + k, n));
}
return s;
}
};
- 1、看思路详解。
// 392. 判断子序列
class Solution {
public:
bool isSubsequence(string s, string t) {
int n = s.size(), m = t.size(), i = 0, j = 0;
while (i < n && j < m) {
if (s[i] == t[j]) //(1)
i++;
j++;
}
return i == n; //(2)
}
};
- 1、要找s是不是t的子序列,如果找到了i和j都要往后移;否则的话只移动j继续找;
- 2、最后看下是不是和s的长度相等。
// 面试题 16.24. 数对和
class Solution {
public:
vector<vector<int>> pairSums(vector<int>& nums, int target) {
vector<vector<int>> ans;
sort(nums.begin(), nums.end()); //(1)
int i = 0, j = nums.size() - 1;
while(i < j) {
int val = nums[i] + nums[j];
if(val > target) {
--j;
} else if(val < target) {
++i;
} else {
ans.push_back({nums[i], nums[j]}); //(2)
++i;
--j;
}
}
return ans;
}
};
- 1、排序之后利用双指针去求两数之和;
- 2、这里是有大于小于和等于三种情况考虑,大于就把j减小,小于把i增大,如果等于的话说明找到了一个满足条件的放入二维数组中去,再移动双指针。
// 696. 计数二进制子串
class Solution {
public:
int countBinarySubstrings(string s) {
vector<int> counts;
s += '-'; //(1)
int n = s.length();
int count = 1;
for(int i = 1; i < n; ++i) {
if(s[i] == s[i - 1]) {
count++;
} else {
counts.push_back(count);
count = 1;
}
} //(2)
int ans = 0;
for(int i = 1; i < counts.size(); ++i) {
ans += min(counts[i], counts[i - 1]);
} //(3)
return ans;
}
};
// 双指针
class Solution {
public:
int countBinarySubstrings(string s) {
s += '-';
int n = s.length();
int count = 1, prev = 0;
int ans = 0;
for(int i = 1; i < n; ++i) {
if(s[i] == s[i - 1]) {
count++;
} else {
ans += min(count, prev);
prev = count;
count = 1;
}
}
return ans;
} //(5)
};
- 1、在字符串末尾加一个东西,不然我们去比较最后一个字符是不会被记录的,就像分割字符串的时候,我们也需要在最后一个末尾加入不相干的字符辅助;
- 2、这一步是在做一个统计并且拆分的事情,举一个例子:
"00110011
,这是题目的例子,这一步就是一次统计连续的0
或者1
的个数,放到counts数组中去,"00110011
这个例子应该很容易看出,2个0、2个1、2个0再接2个1,counts{2,2,2,2}
; - 3、这时候去遍历counts,先看
counts[0]
和counts[1]
,得到的是2个0和2个1,即0011
,这里的贡献其实就是2种情况01
和0011
,即min(counts[0], counts[1])
,那么接下去遍历1100
贡献也是2,得到的是10
和1100
……所以推出每一个位置对答案的贡献是min(counts[i], counts[i - 1])
。 - 4、我上面这个例子可能也比较特殊连续的个数都是2,那再不明白我们再举例子好了,比如
00111011
,这样counts{2,3,1,2}
。接下去逐个去遍历counts,当2个0和3个1,最多贡献01
和0011
2种;当3个1和1个0,最多贡献10
1种;当1个0和2个1,最多贡献01
1种,最终就是4种情况。 - 5、上面用数组需要O(n)的空间的空间复杂度,但是我们其实考虑的是当前位置i和i - 1位置之间的关系,那我们用一个prev指针记录前一个位置的值就只需要O(1)的空间复杂度了。来一张对比图: