对撞指针法(《代码随想录》学习笔记)

一、原理

在处理一个两侧元素数值较大或较小的数组、寻找盛水最多的容器等情况下。可以使用两个指针(左指针left和右指针right)解决问题。其中左指针指向数组头元素,右指针指向数组末位元素,两个指针在一定条件下可以相向移动,最终遍历整个数组。由于仅进行一次遍历,该方法时间复杂度为O(n),优势明显。

二、限制条件

  • 操作的数据类型为数组
  • 其中的元素数值大小按照两侧高(低),中间低(高)的规律排布
  • 结果中要求找出两侧元素的柱状图所围成的“水槽容量”的极值

三、时空复杂度

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)或O(1)

四、常见题型

1.有序数组的平方

题目出处:977. 有序数组的平方 - 力扣(LeetCode)

解题思路:

由于原有序数组中可能存在负数,所以新数组不一定是有序数组。在将数组中所有元素平方(时间复杂度为O(n))以后,若直接使用库函数sort()(时间复杂度为O(nlogn))完成新数组的排序,将会增大算法的时间复杂度。对撞指针法解题思路如下:

  • 对于m,n<0,若m<n,则恒有m*m>n*n。求平方以后新数组中的元素两侧数值大,中间数值小。
  • 新建一个大小与原数组相同的数组用于存储答案
  • 设置两个指针分别指向原数组两端,不断循环比较左右指针所指元素的大小
  • 若左(右)指针元素的值较大,则将其元素按照从右往左的顺序存入新数组
  • 将左(右)指针向右(左)移动一位,直到两指针会合

代码实例:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int len = nums.size();
        int n = len - 1;           // n从右向左遍历新数组以便完成插入操作
        int left = 0;
        int right = len - 1;
        vector<int> res(n + 1, 0);

        // 求数组元素的平方数
        for(int i = 0; i < len; ++i) {
            nums[i] = nums[i]*nums[i];
        }

        while(n >= 0) {
            if(nums[left] >= nums[right]) {
                res[n--] = nums[left++];
            }else {
                res[n--] = nums[right--];
            }
        }
        return res;
    }
};

注意事项:

  • while()判断条件不可使用n > 0,否则将导致新数组首个元素未被处理
  • if(nums[left] >= nums[right]) 中需要使用大于等于号或小于等于号,保证当两指针指向的元素值相同或两指针会合时,仍可以返回正确元素值

2.盛最多水的容器

题目出处:11. 盛最多水的容器 - 力扣(LeetCode)

原题图
题目示例1的图像

解题思路:

本题如使用暴力求解则时间复杂度为O(n*n),要想将其优化为O(n),需要运用缩减搜索空间的思想。(下文以示例1为例进行分析)

  • 注意水体容量由较短的柱子和两元素相对位置共同决定。为了使相对位置最大,两指针从左右两端开始检查。也即分析最左边的 0 号和最右边的 7 号(此处记为(0,7))计算它们的容量。
  • 然后比较两根柱子的高度,从其中较短的一根(0号)入手寻找更优解,因为水体高度由较短的一根柱子决定,当前水体容量受到该柱子的高度限制。此时无论右侧柱子有多高,都不可能使面积增大,同时由于其他柱子与0号柱的相对位置较小,如果移动7号柱,水的面积必然缩小,由此我们可以排除(0,6)、(0,5)、(0,4)、(0,3)、(0,2)、(0,1)从而达到缩减搜索空间的目的。
  • 判断0号为限制因素后,记录下此时的水体面积,左指针右移一位。比较后发现height[1] > height[7]。7号位成为限制因素,面积有可能增大,记录此时面积并比较取较大值。再将右指针左移一位。此时我们又排除了(2,7)、(3,7)、(4,7)、(5,7)、(6,7)。
  • 重复该步骤,直到两指针相遇,即完成查找

代码实例:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int left = 0;
        int right = height.size() - 1;
        int res = 0;
        int temp = 0;
        while(right > left) {
            if(height[left] <= height[right]) {
                temp = (right - left)*height[left++];
                if(temp > res) res = temp;
            }else {
                temp = (right - left)*height[right--];
                if(temp > res) res = temp;
            }
        }
        return res;
    }
};

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值