LeetCode-双指针

1. 题号3:无重复字符的最长子串

滑动窗口,相当于以每个字符都作为起始位置找寻最长值,只需遍历一次
用哈希集合储存之前的字符串,无需重复遍历也可判断是否已经存在

public int lengthOfLongestSubstring(String s) {
        // 哈希集合,记录每个字符是否出现过
        Set<Character> occ = new HashSet<Character>();
        int n = s.length();
        // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
        int rk = -1, ans = 0;
        for (int i = 0; i < n; i++) {
            if (i != 0) {
                // 左指针向右移动一格,移除一个字符
                occ.remove(s.charAt(i - 1));
            }
            while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {
                // 符合条件时不断地移动右指针
                occ.add(s.charAt(rk + 1));
                ++rk;
            }
            // 第 i 到 rk 个字符是一个极长的无重复字符子串
            ans = Math.max(ans, rk - i + 1);
        }
        return ans;
    }

而上种方法 r初始值为-1,用 r+1判断,r正好指向右边界,此时需要加1

右指针初始值为0时,r指针实际指向右区间的下一个所以 r - l不用加1

int lengthOfLongestSubstring(string s) {
        unordered_set<char> occ;
        int length=0,r=0,n=s.size();
        for(int i=0;i<n;i++){
            if(i!=0){
                occ.erase(s[i-1]);
            }
            while(!occ.count(s[r]) && r<n){
                occ.insert(s[r]);
                r++;
            }
            if(length<(r-i+1)){
                length = r-i;
            }
        }
        return length;
    }

2. 题号11:盛水最多的容器

左指针从第一个元素开始,右指针从最后一个元素开始
移动二者之中元素较小的指针,因为储水量是由较小元素 t 决定,如果移动大指针储水量不会超过原有的tx
还是要深刻找寻规律才能找出最优解法

 int maxArea(vector<int>& height) {
        int l=0,r=height.size()-1,max=0,ans=0;
        while(l<r){
            if(height[l]<height[r]){
                ans=(r-l)*height[l];
            }else{
                ans=(r-l)*height[r];
            }
            if(ans>max){
                max=ans;
            }
            if(height[l]<=height[r]){
                l++;
            }else{
                r--;
            }
        }
        return max;
    }

3. 题号15:三数之和

本质双指针求两数之和,但有好多细节
先排序防止重复,进while循环 l=i+1,r=n-1,出while循环后判断nums[i-1]与nums[i]一样就跳过,注意的是当i>0时才可以
根据sum值和零的关系判断移动哪个指针
当前元素k大于零直接跳过,因为在之后i、j所指元素都大于当前元素
[0]和[0,0,0]这两个测试用例绝了
[0,0,0]决定了不能事先排除重复元素,要不然相当于数组里只有一个元素了,只能第一个重复元素进入while之后continue

 vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<vector<int>> ans;
        int n=nums.size();
        for(int i=0;i<n-2;i++){	   n-2原因当数组剩余元素小于3时不用看了
            if(nums[i]>0){
                break;
            }
            if(i>0&&nums[i]==nums[i-1]){  与上一个元素相同直接跳过
                continue;
            }
            int l=i+1,r=n-1;
            while(l<r){
                int sum = nums[i]+nums[l]+nums[r];
                if(sum<0){		这两个if可以合并到whilewhile(l<r&&nums[l]==nums[++l]); 相同元素就++
                }
                else if(sum>0){
                    while(l<r&&nums[r]==nums[--r]); 相同元素就--
                }
                else{
                    ans.push_back({nums[i],nums[l],nums[r]});
                    while(l < r && nums[l] == nums[++l]);
                    while(l < r && nums[r] == nums[--r]);
                }
            }
        }
        return ans;
    }
  1. 题号42:接雨水

在某个位置i处,它能存的水,取决于它左右两边的最大值中较小的一个减去自身高度,再将每个位置水相加就是答案

方法一:暴力遍历

找到每一个位置的左右两边最大高度
时间复杂度O(n2),空间复杂度O(1)

public int trap(int[] height) {
    int ans = 0;
    int size = height.length;
    for (int i = 1; i < size - 1; i++) {
        int max_left = 0, max_right = 0;
        for (int j = i; j >= 0; j--) { //Search the left part for max bar size
            max_left = Math.max(max_left, height[j]);
        }
        for (int j = i; j < size; j++) { //Search the right part for max bar size
            max_right = Math.max(max_right, height[j]);
        }
        ans += Math.min(max_left, max_right) - height[i];
    }
    return ans;
}

