刷题11 双指针

一、同向双指针


209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0 。

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3]
输入:target = 4, nums = [1,4,4]
输出:1
int minSubArrayLen(int target, int* nums, int numsSize) {
    int l=0,r=0;
    int sum=0;
    int ans=INT_MAX;
    while(r<numsSize){
        sum+=nums[r];
        while(sum>=target){
            ans=fmin(ans,r-l+1);
            sum-=nums[l];
            l++;
        }
        r++;
    }
    return ans==INT_MAX?0:ans;
}


713. 乘积小于 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你返回子数组内所有元素的乘积严格小于 k 的连续子数组的数目。

输入:nums = [10,5,2,6], k = 100
输出:8
解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2],、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。
int numSubarrayProductLessThanK(int* nums, int numsSize, int k) {
    int l=0,r=0;
    int sum=1;
    int ans=0;
    while(r<numsSize){
        sum*=nums[r];
        //注意此处l不能超过r
        while(l<=r&&sum>=k){
            sum/=nums[l];
            l++;
        }
        ans+=r-l+1;
        r++;
    }
    return ans;
}


LCR 016. 无重复字符的最长子串

 给定一个字符串 s ,请你找出其中不含有重复字符的 最长连续子字符串 的长度。

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子字符串是 "abc",所以其长度为 3。
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子字符串是 "b"所以其长度为 1。

        此题不是只有字母,因此无法通过,有除字母以外的字符之外出现的话需要用map进行记录。 

int lengthOfLongestSubstring(char * s){
    if(s[0]==' ') return 1;
    int flag[256]={0};
    int l=0,r=0;
    int ans=0;
    int n=strlen(s);
    while(r<n){
        flag[s[r]]++;
        while(flag[s[r]]>1){
            flag[s[l]]--;
            l++;
        }  
        ans=fmax(ans,r-l+1);
        r++;
    }
    return ans;
}

        c++版本:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n=s.size();
        int left=0;
        int answer=0;
        set<char> st;
        for(int right=0;right<n;++right){
            while(st.find(s[right])!=st.end()){//若集合中已经出现过
                st.erase(s[left]);//讲其从集合中删去
                ++left;//左指针右移
            }
            st.insert(s[right]);//若出现过,则加入集合中
            answer=max(answer,right-left+1);//更新最大长度
        }
        return answer;
    }
};

LCR 119. 最长连续序列 

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
int cmp(int* a,int* b){
    return *a-*b;
}
int longestConsecutive(int* nums, int numsSize){
    if(numsSize==0) return 0;
    qsort(nums,numsSize,sizeof(int),cmp);
    int ans=1,cur=1;
    int l=0,r=1;
    while(r<numsSize){
        if(nums[r-1]+1==nums[r]){
            cur++;
            ans=fmax(ans,cur);
        }
        //若遇到相同元素,什么也不做
        //若遇到不同元素,且不连续,就更新各值
        else if(nums[r-1]!=nums[r]){
            l=r;
            cur=1;
        }
        r++;
    }
    return ans;
}

2414. 最长的字母序连续子字符串的长度 

字母序连续字符串 是由字母表中连续字母组成的字符串。换句话说,字符串 "abcdefghijklmnopqrstuvwxyz" 的任意子字符串都是 字母序连续字符串 。

  • 例如,"abc" 是一个字母序连续字符串,而 "acb" 和 "za" 不是。

给你一个仅由小写英文字母组成的字符串 s ,返回其 最长 的 字母序连续子字符串 的长度。

输入:s = "abacaba"
输出:2
解释:共有 4 个不同的字母序连续子字符串 "a"、"b"、"c" 和 "ab" 。
"ab" 是最长的字母序连续子字符串。
int longestContinuousSubstring(char* s) {
    int ans=1,n=strlen(s);
    int count=1;
    for(int i=1;i<n;++i){
        if(s[i-1]+1==s[i]){
            count++;
        }else{
            count=1;
        }
        ans=fmax(ans,count);
    }
    return ans;
}

 

 674. 最长连续递增序列

 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 l 和 rl < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

输入:nums = [1,3,5,4,7]
输出:3
解释:最长连续递增序列是 [1,3,5], 长度为3。

             双指针 

int findLengthOfLCIS(int* nums, int numsSize) {
    if(numsSize==0) return 0;
    int ans=1;
    int l=0,r=1;
    while(r<numsSize){
        if(nums[r-1]<nums[r]){
            r++;
            ans=fmax(ans,r-l);
        }else{
            l=r;
            r++;
        }
    }
    return ans;
}

         朴素解法,计数

int findLengthOfLCIS(int* nums, int numsSize) {
    if(numsSize==0) return 0;
    int ans=1;
    int count=1;
    for(int i=1;i<numsSize;++i){
        if(nums[i-1]<nums[i]){
            count++;
        }else{
            count=1;
        }
        ans=fmax(ans,count);
    }
    return ans;
}

1574. 删除最短的子数组使剩余数组有序

 给你一个整数数组 arr ,请你删除一个子数组(可以为空),使得 arr 中剩下的元素是 非递减 的。一个子数组指的是原数组中连续的一个子序列。请你返回满足题目要求的最短子数组的长度。

