【91 算法-基础篇】05.双指针

例题一:11. 盛水最多的容器

在这里插入图片描述

思路

我们来换个角度来思考这个问题,上述的解法是通过两两组合,这无疑是完备的。我们换个角度思考,是否可以先计算长度为 n 的面积,然后计算长度为 n-1 的面积,… 计算长度为 1 的面积。 这样去不断更新最大值呢?很显然这种解法也是完备的,但是似乎时间复杂度还是 O ( n 2 ) O(n ^ 2) O(n2), 不要着急。

考虑一下,如果我们计算 n-1 长度的面积的时候,是可以直接排除一半的结果的。比如我们计算 n 面积的时候,假如左侧的线段高度比右侧的高度低,那么我们通过左移右指针来将长度缩短为 n-1 的做法是没有意义的,因为新的形成的面积变成了(n-1) * heightOfLeft 这个面积一定比刚才的长度为 n 的面积 (n * heightOfLeft) 小。也就是说最大面积一定是当前的面积或者通过移动短的端点得到。

代码

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

复杂度

  • 时间复杂度:由于左右指针移动的次数加起来正好是 n, 因此时间复杂度为 O ( N ) O(N) O(N)
  • 空间复杂度: O ( 1 ) O(1) O(1)

例题二:875. 爱吃香蕉的珂珂

在这里插入图片描述

思路

采用二分法,由题可知, 1 < = K < = 1 , 000 , 000 , 000 1<=K<=1,000,000,000 1<=K<=1,000,000,000,此题为寻找最小的满足要求的数。在计算吃香蕉的总时间时,有一个小技巧: t i m e + = ( p i l e s [ i ] − 1 ) / K + 1 time += (piles[i] - 1) / K + 1 time+=(piles[i]1)/K+1

代码

class Solution {
    public int minEatingSpeed(int[] piles, int H) {
            int low = 1;
            int high = 1_000_000_000;
            while(low < high){
                    int middle = (high + low) / 2;
                    if(possible(piles, H, middle)){
                            high = middle;
                    }else{
                            low = middle + 1;
                    }
            }
            return low;
    }
        
        public boolean possible(int[] piles, int H, int K){
                int time = 0;
                for(int each : piles)
                        time += (each - 1) / K + 1;
                return time <= H;
        }
}

复杂度

  • 时间复杂度 O ( N l o g ( W ) ) O(Nlog(W)) O(Nlog(W))
  • 空间复杂度 O ( 1 ) O(1) O(1)

N为香蕉的堆数, W为最大堆的橡胶数

例题三:26. 删除排序数组中的重复项

在这里插入图片描述

思路

快慢指针

代码

class Solution {
    public int removeDuplicates(int[] nums) {
            if(nums.length == 0) return 0;
        int i = 0, j = 1;
            while(j < nums.length){
                    if(nums[j] == nums[i])
                            j++;
                    else
                            nums[++i] = nums[j++];
            }
            return i+1;
    }
}

复杂度

  • 时间复杂度 O ( N ) O(N) O(N)
  • 空间复杂度 O ( 1 ) O(1) O(1)

N为数组的长度

例题四:167. 两数之和 II - 输入有序数组

思路

如果是无序的,可以用哈希表。如果是有序,也可以用双指针,从两边向中间靠拢。

代码

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int i = 0, j = numbers.length-1;
            while(i < j){
                    int sum = numbers[i] + numbers[j];
                    if(sum > target)
                            j--;
                    else if(sum < target)
                            i++;
                    else
                            return new int[]{i+1, j+1};
            }
            return new int[]{0,0};
    }
}

复杂度

  • 时间复杂度 O ( N ) O(N) O(N)
  • 空间复杂度 O ( 1 ) O(1) O(1)

N为数组长度

例题五:42. 接雨水

在这里插入图片描述

方法一:暴力解法

思路

针对数组中的每一个元素,求它所能接住的雨水量,具体方法是分别求它左右两边的最大高度(将它自己也考虑进去),其中的较小值与当前的高度的差值就是它所能接住的雨水量。

代码

