双指针法(leetcode分类解题,C++代码详细注释)

前言

双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。也可以延伸到多个数组的多个指针。

若两个指针指向同一数组,遍历方向相同且不会相交,则也称为滑动窗口(两个指针包围的区域即为当前的窗口),经常用于区间搜索。

若两个指针指向同一数组,但是遍历方向相反,则可以用来进行搜索,待搜索的数组往往是排好序的。

167.两数之和 II - 输入有序数组

在这里插入图片描述

题解

这道题还是比较简单的,因为数组已经排好序了,所以我们很容易就想到一个方法:

我们可以使用两个指针 s , l s,l s,l,其中 s s s指向最小值, l l l指向最大值。当两指针的和小于目标值时, s s s指针向右移动,当两指针的和大于目标值时, l l l指针向左移动,这样一定可以得到最优解,就是这样。

代码

//过于简单,我就不写注释了。
vector<int> twoSum(vector<int>& numbers, int target) 
{
	int l = 0, r = numbers.size() - 1, sum;
	while (l < r) 
	{
		sum = numbers[l] + numbers[r];
		if (sum == target) break;
		if (sum < target) ++l;
		else --r;
	}
	return vector<int>{l + 1, r + 1};
}

88.合并两个有序数组

在这里插入图片描述

题解
因为这两个数组已经排好序,我们可以把两个指针分别放在两个数组的末尾,即 nums1 的 m m 1 位和 nums2 的 n n 1 位。每次将较大的那个数字复制到 nums1 的后边,然后向前移动一位。
因为我们也要定位 nums1 的末尾,所以我们还需要第三个指针,以便复制。
在以下的代码里,我们直接利用 m 和 n 当作两个数组的指针,再额外创立一个 p p p 指针,起
始位置为 m + n , n 1 m +n ,n 1 m+n,n1。每次向前移动 m m m n n n 的时候,也要向前移动 p p p。这里需要注意,如果 n u m num num
的数字已经复制完,不要忘记把 nums2 的数字继续复制;如果 nums2 的数字已经复制完,剩余
nums1 的数字不需要改变,因为它们已经被排好序。

这道题用图更好理解,所以推荐查看:leetcode官方解题

注意: a++ 和 ++a都是a+1,但是a++ 返回a,然后再将a+1;
++a返回a+1。

代码

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) 
    {
        int p = m-- + n-- -1;  
        //这里厨初始化p = m + n -1,就指向nums1的最后一位,然后n--,m--。
        while(m>=0 && n>=0)
        {
            nums1[p--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];
            //三目运算符        
        }
        while(n >= 0)
        {
            nums1[p--] = nums2[n--];       
        }
    }
};

142. 环形链表 II

在这里插入图片描述

题解
这道题可以使用快慢指针解决,快指针 f a s t fast fast,慢指针 s l o w slow slow,快指针每次走两步,满指针每次走一步,如果链表中存在环路,那么快指针和慢指针一定会相遇。
当 slow 和 fast 第一次相遇时,我们将 fast 重新移动到链表开头,并让 slow 和 fast 每次都前进一步。当 slow 和 fast 第二次相遇时,相遇的节点即为环路的开始点。

代码

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
      if (head == NULL || head->next == NULL)
      //这种情况下一定没环
      {
        return NULL;
      }
      ListNode* fast=head;
      ListNode* slow=head;
      //初始哈快慢指针
      while (fast->next !=NULL && fast->next->next != NULL)
      //确保环存在
      {
        fast=fast->next->next;
        //快指针一次走两步
        slow=slow->next;
        //慢指针一次走一步。
        if (fast==slow)
        //如果快慢指针相遇
        {
          slow=head;
          //慢指针移到链表头
          while (slow != fast)
          {
            slow=slow->next;
            fast=fast->next;
          }//指针再次相遇即为环路开始点。
          return slow;
        }
      }
      return NULL;
      //如果不存在环路,返回NULL。
    }
};

633.平方数之和

在这里插入图片描述

题解
因为 C C C为两个数的平方值和,所以这两个数都小于 s q r t ( C ) sqrt(C) sqrt(C),我们初始化两个指针,一个初始化为0,另一个初始化为 s q r t ( C ) + 1 sqrt(C)+1 sqrt(C)+1,因为 s q r t ( ) sqrt() sqrt()向下取整,所以要加1。如果两数平方值的和大于 C C C,右指针向左移动;如果小于,左指针向右移。

代码


class Solution {
public:
    bool judgeSquareSum(int c) 
    {
        unsigned int left =0,right = sqrt(c)+1;
        //初始化左右指针
        unsigned int powSum;
        //两数平方的和
        while(right>=left)
        {
            powSum = left*left+right*right;
            if(powSum==c) return true;
            else if(powSum>c) --right;
            else if(powSum<c) ++left;
        }
        return false;
    }
};

680. 验证回文字符串 Ⅱ

在这里插入图片描述

题解

代码

//太简单,不写注释
class Solution {
public:
    bool checkPalindrome(const string& s, int low, int high) {
        for (int i = low, j = high; i < j; ++i, --j) {
            if (s[i] != s[j]) {
                return false;
            }
        }
        return true;
    }
    bool validPalindrome(string s) {
        int low = 0, high = s.size() - 1;
        while (low < high) {
            char c1 = s[low], c2 = s[high];
            if (c1 == c2) {
                ++low;
                --high;
            } else 
            {
                return checkPalindrome(s, low, high - 1) || checkPalindrome(s, low + 1, high);
            }
        }
        return true;
    }
};

