​ 本文记录作者刷题过程中与双指针相关的题目。

1、删除数组中的元素

(1)删除数组中的某个(所有的)目标值

27. 移除元素(简单难度)

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int n=nums.size();
        // 数组中至少有0个元素
        int slow=0; // 慢指针,[0,low),指向删除后的区间的最后一个元素的后一个索引
        int fast = 0;// 快指针,负责遍历整个数组
        while(fast < n)
        {
            //快指针不等于目标值时
            if (nums[fast] != val) 
            {
                nums[slow] = nums[fast];//快指针指向的数覆盖给慢指针指向的数
                slow++;//慢指针+1
                fast++; //快指针+1
            }
            // 快指针等于目标值时,快指针+1
            else{
                fast++;//快指针+1
            }
        }
        return slow;//返回慢指针
    }
};

(2)删除有序数组中的重复项

26. 删除有序数组中的重复项(简单难度)

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int n=nums.size();
        if(n==0) return 0;
        // 数组中至少有1个元素
        int slow=1;// 慢指针,[0,low),指向删除后的区间的最后一个元素的后一个索引
        int fast=1;// 快指针,负责遍历整个数组
        while(fast<n){
            // 快指针和上一个元素不同时,
            if(nums[fast]!=nums[slow-1]){
                nums[slow]=nums[fast];// 快指针元素覆盖慢指针元素
                slow++;//慢指针前进
                fast++;//快指针前进
            }
            // 快指针和上一个元素相同时,快指针前进
            else{
                fast++;
            }
        }
        return slow;
    }
};

80. 删除有序数组中的重复项 II(中等难度)

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int n=nums.size();
        if(n<=2) return n;
        // 数组中至少有2个元素
        int slow=2;// 慢指针,[0,low),指向删除后的区间的最后一个元素的后一个索引
        int fast=2;// 快指针,负责遍历整个数组
        while(fast<n){
            // 快指针不等于
            if(nums[fast]!=nums[slow-2]){
                nums[slow]=nums[fast];
                slow++;
                fast++;
            }
            else{
                fast++;
            }
        }
        return slow;
    }
};

2、移动数组中的元素

283. 移动零(简单难度)

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int n=nums.size();
        // 数组中至少有0个元素
        int slow=0;// 慢指针,[0,low),指向移动后的区间的最后一个元素的后一个索引
        int fast=0;// 快指针,负责遍历整个数组
        while(fast<n){
            // 如果快指针不为0
            if(nums[fast]!=0){
                swap(nums[slow],nums[fast]);// 快慢交换
                slow++;
                fast++;
            }
            else{
                fast++;
            }
        }
    }
};

75. 颜色分类(中等难度)

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int n=nums.size();
        if(n<2) return;
        // 整个数组需要分成3个区间:
        int zero = 0;//[0,zero),红色元素,存放0
        int one = 0;//[zero,one),白色元素,存放1,同时负责遍历
        int two = n-1;// [one,two),篮色元素,存放2
        while (one < n) {
            // 如果当前元素==0,移动到头部
            if (nums[one] == 0&&zero<one) {
                swap(nums[one], nums[zero]);//
                zero++;
            }
            // 如果当前元素==2,移动到表尾
            else if (nums[one] == 2&& one<two) {
                swap(nums[one], nums[two]);
                two--;
            }
            // 如果当前元素==1,跳过
            else {
                one++;
            }
        }
        //quickSort(nums,0,nums.size()-1);
    }
}

215. 数组中的第K个最大元素(中等难度)

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        int len = nums.size();
        return quickFind(nums,k-1,0,len-1);//从大到小排序,第k大就是第k-1个索引,返回第k-1个索引的值
    }
    // 快排法
    int quickFind(vector<int>& nums, int k,int left,int right){
        int index = partition(nums,left,right);//开始分区
        // 结束分区
        if(index==k){
            return nums[index];//first是基准值索引,表示第first个
        }
        else if(index>k){
            return quickFind(nums, k,left,index-1);
        }
        else {
            return quickFind(nums, k,index+1,right);
        }
    }
    // 分区函数
    int partition (vector<int> &nums, int first, int last) {
        int key = nums[first];//默认第一个为基准值
        while (first < last) {
            // 从右往左找到第一个小于key的值
            while (first < last && nums[last] <= key) --last;
            nums[first] = nums[last];
            // 从左往右找到第一个大于key的值
            while (first < last && nums[first] >= key) ++first;
            nums[last] = nums[first];
        }
        nums[first] = key;// 基准值归位
        return first;// 返回索引
    } 
};

905. 按奇偶排序数组(简单难度)