class Solution {
    public int trap(int[] height) {
            int sum = 0;
        for(int i = 0; i < height.length; i++){
                int maxLeft = height[i], maxRight = height[i];
                for(int j = i; j >=0; j--){
                        if(height[j]>maxLeft)
                                maxLeft = height[j];
                }
                for(int j = i; j <height.length; j++){
                        if(height[j]>maxRight)
                                maxRight = height[j];
                }
                sum += Math.min(maxLeft, maxRight) - height[i];
        }
            return sum;
    }
}

复杂度

  • 时间复杂度 O ( N 2 ) O(N^2) O(N2)
  • 空间复杂度 O ( 1 ) O(1) O(1)

方法二:动态编程

思路

暴力解法中,针对每一个位置,都要求它左边的最大高度和右边的最大高度。实际上,我们可以事先将这些数据都计算并储存起来,需要用的时候就可以直接用了,这种方法叫做动态编程。具体做法是维护两个数组。

代码

class Solution {
    public int trap(int[] height) {
            if(height.length == 0) return 0;
            int sum = 0;
            int[] leftMax = new int[height.length];
            int[] rightMax = new int[height.length];
            leftMax[0] = height[0];
            for(int i = 1; i < height.length; i++){
                    leftMax[i] = Math.max(height[i], leftMax[i-1]);
            }
            rightMax[height.length-1] = height[height.length-1];
            for(int i = height.length-2; i > -1; i--){
                    rightMax[i] = Math.max(height[i], rightMax[i+1]);
            }
            for(int i = 0; i < height.length; i++){
                    sum += Math.min(leftMax[i], rightMax[i]) - height[i];
            }
            return sum;
    }
}

复杂度

  • 时间复杂度 O ( N ) O(N) O(N)
  • 空间复杂度 O ( N ) O(N) O(N)

方法三:双指针

思路

在这里插入图片描述
由图可知,在最高的bar的左面,每个位置积水的数量取决于leftMax,在最高的bar的右面,则取决于rightMax。所以,

代码

class Solution {
    public int trap(int[] height) {
            int left = 0, right = height.length - 1;
            int leftMax = 0, rightMax = 0; 
            int sum = 0;
            while(left <= right){
                    if(height[left] < height[right]){
                            if(leftMax < height[left]){
                                    leftMax = height[left];
                            }else{
                                    sum += leftMax - height[left];
                            }
                            left++;
                    }else{
                            if(rightMax < height[right]){
                                    rightMax = height[right];
                            }else{
                                    sum += rightMax - height[right];
                            }
                            right--;
                    }
            }
            return sum;
    }
}

复杂度

  • 时间复杂度 O ( N ) O(N) O(N)
  • 空间复杂度 O ( ) O() O()

例题六:面试题 17.11. 单词距离

在这里插入图片描述

方法一:双指针

思路

用双指针,在遍历数组的过程中更新最小差值。一定要注意,字符串的比较要用equals!不能用==!

代码

class Solution {
    public int findClosest(String[] words, String word1, String word2) {
        int res= words.length;
        int w1 = -1, w2 = -1;
        for(int i = 0; i < words.length; i++){
            if(words[i].equals(word1)){
                w1 = i;
                if(w1 != -1 && w2 != -1)
                    res = Math.min(res, Math.abs(w1-w2));
            }else if(words[i].equals(word2)){
                w2 = i;
                if(w1 != -1 && w2 != -1)
                    res = Math.min(res, Math.abs(w1-w2));
            }
        }
        return res;
    }
}

复杂度

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

方法二:哈希表

思路:

因为这个过程要重复多次,所以很容易想到用哈希表来存储单词极其对应的下标,于是这道题转换成从两组有序的数组中找到最小的差值。

代码

class Solution {
    public int findClosest(String[] words, String word1, String word2) {
        Map<String, List<Integer>> map = new HashMap<>();
        for(int i = 0; i < words.length; i++){
            if(!map.containsKey(words[i]))
            map.put(words[i], new ArrayList<Integer>());
            map.get(words[i]).add(i);
        }
        List<Integer> list1 = map.get(word1);
        List<Integer> list2 = map.get(word2);
        if(list1 == null || list2 == null)
            return 0;
        int i = 0, j = 0, res = Integer.MAX_VALUE;
        while(i < list1.size() && j < list2.size()){
            if(list1.get(i) < list2.get(j)){
                res = Math.min(res, list2.get(j)-list1.get(i));
                i++;
            }else{
                res = Math.min(res, list1.get(i)-list2.get(j));
                j++;
            }
        }
        return res;
    }
}