27. 移除元素

在这里插入图片描述
题解

先来个暴力解法:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) 
    {
        int size =nums.size();
        for(int i =0;i <size;++i)
        {
            if(nums[i]==val)
            {
                for(int j = i+1;j<size;++j)
                {
                    nums[j-1] =nums[j];
                }
                --i;
                --size;
            }
        }
        return size;

    }
};

暴力解法是可以通过的,但是你能满足吗?你不能满足!!!
下面是双指针法:

代码

class Solution {
public:
    int removeElement(vector<int>& nums, int val) 
    {
        int slowIndex = 0;
        for(int fastIndex =0;fastIndex<nums.size();++fastIndex)
        {
            if(val != nums[fastIndex])
            //如果快指针指向val的值,慢指针就会停止,直到快指针指向不同的值。
            {
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }
};

344. 反转字符串

题解

其实这道题可以直接用C++的库函数解决,但这样没有什么意义。

我们定义两个指针(也可以说是索引下表),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。

代码

class Solution {
public:
    void swap(char& i,char &j)
    {
    //这是一种不常见的交换元素方式,根据^(异或)的性质:a ^ a = 0。(参见《深入理解计算机系统》)
        i ^= j;
        j ^= i;
        i ^= j;
    }
    void reverseString(vector<char>& s) 
    {
        for(int i =0,j=s.size()-1;i<s.size()/2;++i,--j)
        {
            swap(s[i],s[j]);
        }        
    }
};

剑指 Offer 05. 替换空格

题解
如果想把这道题目做到极致,就不要只用额外的辅助空间了!

首先扩充数组到每个空格替换成 “%20” 之后的大小。

然后从后向前替换空格,也就是双指针法,过程如下:

i i i指向新长度的末尾, j j j指向旧长度的末尾。
代码

class Solution {
public:
    string replaceSpace(string s) {
        int count = 0; 
        // 统计空格的个数
        int sOldSize = s.size();
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ' ') {
                count++;
            }
        }
        // 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小
        s.resize(s.size() + count * 2);
        int sNewSize = s.size();
        // 从后先前将空格替换为"%20"
        for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
            if (s[j] != ' ') {
                s[i] = s[j];
            } else {
                s[i] = '0';
                s[i - 1] = '2';
                s[i - 2] = '%';
                i -= 2;
            }
        }
        return s;
    }
};

151. 翻转字符串里的单词

在这里插入图片描述

206.反转链表

在这里插入图片描述
代码

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* temp; // 保存cur的下一个节点
        ListNode* cur = head;
        ListNode* pre = NULL;
        while(cur) {
            temp = cur->next;  // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next = pre; // 翻转操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

125. 验证回文串

在这里插入图片描述
代码

class Solution {
public:
    bool isPalindrome(string s) 
    {
        string str;
        for(auto i : s)
        {
            if(isalnum(i))
            //先除了字母和数字之外的其他字符
            //isalnum()函数用来判断是否是字母或者是数字
            {
                str +=tolower(i);
            }
        }
        for(int i = 0,j =str.size()-1; i<j; ++i,--j)
        //通过前后两个指针,判断是否对称。
        {
            if(str[i] !=  str[j])
            {
                return false;
            }
        }
        return true;
    }
};

19. 删除链表的倒数第 N 个结点

在这里插入图片描述
代码

/**
 * 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:
    ListNode* removeNthFromEnd(ListNode* head, int n) 
    {
        if(!head->next) return nullptr;
        //如果链表就一个元素,那也只能去掉一个,所以就返回nullptr。
        ListNode* fast = head;
        ListNode* slow = head;
        //初始化快指针
        //快指针先走n步,然后慢指针在和快指针同步走,这样的话,当快指针走到链表结尾时,慢指针刚好到达我们要去掉的元素之前的一个元素
        //这样slow->next =slow -> next ->next就可以从链表中去掉要除去的元素了。
        for(int i =0; i<n;--n)
        {
            fast=fast->next;
            if(!fast)
            //如果快指针为nullptr说明点前题目要去除的元素为第一个元素,那么,直接返回链表的第二个元素就可以了。
            {
                return head->next;
            }
        }
        while(fast->next)
        {
            slow = slow->next;
            fast = fast->next;
        }
        slow->next =slow->next->next;
        return head;
    }
};

面试题 02.02. 返回倒数第 k 个节点

在这里插入图片描述
题解
这种题的双指针解法大同小异,没有什么新意…
代码

 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    int kthToLast(ListNode* head, int k) 
    {
        ListNode* fast =head;
        ListNode* slow =head;
        for(int i =0;i<k;--k)
        {
            fast =fast->next;
            if(!fast)
            {
                return head -> val;
            }
        }
        while(fast)
        {
            
            fast =fast->next;
            if(!fast)
            {
                return slow->next->val;
            }
            slow =slow->next;
        }
        return -1;
        //这块有一点不完美
        //因为程序不可能运行到这,所以返回啥都可以。

    }
};

持续更新中…

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值