class Solution {
public:
    /*思路:这道题解决不难,使用额外的数组,只要会判断奇偶数就可以分出两类数,难点在于原地排序算法:双指针的方法:
    左指针寻找奇数值,右指针寻找偶数值,当符合交换条件时,进行两数交换;
    否则指针继续左右运动,寻找符合条件的奇偶值。
    当两指针相遇时,结束循环。*/
    vector<int> sortArrayByParity(vector<int>& nums) {
        int left=0;//左指针
        int right=nums.size()-1;//右指针
        // 遍历数组[left,right],
        while(left<right){
            // 如果左指针指向奇数,右指针指向偶数,就交换两个数
            if (nums[left] % 2 > nums[right] % 2) {
                swap(nums[left],nums[right]);
            }
            // 如果左指针指向偶数,左指针移动
            if (nums[left] % 2 == 0) left++;
            // 如果右指针指向奇数,右指针移动
            if (nums[right] % 2 == 1) right--;
        }
        return nums;
    }
};

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面(简单难度)

class Solution {
public:
    /*思路:这道题解决不难,使用额外的数组,只要会判断奇偶数就可以分出两类数,难点在于原地排序算法:双指针的方法:
    左指针寻找偶数值,右指针寻找奇数值,当符合交换条件时,进行两数交换;
    否则指针继续左右运动,寻找符合条件的奇偶值。
    当两指针相遇时,结束循环。*/
    vector<int> exchange(vector<int>& nums) {
        int left=0;//左指针
        int right=nums.size()-1;//右指针
        // 遍历数组[left,right],
        while(left<right){
            // 如果左指针指向偶数,右指针指向奇数,就交换两个数
            if (nums[left] % 2 < nums[right] % 2) {
                swap(nums[left],nums[right]);
            }
            // 如果左指针指向奇数,左指针移动
            if (nums[left] % 2 == 1) left++;
            // 如果右指针指向偶数,右指针移动
            if (nums[right] % 2 == 0) right--;
        }
        return nums;
    }
};

922. 按奇偶排序数组 II(简单难度)

class Solution {
public:
    /*思路:这道题解决不难,使用额外的数组,只要会判断奇偶数就可以分出两类数,难点在于原地排序算法:双指针的方法:
    遍历所有偶数索引*/
    vector<int> sortArrayByParityII(vector<int>& nums) {
        //
        int n = nums.size();
        int j = 1;//奇数索引
        // 遍历整个数组的所有偶数索引
        for (int i = 0; i < n; i += 2) {
            // 如果偶数索引上的数是奇数
            if (nums[i] % 2 == 1) {
                // 连续遍历整个数组的所有奇数索引,找到一个数是偶数
                while (nums[j] % 2 == 1) j += 2;
                swap(nums[i], nums[j]);//交换两个数
            }
        }   
        return nums;
    }
};

3、查找排序数组中的2个数

剑指 Offer 57. 和为s的两个数字(简单难度)

剑指 Offer II 006. 排序数组中两个数字之和(简单难度)

class Solution {
public:
    /*解析:这道题在一个排序数组中查找2个数,使其和为target
    提到排序,可能会选用二分查找法,二分查找法确实可以结合使用
    但是这道题难度简单,用普通的双指针就可以搞定
    排序数组的特点可以作为移动左右指针的依据,和较小右移左指针,和较小左移右指针
    */
    vector<int> twoSum(vector<int>& nums, int target) {
        int left=0;//左指针
        int right=nums.size()-1;//右指针
        int sum=0;
        while(left<=right){
            sum=nums[left]+nums[right];
            // 如果两数和==target,直接返回
            if(sum==target) return {nums[left],nums[right]};
            // 如果两数和>target,说明应该降低两数和,右指针左移即可
            else if(sum>target) right--;
            // 如果两数和<target,说明应该增加两数和,左指针右移即可
            else left++;
        }
        return {nums[left],nums[right]};
    }
};

4、其他问题

977. 有序数组的平方(简单难度)

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int n=nums.size();
        vector<int> res(n,0);
        int k=n-1;
        int left=0;
        int right=n-1;
        while(left<=right){
            // 左右指针,谁大加谁
            if((nums[left]*nums[left])<(nums[right]*nums[right]))  {
                res[k--]=nums[right]*nums[right];
                right--;
            }
            else{
                res[k--] = nums[left]*nums[left];
                left++;
            }
        }
        return res;
    }
};

169. 多数元素(简单难度)

