leetcode_刷题总结(c++)_双指针_左右指针

主要参考:

文章目录

  • 左右指针
  • leetcode部分题目
    • (一)从两端向中心靠近
      • 647. 回文子串
      • 31. 下一个排列
      • 42.接雨水
    • (二)从中心向两端扩散
      • 5. 最长回文子串

左右指针

(一)从两端向中心靠近
又称首尾指针

(二)从中心向两端扩散
中心扩散法

leetcode部分题目

(一)从两端向中心靠近

647. 回文子串

647. 回文子串
leetcode_刷题总结(c++)_双指针_左右指针_第1张图片

class Solution {
public:
    bool isGood(const string &str) {
        int left = 0, right = str.size() - 1;
        while (left < right) {
            if (str[left] != str[right])return false;
            //双指针向中间缩
            left++;
            right--;//这里容易写成++,记住是往中间缩!!!
        }
        return true;
    }
    int countSubstrings(string s) {
        int sum = 0;
        for (int i = 0; i < s.size(); ++i) {
            for (int j = i; j < s.size(); ++j) {
                //遍历所有子字符串,然后判断是否回文
                if (isGood(s.substr(i, j - i + 1)))sum++;
            }
        }
        return sum;
    }

dp做法:请移步我动态规划的博客

31. 下一个排列

31. 下一个排列
leetcode_刷题总结(c++)_双指针_左右指针_第2张图片
思路:
参考官方题解
注意到下一个排列总是比当前排列要大,除非该排列已经是最大的排列。我们希望找到一种方法,能够找到一个大于当前序列的新序列,且变大的幅度尽可能小。
(1)将后面大的数和前面小的数交换(这样可以保证 从后往前,升序对)
(2)希望下一个排序增加的幅度尽可能小

  • 尽可能靠右交换,因此从后往前查找
  • 尽可能小的 大数 和 小数 交换
  • 大数交换 后,后面排序升序排列
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int i = nums.size() - 2;
        //从后往前找升序对
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }
        //找到升序对
        if (i >= 0) {
            int j = nums.size() - 1;
            //从后往前找第一个大于[小数]的[大数]
            while (j >= 0 && nums[i] >= nums[j]) {
                j--;
            }
            //交换小数与大数的位置
            swap(nums[i], nums[j]);
        }
        //未找到升序对 则i=0 说明数组为逆序的 故应该输出新的正序数组
        //若找到升序对 从[i+1,n]进行升序排序
        // reverse(nums.begin() + i + 1, nums.end());
        sort(nums.begin() + i + 1, nums.end());
    } 
    
};

42.接雨水

42.接雨水
leetcode_刷题总结(c++)_双指针_左右指针_第3张图片

class Solution {
public:
    int trap(vector<int>& height) {
        int left = 0, right = height.size() - 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;
    }
};

(二)从中心向两端扩散

5. 最长回文子串

5. 最长回文子串
leetcode_刷题总结(c++)_双指针_左右指针_第4张图片
DP做法:移步leetcode_刷题总结(c++)_动态规划

中心扩散做法参考:
动态规划、中心扩散、Manacher 算法
【一题六解】从暴力、区间dp到字符串哈希,掌握解决该类问题通法
leetcode_刷题总结(c++)_双指针_左右指针_第5张图片
leetcode_刷题总结(c++)_双指针_左右指针_第6张图片

注意点:
(1) left 和 right 从中间向两侧扩散,直到 left 和 right 指向的字符不相等的时候停下来。所以「回文」的开始位置是 left + 1,结束位置是 right - 1。

回文长度 = 结束位置 - 起始位置 + 1
所以 right - 1 - (left + 1) + 1 = right - left - 1

(2) start = i - (len - 1) / 2 ,end = i + len / 2
这个是因为要考虑len为偶数的情况,如果不减1的话,扩散出来的长度会比需要的长度大1。
i = 5,len = 6,如果不减1的话扩散得到的start为2,end为8,此时的区间长度是8 - 2 + 1 = 7,并不是我们预想的6

(3)s.substr() 与 s…substring()

1.首先针对只有一个参数的情况:
s.substr(start) 和 s.substring(start) 均表示从start位置开始到结尾的子字符串。
2、对于有两个参数的情况:
二者就存在区别:
C++: substr(start, len), 第一个参数的起始位置,第二个参数是子串长度
Java: substring(start, end),第一个参数是起始位置,第二个参数是结束位置 左闭右开[start, end)

class Solution {
public:
    string longestPalindrome(string s) {
		if (s.length() < 1) {
			return "";
		}
		int start = 0, end = 0;
		for (int i = 0; i < s.length(); i++) {
            //分奇数和偶数进行扩散
            //奇数中点的情况,一个元素为中心,尝试往两端最多扩散各 i 个长度
			int len1 = expandAroundCenter(s, i, i);
            //偶数中点的情况,两个元素为中心,尝试往左边最多扩散i个长度,右边扩散i + 1个长度
			int len2 = expandAroundCenter(s, i, i + 1);
			int len = max(len1, len2);
            //更新最优解
			if (len > end - start) {
				start = i - (len - 1) / 2;
				end = i + len / 2;
			}
		}
		return s.substr(start, end - start + 1);
	}

	int expandAroundCenter(string s, int left, int right)
	{
		int L = left, R = right;
        // 计算以left和right为中心
		while (L >= 0 && R < s.length() && s[L] == s[R]) {的回文串长度
			L--;
			R++;
		}
		return R - L - 1;
	}
};

马拉车算法的思想是在中心扩散的基础上减少重复判断——记录最新且边界最右回文子串的右边界和中心,那么根据回文的对称性,在该子串中心以右但不超出右边界的待验证子串可以用该子串中心以左的已验证子串来验证,超出右边界的待验证子串则进行朴素匹配。
(待续)

你可能感兴趣的:(leetcode,leetcode,c++,算法)