双指针

131 阅读3分钟

题目描述

这是 LeetCode 上的「3. 无重复字符的最长子串」,难度为 「Medium」

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

示例 1:

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

示例 2:

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

示例 3:

输入: s = "pwwkew" 
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

示例 4:

输入: s = "" 
输出: 0

提示:

  • 0 <= s.length <= 5 *
  • s 由英文字母、数字、符号和空格组成

双指针 + 哈希表解法(滑动窗口)

定义两个指针 ij,表示当前处理到的子串是 [i,j][i,j] 始终满足无重复字符。

从前往后进行扫描,同时维护一个哈希表记录 [i,j] 中每个字符出现的次数。

遍历过程中,j 不断自增,同时将第 j 个字符在哈希表中出现的次数加一。

当满足 map.get(r) > 1 代表此前出现过第 j 位对应的字符。此时应该更新 i 的位置(使其右移),直到不满足 map.get(r) > 1 ,代表 [i,j] 恢复满足无重复字符的条件。

同时使用 [i,j] 长度更新最大长度。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> map = new HashMap<>();
        int ans = 0;
        for (int i = 0, j = 0; j < s.length(); j++) {
            char r = s.charAt(j);
            map.put(r, map.getOrDefault(r, 0) + 1);
            while (map.get(r) > 1) {
                char l = s.charAt(i);
                map.put(l, map.get(l) - 1);
                i++;
            }
            ans = Math.max(ans, j - i + 1);
        }
        return ans;
    }
}

11. 盛最多水的容器

给你 n 个非负整数 a1,a2,...,a``n,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明: 你不能倾斜容器。

 

示例 1:

输入: [1,8,6,2,5,4,8,3,7]
输出: 49 
解释: 图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49

示例 2:

输入: height = [1,1]
输出: 1

示例 3:

输入: height = [4,3,2,1,4]
输出: 16

示例 4:

输入: height = [1,2,1]
输出: 2

 

提示:

  • n == height.length

  • 2 <= n <= 105

  • 0 <= height[i] <= 104

双指针 + 贪心解法

先用两个指针 i 和 j 指向左右边界,然后考虑指针应该怎么移动。

由于构成矩形的面积,取决于 i 和 j 之间的距离(记为 w) 和 i 和 j 下标对应的高度的最小值(记为 h)。

首先无论是 i 指针往右移动还是 j 指针往左移动都会导致 w 变小,所以想要能够枚举到更大的面积,我们应该让 h 在指针移动后变大。

不妨假设当前情况是 height[i] < heigth[j](此时矩形的高度为 height[i]),然后分情况讨论:

  • 让 i 和 j 两者高度小的指针移动,即 i 往右移动:
    • 移动后,i 指针对应的高度变小,即 height[i] > height[i + 1]w 和 h都变小了,面积一定变小
    • 移动后,i 指针对应的高度不变,即 height[i] = height[i + 1]w 变小,h不变,面积一定变小
    • 移动后,i 指针对应的高度变大,即 height[i] < height[i + 1]w 变小,h变大,面积可能会变大
  • 让 i 和 j 两者高度大的指针移动,即 j 往左移动:
    • 移动后,j 指针对应的高度变小,即 height[j] > height[j - 1]w 变小,h可能不变或者变小(当 height[j - 1] >= height[i] 时,h 不变;当 height[j - 1] < height[i] 时,h 变小),面积一定变小
    • 移动后,j 指针对应的高度不变,即 height[j] = height[j - 1]w 变小,h不变,面积一定变小
    • 移动后,j 指针对应的高度变大,即 height[j] < height[j - 1]w 变小,h不变,面积一定变小

综上所述,我们只有将高度小的指针往内移动,才会枚举到更大的面积:

class Solution {
    public int maxArea(int[] height) {
        int n = height.length;
        int i = 0, j = n - 1;
        int ans = 0;
        while (i < j) {
            ans = Math.max(ans, (j - i) * Math.min(height[i], height[j]));
            if (height[i] < height[j]) {
                i++;
            } else {
                j--;
            }
        }
        return ans;
    }
}

1438. 绝对差不超过限制的最长连续子数组