复杂度

  • 时间复杂度: O ( N ) O(N) O(N)
  • 空间复杂度: O ( N ) O(N) O(N)

例题七:84. Largest Rectangle in Histogram

在这里插入图片描述

方法一:暴力解法

思路

矩形的宽取决于矩形中最短的bar的长度。

代码

class Solution {
    public int largestRectangleArea(int[] heights) {
            int res = 0;
            for(int i = 0; i < heights.length; i++){
                    int minHeight = Integer.MAX_VALUE;
                    for(int j = i; j< heights.length; j++){
                            minHeight = Math.min(minHeight, heights[j]);
                            res = Math.max(res, (j-i+1)*minHeight);
                    }
            }
            return res;
    }
}

复杂度

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2)
  • 空间复杂度: O ( 1 ) O(1) O(1)

方法二:分治算法

思路

长度最长的矩形,其中最短的bar是矩形的宽,以这个bar为中心,分成左右两个部分,再去寻找它们最短的bar作为左右两个矩形的宽,再继续分割,以此类推。

代码

class Solution {
    public int largestRectangleArea(int[] heights) {
            return helper(heights, 0, heights.length-1);
    }
        public int helper(int[] heights, int start, int end){
                if(start > end)
                        return 0;
                int minIndex = start;
                for(int i = start+1; i <= end; i++){
                        if(heights[minIndex] > heights[i])
                                minIndex = i;
                }
                int area1 = (end-start+1)*heights[minIndex];
                //divide and conquer
                int area2 = Math.max(helper(heights, start, minIndex-1), helper(heights, minIndex+1, end));
                return Math.max(area1, area2);
        }
}

复杂度

  • 时间复杂度:平均复杂度 O ( N l g ( N ) ) O(Nlg(N)) O(Nlg(N)),最坏情况下,数组是有序的,复杂度为 O ( N 2 ) O(N^2) O(N2)
  • 空间复杂度:平均复杂度 O ( N l g ( N ) ) O(Nlg(N)) O(Nlg(N)),最坏情况下 O ( N ) O(N) O(N)

方法三:栈

思路

对于数组中的一个bar来说,以它为高的可能是最大面积的矩形的两端都是恰好刚开始比这个bar小。所以我们从数组的左边开始遍历,当出现一个这样的矩形,我们就把这样的矩形面积计算出来。

  1. Idea is, we will consider every element a[i] to be a candidate for the area calculation. That is, if a[i] is the minimum element then what is the maximum area possible for all such rectangles? We can easily figure out that it’s a[i]*(R-L+1-2) or a[i] * (R-L-1), where a[R] is first subsequent element(R>i) in the array just smaller than a[i], similarly a[L] is first previous element just smaller than a[i]. makes sense? (or take a[i] as a center and expand it to left and right and stop when first just smaller elements are found on both the sides). But how to implement it efficiently?

  2. We add the element a[i] directly to the stack if it’s greater than the peak element (or a[i-1] ), because we are yet to find R for this. Can you tell what’s L for this? Exactly, it’s just the previous element in stack. (We will use this information later when we will pop it out).

  3. What if we get an element a[i] which is smaller than the peak value, it is the R value for all the elements present in stack which are greater than a[i]. Pop out the elements greater than a[i], we have their R value and L value(point 2). and now push a[i] and repeat…

代码

class Solution {
    public int largestRectangleArea(int[] heights) {
            Deque<Integer> stack = new ArrayDeque<>();
            stack.push(-1);
            int res = 0;
            for(int i = 0; i < heights.length; i++){
                    while(stack.peek() != -1 && heights[i] < heights[stack.peek()]){
                            res = Math.max(res, heights[stack.pop()]*(i-stack.peek()-1));
                    }
                    stack.push(i);
            }
            while(stack.peek() != -1){
                    res= Math.max(res, heights[stack.pop()]*(heights.length-stack.peek()-1));
            }
            return res;
        }
}

复杂度

  • 时间复杂度: O ( N ) O(N) O(N)
  • 空间复杂度: O ( N ) O(N) O(N)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值