class Solution {
public:
    int majorityElement1(vector<int>& nums) {
        // 统计nums中每个数组元素出现的频率
        unordered_map<int,int> dict;// nums中每个数,出现频率
        for(int num:nums) dict[num]++;
        //建立哈希数组找其中出现次数大于n/2的数
        int n=nums.size();
        for(auto it=dict.begin();it!=dict.end();++it){
            if(it->second>n/2)
                return it->first;
        }
        return 0;
    }
    // 方法二
    int majorityElement(vector<int>& nums) {
        if (nums.size()==1) return nums[0];
        //排序
        sort(nums.begin(),nums.end());
        vector<int> res;
        // 使用双指针查找所有可能的众数
        int cur=0;//当前数
        int count=0;//计数器
        int maxCount=0;//最大计数
        int slow=0; //慢指针
        int fast=1;// 快指针
        //统计前一个数和后一个数相同的频率
        while(fast<nums.size()){
            //如果当前数与前一个数相同
            if(nums[fast]==nums[slow]) {
                count++;
                cur= nums[fast];//记录快指针手中的数
                slow++;
                fast++;
            }
            //如果当前数与前一个数不相同
            else{
                count=1;
                slow++;
                fast++;
            }
            //如果计数超过最大计数
            if(count>maxCount){
                maxCount=count;//更新最大计数
                res.clear();//清空众数数组(之前的失效)
                res.push_back(cur);//将当前数加入众数数组
            }
            //如果计数等于最大计数
            else if(count==maxCount){
                res.push_back(cur);//将当前数加入众数数组
            }
        }
        return res[0];
    }
};

941. 有效的山脉数组

class Solution {
public:
    bool validMountainArray(vector<int>& arr) {
        int N = arr.size();
        int i = 0;
        // 递增扫描
        while (i+1<N&&arr[i]<arr[i+1]) i++;
        // 最高点不能是数组的第一个位置或最后一个位置
        if(i==0||i==N-1) return false;
        // 递减扫描
        while(i+1<N&&arr[i]>arr[i + 1]) i++;
        return i == N-1;
    }
};

189. 轮转数组

class Solution {
public:
    // 方法一:使用额外数组
    void rotate1(vector<int>& nums, int k) {
        int n=nums.size();
        vector<int> res(n);
        int j=0;
        for(int i=0;i<n;i++){
            j=(i+k)%n;
            res[j]=nums[i];
        }
        nums.assign(res.begin(), res.end());
    }
    // 方法二:数组反转
    void rotate(vector<int>& nums, int k) {
        int n=nums.size();// 1 2 3 4 5 6 7,k=3
        k = k%n;
        reverse(nums.begin(), nums.end());// 反转[0,n-1] 7 6 5 4 3 2 1
        reverse(nums.begin(), nums.begin()+k);// 反转[0,k-1] 5 6 7 4 3 2 1
        reverse(nums.begin()+k, nums.end());// 反转[k,n-1] 5 6 7 1 2 3 4
    }
};

11. 盛最多水的容器

class Solution {
public:
    /* 左右指针分别从数组两端出发,容量的高为min(height[left],height[right])
    容量的长为right-left,则容量可以计算出来
    移动左右指针时,谁小移动水*/
    int maxArea(vector<int>& height) {
        int left = 0, right = height.size() - 1;
        int res = 0;
        while (left < right) {
            int area =min(height[left], height[right])*(right-left);
            res = max(res, area);
            if (height[left]<= height[right]) ++left;
            else --right;
        }
        return res;
    }
};

238. 除自身以外数组的乘积

class Solution {
public:
    /*原      数组:  [1       2       3       4]
      左部分的乘积:   1       1      1*2    1*2*3
      右部分的乘积: 2*3*4    3*4      4      1
      结        果:1*2*3*4  1*3*4   1*2*4  1*2*3*1*/
    vector<int> productExceptSelf(vector<int>& a) {
        int n=a.size();
        vector<int> res(n,1);
        // 左遍历
        int left=1;// [0,i-1]的累乘
        for(int i=0;i<n;i++){
            res[i]=left;
            left*=a[i];
        }
        // 右遍历
        int right=1;// [i+1,n-1]的累乘
        for(int i=n-1;i>=0;i--){
            res[i]*=right;
            right*= a[i]; 
        }
        return res;
    }
};

392. 判断子序列

bool isSubsequence(string s, string t) {
        int n = s.length(), m = t.length();
        int i = 0, j = 0;
        while (i<n&&j<m) {
            if (s[i] == t[j]) i++;
            j++;
        }
        return i == n;
    }

581. 最短无序连续子数组

class Solution {
public:
   /*从左往右遍历,max记录的是0到i最大的数,
   如果第i个位置比max小,证明第i位置元素不正确,
   因为它前面有个比它大的数,记录下标high=i
   从右往左遍历,min记录的是n-1到i最小的数,
   如果第i位置元素比min大,证明第i位置元素不正确,
   因为它后面有比它小的数,记录下标low=i
   区间[low,high]即为所求*/
    int findUnsortedSubarray(vector<int>& nums) {
        int n=nums.size();
        if(n<=1) return 0;
        int max=nums[0];// 记录正序遍历中的最大值
        int min=nums[n-1];// 记录反序遍历中的最小值
        int low=1,high=0;// 最短无序连续子区间就是[low,high]
        for(int i=1;i<=n-1;++i){
            if(nums[i]>=max) max=nums[i];// 注意=
            else high=i;
        }
        for(int i=n-2;i>=0;--i){
            if(nums[i]<=min) min=nums[i];// 注意=
            else low=i;
        }
        // 子区间可能不存在,需要讨论
        return low<=high?high-low+1:0;
    }
};