输入:arr = [1,2,3,10,4,2,3,5]
输出:3
解释:我们需要删除的最短子数组是 [10,4,2] ,长度为 3 。剩余元素形成非递减数组 [1,2,3,3,5] 。
另一个正确的解为删除子数组 [3,10,4] 。
int findLengthOfShortestSubarray(int* arr, int arrSize){
    int r=arrSize-1;
    while(r&&arr[r-1]<=arr[r]){
        r--;
    }
    //当前数列已经是递增的
    if(r==0) return 0;
    int ans=r;
    //枚举左区间起点
    for(int l=0;l==0||arr[l-1]<=arr[l];++l){
        while(r<arrSize&&arr[r]<arr[l]){
            r++;
        }
        ans=fmin(ans,r-l-1);
    }
    return ans;
}


1004. 最大连续1的个数 III

 给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。

输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
int longestOnes(int* nums, int numsSize, int k) {
    int l=0,r=0;
    int zero=0;
    int ans=0;
    while(r<numsSize){
        if(nums[r]==0) zero++;
        while(zero>k){
            if(nums[l]==0) zero--;
            l++;
        }
        ans=fmax(ans,r-l+1);
        r++;
    }
    return ans;
}


1234. 替换子串得到平衡字符串

 有一个只含有 'Q', 'W', 'E', 'R' 四种字符,且长度为 n 的字符串。

假如在该字符串中,这四个字符都恰好出现 n/4 次,那么它就是一个「平衡字符串」。给你一个这样的字符串 s,请通过「替换一个子串」的方式,使原字符串 s 变成一个「平衡字符串」。

你可以用和「待替换子串」长度相同的 任何 其他字符串来完成替换。请返回待替换子串的最小可能长度。如果原字符串自身就是一个平衡字符串,则返回 0

输入:s = "QWER"
输出:0
解释:s 已经是平衡的了。
输入:s = "QQWE"
输出:1
解释:我们需要把一个 'Q' 替换成 'R',这样得到的 "RQWE" (或 "QRWE") 是平衡的。
int balancedString(char* s) {
    int hash[26]={0};
    int l=0,r=0;
    int len=strlen(s);
    int n=len/4;
    int ans=len;
    //记录每个字母出现的次数
    for(int i=0;i<len;++i){
        hash[s[i]-'A']++;
    }
    //如果满足条件,直接返回0
    if(hash['Q'-'A']==n&&hash['Q'-'A']==n&&hash['W'-'A']==n&&hash['R'-'A']==n){
        return 0;
    }
    //快指针向前遍历
    for(r=0;r<len;++r){
        //快指针每走一步状态更新
        hash[s[r]-'A']--;
        //慢指针按条件收缩窗口
        while(l<=r&&hash['Q'-'A']<=n&&hash['W'-'A']<=n&&hash['E'-'A']<=n&&hash['R'-'A']<=n){
            //慢指针每走一步状态更新
            hash[s[l]-'A']++;
            //记录窗口最值
            ans=fmin(ans,r-l+1);
            l++;
        }
    }
    return ans;
}


1658. 将 x 减到 0 的最小操作数

 给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。

输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最佳解决方案是移除后两个元素,将 x 减到 0 。

方法一:逆向思维+双指针
把问题转换成「从 nums 中移除一个最长的子数组,使得剩余元素的和为 x。换句话说,要从 nums 中找最长的子数组,其元素和等于 s−x,这里 s 为 nums 所有元素之和。

int minOperations(int* nums, int numsSize, int x) {
    //从nums中移除一个最长的子数组,使得剩余元素的和为x
    int target=0;
    for(int i=0;i<numsSize;++i){
        target+=nums[i];
    }
    target-=x;
    if(target<0) return -1;
    int l=0,r=0;
    int ans=-1,sum=0;
    for(r=0;r<numsSize;++r){
        sum+=nums[r];
        while(sum>target){
            sum-=nums[l];
            l++;
        }
        if(sum==target){
            ans=fmax(ans,r-l+1);
        }
    }
    return ans<0?-1:numsSize-ans;
}

二、对撞双指针


15. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
         vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if (nums[i] > 0) {
                return result;
            }
            if (i > 0 && nums[i] == nums[i - 1]) {//去重
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
                if (nums[i] + nums[left] + nums[right] > 0) right--;
                else if (nums[i] + nums[left] + nums[right] < 0) left++;
                else {
                    result.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;
                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }

        }
        return result;
    }
};


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

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列  ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1  index2

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
int* twoSum(int* numbers, int numbersSize, int target, int* returnSize) {
    int *res=malloc(sizeof(int)*2);
    *returnSize=2;
    int l=0,r=numbersSize-1;
    while(l<r){
        int sum=numbers[l]+numbers[r];
        if(sum<target){
            l++;
        }else if(sum>target){
            r--;
        }else{
            res[0]=l+1;res[1]=r+1;
            return res;
        }
    }
    return res;
}


11. 盛最多水的容器

 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。

输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
int maxArea(int* height, int heightSize) {
    int mmax=0;
    int i=0,j=heightSize-1;
    while(i<j){
        int cur=fmin(height[i],height[j])*(j-i);
        if(cur>mmax) mmax=cur;
        if(height[i]<height[j]){
            i++;
        }else{
            j--;
        }
    }
    return mmax;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值