这道题虽然看起来很简单,用双指针法可以快速解决,但是还是有非常多需要注意的细节的。
我的思路是:有两个指针, l e f t P leftP leftP从数组的左边开始, r i g h t P rightP rightP从数组的右边开始查找,每当发现 l e f t P leftP leftP位置的元素等于目标值时就进行左右两个指针元素的交换,同时记录长度的变量要减一。具体代码如下:
class Solution {
public:
int removeElement(vector& nums, int val) {
int leftP = 0;
int rightP = nums.size()-1;
int lenNums = nums.size(); // 记录长度的变量
if(nums.size() == 0){
return 0; // 为空直接返回
}
while(rightP >= 0 && nums[rightP] == val ){ //这里注意一定要先判断rightP是否大于等于0,否则会出现索引超出范围的问题!
rightP--; // 将右端目标值不断去掉
lenNums--; // 注意更新长度
}
while(leftP <= rightP){
if(nums[leftP] == val){
swap(nums[leftP],nums[rightP]); // 交换两个值
lenNums--;
rightP--; // 只有交换了才需要往后移动
}
leftP++; //放在这里是因为不管有没有交换都要往后移动
while(rightP >= 0 && nums[rightP] == val ){ //这里同样要先判断rightP
rightP--;
lenNums--;
}
}
nums.resize(lenNums);
return lenNums;
}
};
这里要注意循环中的判断条件必须为 l e f t P < = r i g h t P leftP <= rightP leftP<=rightP,因为遍历最后一定是两个指针相等,而此时如果是 l e f t P leftP leftP逐步增加到两个指针相等,虽然进行该循环后如果该值等于 v a l val val时进行交换相当于没有交换,但会使得 l e n N u m s lenNums lenNums减一,也相当于把这个值给去掉了。
代码随想录中的双指针法则更为简便,其主要思想是双指针同时从头开始移动,一旦发现目标值则慢指针暂停移动,快指针继续移动去寻找下一个不是目标的元素后进行交换,具体代码如下:
class Solution {
public:
int removeElement(vector& nums, int val) {
int slowP = 0;
for( int fastP = 0; fastP < nums.size(); fastP++){
if(val != nums[fastP]){
nums[slowP++] = nums[fastP];
}
}
return slowP;
}
};
它的另一种双指针跟我的思路一样单独代码更为简洁方便,很值得学习:
class Solution {
public:
int removeElement(vector& nums, int val) {
int leftIndex = 0;
int rightIndex = nums.size() - 1;
while (leftIndex <= rightIndex) {
// 找左边等于val的元素
while (leftIndex <= rightIndex && nums[leftIndex] != val){
++leftIndex;
}
// 找右边不等于val的元素
while (leftIndex <= rightIndex && nums[rightIndex] == val) {
-- rightIndex;
}
// 将右边不等于val的元素覆盖左边等于val的元素
if (leftIndex < rightIndex) {
nums[leftIndex++] = nums[rightIndex--];
}
}
return leftIndex; // leftIndex一定指向了最终数组末尾的下一个元素
}
};
经过上一道题的提醒,这道题的思路就很清晰了,同样用双指针法可以很快地解决,具体思路为快指针初始化为1,慢指针初始化为0,比较快慢指针对应位置元素是否相等,如果相等则慢指针保持不动,快指针往后移动;如果不相等,则将快指针位置的元素赋予到慢指针的下一个位置处,具体代码如下:
class Solution {
public:
int removeDuplicates(vector& nums) {
if(nums.size() == 0){
return 0;
}
int slowP = 0;
int fastP = 1;
for(; fastP < nums.size() ; fastP ++){
if(nums[slowP] != nums[fastP]){
nums[slowP+1] = nums[fastP];
slowP++;
}
}
return slowP+1;
}
};
其实根据前两道题基本上已经可以掌握双指针法的套路了
那么就是细节上的处理问题了,我们的思路是移动快慢指针,如果为0则慢指针就停住,然后快指针去移动找到下一个非零的位置,再将快指针当前这个非零赋予慢指针这个为零的
class Solution {
public:
void moveZeroes(vector& nums) {
int slowP = 0;
int fastP = 0;
for(; fastP < nums.size(); fastP++){
if( nums[fastP] != 0){
nums[slowP] = nums[fastP];
slowP++;
}
}
for(int i = slowP; i < nums.size(); i++){
nums[i] = 0;
}
}
};
这道题在之前的内容中有,具体查看这篇文章[点此跳转]。我重写了一遍代码如下,解释就看那篇文章中的解释吧。
class Solution {
public:
bool backspaceCompare(string s, string t) {
int sIndex = s.size() - 1;
int tIndex = t.size() - 1;
int sCount = 0;
int tCount = 0;
while(sIndex >= 0 || tIndex >= 0){
while(sIndex >= 0){
if( s[sIndex] == '#'){
sIndex--;
sCount++;
}else if(sCount != 0){
sCount--;
sIndex--;
}else{
break;
}
}
while(tIndex >= 0){
if( t[tIndex] == '#'){
tIndex--;
tCount++;
}else if(tCount != 0){
tCount--;
tIndex--;
}else{
break;
}
}
if( sIndex >= 0 && tIndex >= 0){
if(s[sIndex] != t[tIndex]){
return false;
}
}
else{
if(sIndex >= 0 || tIndex >= 0){
return false;
}
}
sIndex--;
tIndex--;
}
return true;
}
};
这道题也在之前的题目中出现过,我在之前的文章中写了好几种解答方法和详细的注释,这里就不过多做解释了。这里补充上我刚写的代码吧:
class Solution {
public:
vector sortedSquares(vector& nums) {
int leftP = 0;
int rightP = nums.size()-1;
vectorresult(nums.size(),0);
int rIndex = result.size() - 1;
while( leftP <= rightP){
if(nums[leftP]*nums[leftP] > nums[rightP] * nums[rightP]){
result[rIndex] = nums[leftP]*nums[leftP];
rIndex--;
leftP++;
}else{
result[rIndex] = nums[rightP]*nums[rightP];
rIndex--;
rightP--;
}
}
return result;
}
};
这里需要注意一个点:在将值放入result时最好实现确定其大小然后用索引的方式插入,不要不确定大小然后每次都用insert来插入,因为这个函数的时间复杂度会更高,不太好。
除此之外我在力扣中也多做了几道双指针的题目,也将自己的见解放于此处了,之后有补充也会慢慢更新的
这道题如果采用双指针法从两个数组的开头开始比较的话,就需要另外一个数组来存储nums1中前m个字符的内容,为了节约了这些空间,可以从后面开始比较起,即从nums1[m-1]和nums2[n-1]开始比较,将大的逐步放入。
class Solution {
public:
void merge(vector& nums1, int m, vector& nums2, int n) {
int nums1Index = m-1;
int nums2Index = n-1;
int count = m+n-1;
while( nums1Index >= 0 && nums2Index >= 0){
if(nums1[nums1Index] < nums2[nums2Index]){
nums1[count] = nums2[nums2Index];
count--;
nums2Index--;
}else{
nums1[count] = nums1[nums1Index];
count--;
nums1Index--;
}
}
for(;nums1Index >=0;nums1Index--){ //检查是否还剩余元素
nums1[count] = nums1[nums1Index];
count--;
}
for(;nums2Index >= 0; nums2Index--){ //检查是否还剩余元素
nums1[count] = nums2[nums2Index];
count--;
}
}
};
这道题很经典,思路都是非常重要的让快指针走路的速度是慢指针的两倍,如果存在环那么它们一定会相遇,主要就是在一些细节的处理上:
/**
* 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) {
ListNode* fastP = head;
ListNode* slowP = head;
while(fastP != NULL && fastP->next !=NULL && fastP->next->next != NULL){
fastP = fastP->next->next;
slowP = slowP->next;
if(fastP == slowP){
return true;
}
}
return false;
}
};
这道题最简单的思路就是事先将字符串处理一下,只取出其中的字符并且将其转换为小写,之后就采用双指针法就很方便了
class Solution {
public:
bool isPalindrome(string s) {
string temStr;
for(char c:s){
if(isalnum(c)){ // 判断是否是字符和数字
temStr += tolower(c); // 将其转换为小写
}
}
int leftP = 0;
int rightP = temStr.size() - 1;
while(leftP <= rightP){
if(temStr[leftP] != temStr[rightP]){
return false;
}
leftP++;
rightP--;
}
return true;
}
};
这道题我一直想着双指针法,但是目前仍然没有在链表上用双指针法的方法,因此只能将链表上的元素转到数组上,因此空间和时间都挺高的。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
//先将数值复制到数组然后再用双指针法来做
ListNode* temPtr = head;
int lenhead = 0;
while(temPtr != NULL){
lenhead++;
temPtr = temPtr->next;
}
temPtr = head;
vectortemV(lenhead,0);
for(int i = 0; i < lenhead; i++){
temV[i] = temPtr->val;
temPtr = temPtr->next;
}
int leftP = 0;
int rightP = temV.size()-1;
while(leftP <= rightP){
if(temV[leftP] != temV[rightP]){
return false;
}
leftP++;
rightP--;
}
return true;
}
};
这道题也很简单,事先将元音字母的大小写用集合存起来,然后用双指针法一个从头找元音字母,一个从尾找元音字母,找到就交换即可。
class Solution {
public:
string reverseVowels(string s) {
unordered_set temSet{'a','A','e','E','i','I','o','O','u','U'};
int leftP = 0;
int rightP = s.size() - 1;
while(leftP <= rightP){
while( temSet.find(s[leftP]) == temSet.end() && leftP <= rightP){
leftP++;
}
while(temSet.find(s[rightP]) == temSet.end() && leftP <= rightP){
rightP--;
}
if(leftP <= rightP){
swap(s[leftP],s[rightP]);
}
leftP++;
rightP--;
}
return s;
}
};
这道题也是双指针法的典型应用,只要慢指针指向t中的每个字符,然后快指针法在s中找对应字符,找到了t才可以移动,按照此思路补充边界条件即可:
class Solution {
public:
bool isSubsequence(string s, string t) {
int sIndex = 0;
int tIndex = 0;
if(t.size() < s.size()){
return false;
}
while( tIndex < t.size() && sIndex < s.size()){
if( t[tIndex] == s[sIndex]){
sIndex++;
}
tIndex++;
}
if( sIndex == s.size()){
return true;
}
else{
return false;
}
}
};
结合前面反转字符串的题目,因此反转单词这部分很简单,主要就是要用快指针向前探路,找到空格时,快指针和慢指针之间就夹着一个单词,进行翻转即可:
class Solution {
public:
string reverseWords(string s) {
int slowP = 0;
int fastP = 0;
while(fastP < s.size()){
while( fastP < s.size() && s[fastP] != ' '){
fastP++;
}
for(int i = 0; i < (fastP - slowP)/2 ; i++){
swap(s[slowP + i],s[fastP-1-i]);
}
slowP = fastP+1;
fastP = fastP+1;
}
return s;
}
};
这道题虽然在双指针的类型题目中,但我用的方法并不是双指针的方法。由于每个字符的两边都可能会有目标字符的存在,而它与两端的目标字符的距离的较小值就是我们要求的答案,因此可以从两边进行扫描,相当于将每个字符左右两边距离目标字符的距离都求出来,然后取小值即可。
class Solution {
public:
vector shortestToChar(string s, char c) {
vector result(s.size(),0);
int lenS = s.size();
for( int i = 0, cIndex = -lenS; i < lenS; i++){ //注意cIndex初始化
if(s[i] == c){
cIndex = i; //目标字符出现,更新cIndex
}
result[i] = i - cIndex;
}
for ( int i = lenS-1, cIndex = 2 * lenS; i >= 0; i--){
if(s[i] == c){
cIndex = i;
}
result[i] = min(result[i], cIndex - i);
}
return result;
}
};