给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit

如果不存在满足条件的子数组,则返回 0 。

 

示例 1:

输入: nums = [8,2,4,7], limit = 4
输出: 2 
解释: 所有子数组如下:
[8] 最大绝对差 |8-8| = 0 <= 4.
[8,2] 最大绝对差 |8-2| = 6 > 4. 
[8,2,4] 最大绝对差 |8-2| = 6 > 4.
[8,2,4,7] 最大绝对差 |8-2| = 6 > 4.
[2] 最大绝对差 |2-2| = 0 <= 4.
[2,4] 最大绝对差 |2-4| = 2 <= 4.
[2,4,7] 最大绝对差 |2-7| = 5 > 4.
[4] 最大绝对差 |4-4| = 0 <= 4.
[4,7] 最大绝对差 |4-7| = 3 <= 4.
[7] 最大绝对差 |7-7| = 0 <= 4. 
因此,满足题意的最长子数组的长度为 2 。

示例 2:

输入: nums = [10,1,2,4,7,2], limit = 5
输出: 4 
解释: 满足题意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5 <= 5 。

示例 3:

输入: nums = [4,2,2,2,4,4,2,2], limit = 0
输出: 3

 

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • 0 <= limit <= 10^9

双指针 解法

上述解法我们是在对 len 进行二分,而事实上我们可以直接使用「双指针」解法找到最大值。

始终让右端点 r 右移,当不满足条件时让 l 进行右移。

同时,还是使用「单调队列」保存我们的区间最值,这样我们只需要对数组进行一次扫描即可得到答案。

class Solution {
    public int longestSubarray(int[] nums, int limit) {
        int n = nums.length;
        int ans = 0;
        Deque<Integer> max = new ArrayDeque<>(), min = new ArrayDeque<>();
        for (int r = 0, l = 0; r < n; r++) {
            while (!max.isEmpty() && nums[r] >= nums[max.peekLast()]) {
                max.pollLast();
            }
            max.addLast(r);

            while (!min.isEmpty() && nums[r] <= nums[min.peekLast()]) {
                min.pollLast();
            }
            min.addLast(r);

            while (Math.abs(nums[max.peekFirst()] - nums[min.peekFirst()]) > limit) {
                l++;
                if (max.peekFirst() < l) max.pollFirst();
                if (min.peekFirst() < l) min.pollFirst();
            }
            ans = Math.max(ans, r - l + 1);
        }
        return ans;
    }
}

15. 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c , 使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

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

 

示例 1:

输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]

示例 2:

输入: nums = []
输出: []

示例 3:

输入: nums = [0]
输出: []

 

提示:

  • 0 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

排序+双指针解法

对数组进行排序,使用三个指针 ij 和 k 分别代表要找的三个数。

  1. 通过枚举 i 确定第一个数,另外两个指针 jk 分别从左边 i + 1 和右边 n - 1 往中间移动,找到满足 nums[i] + nums[j] + nums[k] == 0 的所有组合。
  2. j 和 k 指针的移动逻辑,分情况讨论 sum = nums[i] + nums[j] + nums[k] :
    • sum > 0:k 左移,使 sum 变小
    • sum < 0:j 右移,使 sum 变大
    • sum = 0:找到符合要求的答案,存起来

由于题目要求答案不能包含重复的三元组,所以在确定第一个数和第二个数的时候,要跳过数值一样的下标(在三数之和确定的情况下,确保第一个数和第二个数不会重复,即可保证三元组不重复)。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        List<List<Integer>> ans = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            for (int j = i + 1, k = n - 1; j < k; j++) {
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                while (k - 1 > j && nums[i] + nums[j] + nums[k - 1] >= 0) k--;
                if (nums[i] + nums[j] + nums[k] == 0) {
                    ans.add(Arrays.asList(nums[i], nums[j], nums[k]));
                }
            }
        }
        return ans;
    }
}

16. 最接近的三数之和

给你一个长度为 n 的整数数组 nums **和 一个目标值 target。请你从 nums **中选出三个整数,使它们的和与 target 最接近。

返回这三个数的和。

假定每组输入只存在恰好一个解。

 

示例 1:

输入: nums = [-1,2,1,-4], target = 1
输出: 2
解释: 与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

示例 2:

输入: nums = [0,0,0], target = 1
输出: 0

 

提示:

  • 3 <= nums.length <= 1000
  • -1000 <= nums[i] <= 1000
  • -104 <= target <= 104

排序 + 双指针解法

这道题的思路和 15. 三数之和 区别不大。

对数组进行排序,使用三个指针 ij 和 k 分别代表要找的三个数。

  1. 通过枚举 i 确定第一个数,另外两个指针 jk 分别从左边 i + 1 和右边 n - 1 往中间移动,找到满足 nums[i] + nums[j] + nums[k] 最接近 target的唯一解。
  2. j 和 k 指针的移动逻辑,分情况讨论 sum = nums[i] + nums[j] + nums[k] :
    • sum > targetk 左移,使 sum 变小
    • sum < targetj 右移,使 sum 变大
    • sum = target:找到最符合要求的答案,直接返回

为了更快找到答案,对于相同的 i,可以直接跳过下标。

class Solution {
    public int threeSumClosest(int[] nums, int t) {
        Arrays.sort(nums);
        int ans = nums[0] + nums[1] + nums[2];
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            int j = i + 1, k = n - 1;
            while (j < k) {
                int sum = nums[i] + nums[j] + nums[k];
                if (Math.abs(sum - t) < Math.abs(ans - t)) ans = sum;
                if (ans == t) {
                    return t;
                } else if (sum > t) {
                    k--;
                } else if (sum < t) {
                    j++;
                }
            }
        }
        return ans;
    }
}

18. 四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abc 和 d 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

 

示例 1:

输入: nums = [1,0,-1,0,-2,2], target = 0
输出: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:

输入: nums = [2,2,2,2,2], target = 8
输出: [[2,2,2,2]]

 

提示:

  • 1 <= nums.length <= 200
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109

排序 + 双指针解法

这道题的思路和 15. 三数之和(中等)16. 最接近的三数之和(中等)类似。

对数组进行排序,使用四个指针 ij 、k 和 p 分别代表要找的四个数。

  1. 通过枚举 i 确定第一个数,枚举 j 确定第二个数,另外两个指针 k 和 p 分别从左边 j + 1 和右边 n - 1 往中间移动,找到满足 nums[i] + nums[j] + nums[k] + nums[p] == t 的所有组合。
  2. k 和 p 指针的移动逻辑,分情况讨论 sum = nums[i] + nums[j] + nums[k] + nums[p] :
    • sum > targetp 左移,使 sum 变小
    • sum < targetk 右移,使 sum 变大
    • sum = target:将组合加入结果集,k 右移继续进行检查

题目要求不能包含重复元素,所以我们要对 ij 和 k 进行去重,去重逻辑是对于相同的数,只使用第一个。

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int t) {
        Arrays.sort(nums);
        int n = nums.length;
        List<List<Integer>> ans = new ArrayList<>();
        for (int i = 0; i < n; i++) { // 确定第一个数
            if (i > 0 && nums[i] == nums[i - 1]) continue; // 对第一个数进行去重(相同的数只取第一个)
            for (int j = i + 1; j < n; j++) { // 确定第二个数
                if (j > i + 1 && nums[j] == nums[j - 1]) continue; // 对第二个数进行去重(相同的数只取第一个)
                // 确定第三个数和第四个数
                int k = j + 1, p = n - 1;
                while (k < p) {
                
                    // 对第三个数进行去重(相同的数只取第一个)
                    while (k > j + 1 && k < n && nums[k] == nums[k - 1]) k++
                    // 如果 k 跳过相同元素之后的位置超过了 p,本次循环结束
                    if (k >= p) break;
                    
                    int sum = nums[i] + nums[j] + nums[k] + nums[p];
                    if (sum == t) {
                        ans.add(Arrays.asList(nums[i], nums[j], nums[k], nums[p]));
                        k++;
                    } else if (sum > t) {
                        p--;
                    } else if (sum < t) {
                        k++;
                    }
                }
            }
        }
        return ans;
    }
}

转载自:宫水三叶的刷题日记