方法二:动态规划

运用两次单层循环记录每个位置左右大小值,降低了时间复杂度
时间复杂度O(n),空间复杂度O(n)

public int trap(int[] height) {
    if (height == null || height.length == 0)
        return 0;
    int ans = 0;
    int size = height.length;
    int[] left_max = new int[size];
    int[] right_max = new int[size];
    left_max[0] = height[0];
    for (int i = 1; i < size; i++) {
        left_max[i] = Math.max(height[i], left_max[i - 1]);
    }
    right_max[size - 1] = height[size - 1];
    for (int i = size - 2; i >= 0; i--) {
        right_max[i] = Math.max(height[i], right_max[i + 1]);
    }
    for (int i = 1; i < size - 1; i++) {
        ans += Math.min(left_max[i], right_max[i]) - height[i];
    }
    return ans;
}

方法三:双指针遍历

当我们从左往右处理到left下标时,左边的最大值left_max对它而言是可信的,但right_max对它而言是不可信的。
当我们从右往左处理到right下标时,右边的最大值right_max对它而言是可信的,但left_max对它而言是不可信的。
左边0到left是小于右边最大值的

public int trap(int[] height) {
    int left = 0, right = height.length - 1;
    int ans = 0;
    int left_max = 0, right_max = 0;
    while (left < right) {
        if (height[left] < height[right]) {
            if (height[left] >= left_max) {
                left_max = height[left];
            } else {
                ans += (left_max - height[left]);
            }
            ++left;
        } else {
            if (height[right] >= right_max) {
                right_max = height[right];
            } else {
                ans += (right_max - height[right]);
            }
            --right;
        }
    }
    return ans;
}
  1. 题号209:长度最小的子数组

从头开始遍历,sum+=尾指针元素。sum>目标值,首指针++,用while的原因是加上尾指针后,前面可能少两个数的情况下也大于目标值,只要大于sum就滑动
有些时候if,else可以被一个if或者while代替
要思考什么时候滑动窗口,本题为sum>s时滑动窗口
时间n,空间1

int minSubArrayLen(int s, vector<int>& nums) {
        int sum=0,mlength=INT_MAX,n=nums.size(),l=0;
        for(int i=0;i<n;i++){
            sum+=nums[i];
            while(sum>=s){
                if((i-l+1)<mlength){
                    mlength=(i-l+1);
                }
                sum-=nums[l++];
            }
        }
        return mlength==INT_MAX? 0:mlength;
    }

6.题号88:合并两个有序数组

用从后向前插入可以节省复制的空间
尤其要注意数组m–=-1的这种情况

void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
		int i = n + m - 1;
		m--;
		n--;
		while (n >= 0 && m>=0) {	防止n或m=-1的情况
			把两者大的数放到后面
			if (nums1[m] > nums2[n]) {
				swap(nums1[m--], nums1[i--]);
			}
			else {
				swap(nums2[n--], nums1[i--]);
			}
		}
        while(n>=0){	把剩余nums2插入
            nums1[i--]=nums2[n--];
        }
	}

7.剑指 Offer 04: 二维数组中的查找

找好分界线,以右上角为分界线,当前值大于taget就向下找(比他大的值都在下面),当前值小于target就向左找(比他小的值都在左边)
要注意空数组的情况
判空时matrix.size()==0要当在前面,不然 [] 时会matrix[0]超索引报错

  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
  bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {   
        if(matrix.size()==0||matrix[0].size()==0){ 
            return false;
        }
        int row = matrix.size(),columns=matrix[0].size();
        int trow=0,tcolumns=columns-1;
        while(trow<row && tcolumns>=0){
            if(matrix[trow][tcolumns]==target){
                return true;
            }else if(matrix[trow][tcolumns]<target){
                trow++;
            }else{
                tcolumns--;
            }
        }
        return false;
    }

8. 题号925:长按键入

双指针分别指向name和typed
当两个字符一致时都加加,不一致时判断是否与name上一个字符相同(是否长按键入),相同只t++,不同直接返回false
最后可能typed结束了,如“asdfasd”和“asdf”,前半部分一致但name没有遍历完,所以return n==name.size(),判断name是否完全遍历了
要注意数组越界问题,可以把n>0放在if前面

    bool isLongPressedName(string name, string typed) {
        int n=0,t=0,namel=name.size(),typel=typed.size();
        while(t<typel){
            if(n<name.size() && name[n]==typed[t]){
                n++;
                t++;
            }
            else if(n>0 && typed[t] == name[n-1]){
                t++;
            }
            else{
                return false;
            }
        }
        return n==name.size();
    }

