【LeetCode刷题记录】 热题 100 —— 双指针

相关博客

283. 移动零

题目链接

看到题目最简单的想法应该是按顺序找出全部的非零元素,然后填充在数组的前面,最后剩下的全部赋值0。想到这种解法的原因是27. 移除元素,有异曲同工之妙。

    public void moveZeroes(int[] nums) {
        int index = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                nums[index] = nums[i];
                index++;
            }
        }
        while (index < nums.length) {
            nums[index] = 0;
            index++;
        }
    }

提交当然也是成功的,但是翻了一下题解之后,发现一个很有意思的解法叫滚雪球解法。

    public void moveZeroes(int[] nums) {
        int snowBall = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == 0) {
                snowBall++;
            } else if (snowBall > 0) {
                nums[i - snowBall] = nums[i];
                nums[i] = 0;
            }
        }
    }

把 0 想象成一个雪球,0 和 0 相遇的时候就会越滚越大,0 聚在一起会形成一个数组,而这个 snowBall 就是这个雪球数组的长度,i 是当前指针所在的地方。每次交换雪球中第一个0和雪球后面第一个非零元素,让雪球一直往后滚动,最后所有的0就会滚动到数组最后面。

11. 盛最多水的容器

题目链接

在这里插入图片描述

这一题求两个柱之间的最大容量,看图很容易理解,就是短一点的柱子高度乘上两个柱子之间的距离。相当于是求两个数的最大乘积,暴力解法也就是双重for循环,但是 O(n²) 的时间复杂度很容易就超时了。

    public int maxArea(int[] height) {
        int length = height.length;
        int result = 0;
        for (int i = 0; i < length; i++) {
            int tempArea = 0;
            int lineHeight = height[i];
            for (int j = i + 1; j < length; j++) {
                tempArea = Math.min(lineHeight, height[j]) * (j - i);
                result = Math.max(result, tempArea);
            }
        }
        return result;
    }

所以需要对解法进行优化,我们并不需要把每个柱子有可能的容器全求一遍,参考下面的图片,有两种移动方案

  • 移动短边,面积变化不定,可大可小可不变
  • 移动长边,面积一定变小

在这里插入图片描述

有了这两种移动方案的结果,我们只需要维护两个指针,短的指针向内移动,长指针不动,就可以写出时间复杂度为 O(n)的解法

    public int maxArea(int[] height) {
        int left = 0;
        int right = height.length - 1;
        int result = 0;

        while (left < right) {
            result = Math.max(result, Math.min(height[left], height[right]) * (right - left));
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return result;
    }

可以简化一下代码

    public int maxArea(int[] height) {
        int i = 0, j = height.length - 1, res = 0;
        while(i < j) {
            res = height[i] < height[j] ? 
                Math.max(res, (j - i) * height[i++]): 
                Math.max(res, (j - i) * height[j--]); 
        }
        return res;
    }

15. 三数之和

题目链接

题目需要找到三个数的组合,让他们的和为 0,最简单的解法就是三重for循环,但是这个解法时间复杂度太高了,不用写都知道会超时。如果我们先固定住一个指针,剩下两个指针就可以用上一题的思路进行移动,这样就是一个O(n²)的时间复杂度。

其中,题目里最关键的一个点就是结果集不能重复,所以我们需要进行去重。但是数组是无序的,这就让去重的难度变高了,所以可以先对题目数组进行一个排序,这样也给双指针移动的规则简单了许多。

	public List<List<Integer>> threeSum(int[] nums) {
    	// 结果集
        List<List<Integer>> result = new ArrayList<>();
        // 排序
        Arrays.sort(nums);

        // 如果排序后 第一个元素大于0,或者最后一个元素小于0,那么就不用继续执行了
        if (nums[0] > 0 || nums[nums.length - 1] < 0) {
            return result;
        }

        // 进入 for 循环
        for (int i = 0; i < nums.length; i++) {
            // 左指针从 i 后一个元素开始,右指针永远是最后一个元素
            int left = i + 1;
            int right = nums.length - 1;
            // 如果 当前元素 和 上一个元素 相等,去重
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            // 定位剩下两个指针
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                // 由于现在数组有序,所以求和大于 0 就把右指针变小;求和小于 0 就把左指针变大
                if (sum > 0) {
                    right--;
                } else if (sum < 0) {
                    left++;
                } else {
                    // 如果找到了符合条件的结果,加入结果集
                    result.add(List.of(nums[i], nums[left], nums[right]));
                    // 进行去重,移动右指针,跳过重复元素
                    while (right > left && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    // 同样进行去重
                    while (right > left && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    // 去重结束,正常移动指针
                    left++;
                    right--;
                }
            }
        }
        return result;
    }

42. 接雨水

题目链接

这是一道困难题,看到题目这张图片,和上一题乍一看挺像的,所以实际思路也差不多。

这一题并不是求最大的储水量,而是需要求这些柱子中间能存多少水,能储水的格子就是有左右两边都有高度差的位置,具体储水多少就是看左右两边谁更低一些,有了上一题的铺垫,理解题目应该会容易许多。

这一题有很多解法,目前就仿照上一题使用双指针的思路来解题,参考下面官方题解中提供的图解。

  • 首先需要两个左右指针。两个指针慢慢向中间移动,当指针相遇的时候就结束循环。
  • 那么怎么计算某一个位置的储水量呢?
    • 首先这个位置的左右两边都要有一个比这个位置更高的柱子
    • 储水量需要根据两边更高柱子中,矮一些的柱子高度减去当前指针的高度
  • 如何移动指针呢?
    • 两个指针分别指向头和尾,向中间移动
    • 和上一题类似,优先移动短边,因为移动短边有概率让储水量变大

在这里插入图片描述

    public int trap(int[] height) {
        int result = 0;
        int left = 0, right = height.length - 1;
        int leftHeight = 0, rightHeight = 0;
        while (left < right) {
            // 更新左右两边的最高高度
            if (height[left] > leftHeight) {
                leftHeight = height[left];
            }
            if (height[right] > rightHeight) {
                rightHeight = height[right];
            }
            // 统计结果
            result += (leftHeight - height[left]) + (rightHeight - height[right]);
            // 移动指针
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return result;
    }
  • 16
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值