9.题号424: 替换后的最长重复字符

滑动窗口,替换后也不符合要求就滑动
需要被替换的字符(窗口长度-窗口内最多字符的数量)<=k,满足条件

int characterReplacement(string s, int k) {
     int l=0,cmax=INT_MIN,result=0;
     vector<int> arr(26);
     for(int i=0;i<s.size();i++){
         arr[s[i]-'A']++;
         cmax=max(cmax,arr[s[i]-'A']);
         if(i-l+1-cmax>k){	  替换后也不符合要求
             arr[s[l]-'A']--;
             l++;
         }else{	本题else不写也可以,都无法超过当时的最大值
             result=max(result,i-l+1);
         }
     }
     return result;
 }

10.题号1004. 最大连续1的个数 III

与上题类似
本题 r-l+1-count可换为0的数量(需要被替换的数)小于K

int longestOnes(vector<int>& A, int K) {
        int count=0,mlength=0,l=0;
        for(int r=0;r<A.size();r++){
            if(A[r]==0){
                count++;
            }
            if(count>K){
                if(A[l]==0){
                    count--;
                }	这个要放在l++的前面,要不然就不是原来的值了
                l++;
            }
            mlength=max(mlength,r-l+1);
        }
        return mlength;
    }

11.题号28. 实现 strStr()

只有当首字母相同时才开始比较,不同时跳出循环,判断j-i与目标字符串长度是否一致

int strStr(string haystack, string needle) {
        int h=haystack.size(),n=needle.size();
        if(n==NULL){
            return 0;
        }
        for(int i=0;i<=h-n;i++){	剩余长度小于目标字符串不用继续遍历
            if(haystack[i]==needle[0]){
                int j=i+1,k=1;
                while(j<h && haystack[j]==needle[k]){
                    j++;
                    k++;
                }
                if((j-i)==n){
                    return i;
                }
            }
        }
        return -1;
    }

12.题号202. 快乐数

不快乐数会进循环,快慢指针破循环
用dowhile可以不用赋初值,当两数相等或快指针等于1了,出循环
返回快指针是否等于1(确认是因为=1出循环,而不是两指针相遇了)

int bitSquareSum(int n) {
        int sum = 0;
        while(n)
        {
            int bit = n % 10;
            sum += bit * bit;
            n = n / 10;
        }
        return sum;
    }
    
    bool isHappy(int n) {
        int slow = n, fast = n;
        do{
            slow = bitSquareSum(slow);
            fast = bitSquareSum(fast);
            fast = bitSquareSum(fast);
        }while(slow != fast && fast!=1);
        return fast == 1;
    }

13.题号26. 删除排序数组中的重复项

不满足条件left就不动,对的才赋值,这样left之前都是满足条件的数
要注意right、left开始时要指向同一元素

int removeDuplicates(vector<int>& nums) {
		if (nums.size() == 0) {
			return 0;
		}
		int left = 0, right = 1;
		for (int right = 0; right < nums.size();right++) {
			不满足条件不动
			if (nums[left]!=nums[right]) {	满足条件赋值给left
				nums[++left] = nums[right];
			}
		}
		return left+1;
	}

14.题号80. 删除排序数组中的重复项 II

与上题一致,不过条件变化了

int removeDuplicates(vector<int>& nums) {
        int j=1,n=nums.size(),count=1;
        for(int i=1;i<n;i++){
            if(nums[i]==nums[i-1]){
                count++;
            }else{
                count=1;
            }
            if(count<=2){	满足条件了,赋值并left右移
                nums[j++]=nums[i];
            }
        }
        return j;
    }

15.题号283. 移动零

太棒了! 把0都放在后面就是把非零数都放到前面
实现方法也与上题类似,满足条件就赋值

void moveZeroes(vector<int>& nums) {
        int n=nums.size(),left=0;
        for(int j=0;j<n;j++){
            if(nums[j]!=0){	满足条件了,赋值并left右移
                swap(nums[j],nums[left++]);
            }
        }
    }

收获与体会

  1. 注意数组索引 - - 之后等于-1的情况
  2. 判空尽可能放在程序最前面
  3. 滑动窗口题,主要思考什么时候滑动窗口(一般是满足条件,扩充后又不满足时),左指针滑动到再次满足条件时
  4. 快慢指针破循环
  5. 把0都放在后面,就是把非零数都放在前面
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值