Bootstrap

代码练习9---双指针

一、把数组中的零移动到末尾

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

思路

  • 遍历指针 (i)

    • 这是在 for 循环中用于遍历数组的指针。
    • i 从数组的第一个元素开始,一直遍历到最后一个元素。
    • 每次迭代中,i 指向当前检查的元素。
  • 非零元素指针 (nonZeroIndex)

    • 这是一个用于记录下一个非零元素应该放置的位置的指针。
    • 初始值为 0。
    • 当遍历指针 i 发现一个非零元素时,nonZeroIndex 指向的位置会被设置为该非零元素,然后 nonZeroIndex 自增。
    • 这样确保所有非零元素按照原始顺序被放置在数组的前部。

C++核心代码: 

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int nonZeroIndex=0;
        for(int i=0;i<nums.size();i++){
            if(nums[i]!=0){
                nums[nonZeroIndex]=nums[i];
                nonZeroIndex++;
            }
        }
        for(int i=nonZeroIndex;i<nums.size();i++){
            nums[i]=0;
        }
        

    }
};

 二、盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。

 使用双指针法解决这个问题的主要原因是为了高效地找到可以容纳最多水的容器,同时保持算法的时间复杂度为 O(n).

思路解释

  1. 初始化双指针:左指针 left 初始化为数组的第一个元素,右指针 right 初始化为数组的最后一个元素。
  2. 计算当前面积:使用当前左右指针指向的两个垂线,计算它们与 x 轴构成的容器的面积。
  3. 记录最大面积:在每一步计算中,记录到目前为止发现的最大面积。
  4. 移动指针:每次根据左右指针指向的垂线的高度来决定移动哪一个指针:
    • 如果左边的高度较小,则移动左指针;
    • 如果右边的高度较小或相等,则移动右指针。

移动指针的目的是尝试找到一个更高的垂线,从而可能增加容器的高度,进而增加面积。

为什么先有 while (left < right) 再有 if (height[left] < height[right])

  • 循环控制

    • while (left < right) 是循环的主控制结构,确保双指针从两端向中间移动,遍历所有可能的容器组合。
    • 没有这个循环,算法就无法遍历所有可能的容器组合,也就无法找到最大的面积。当 leftright 相遇时,意味着所有可能的容器组合都已经被遍历过,循环结束。
  • 指针移动决策

    • if (height[left] < height[right]) 是在每次循环中决定如何移动指针的条件。
    • 根据当前 leftright 位置的高度,决定是移动 left 还是 right,从而找到可能更大的容器。

C++核心代码: 

class Solution {
public:
    int maxArea(vector<int>& height) {
        int left=0;
        int right = height.size()-1;
        int max_area=0;
        while(left < right){
            int width = right -left;
            int current_height = min(height[left],height[right]);
            int current_area = width * current_height;
            max_area = max(max_area,current_area);
            if(height[left]<height[right]){
                left++;
            }
            else{
                right--;
            }
        }
        return max_area;

    }
};

三、返回所有和为 0 且不重复的三元组

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

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

 解题思路

为了高效地解决这个问题,我们可以采用排序和双指针的方法。以下是详细的步骤:

  1. 排序:首先对数组进行排序。这将有助于我们使用双指针技术。
  2. 遍历数组:使用一个循环,固定一个元素,假设这个元素是第一个元素 nums[i]
  3. 双指针查找:在剩下的元素中使用双指针查找两个数,使得这三个数的和为零。具体做法是:
    • 左指针 left 初始化为 i + 1
    • 右指针 right 初始化为数组的末尾。
    • 计算当前三个数的和:
      • 如果和为零,记录这个三元组,并移动左指针和右指针以查找下一个可能的三元组。
      • 如果和小于零,说明需要更大的数,因此移动左指针。
      • 如果和大于零,说明需要更小的数,因此移动右指针。
  4. 去重:在整个过程中,注意跳过重复的元素以避免输出重复的三元组。

C++核心代码:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>>result;
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++){
            if(i>0 && nums[i]==nums[i-1]){
                continue;
            }
            int left = i +1;
            int right = nums.size() -1;
            while(left<right){
                int sum = nums[i]+nums[left]+nums[right];
                if(sum == 0){
                    result.push_back({nums[i],nums[left],nums[right]});
                    while(left <right && nums[left]==nums[left + 1]){
                        left++;
                    }
                    while(left<right && nums[right] == nums[right-1]){
                        right--;
                    }
                    left++;
                    right--;
                }
                else if (sum<0){
                    left++;

                }
                else{
                    right--;
                }
            }

        }
        return result;
    }
};

四、接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

双指针法(优化方法): 使用两个指针分别从数组的两端向中间移动,同时记录当前左右两端的最大高度。这种方法的时间复杂度是 O(n)和空间复杂度是 O(1).

双指针法的步骤

  1. 初始化:

    • 使用两个指针 leftright,分别指向数组的两端。
    • 使用两个变量 left_maxright_max 来记录当前左边和右边的最大高度。
    • 使用一个变量 trapped_water 来累计总的雨水量。
  2. 遍历:

    • left 指针小于等于 right 指针时:
      • 如果 height[left] 小于等于 height[right],处理左边的柱子:
        • 如果 height[left] 大于等于 left_max,更新 left_max
        • 否则,计算当前 left 指针位置的雨水量并累加到 trapped_water
        • 移动 left 指针向右。
      • 否则,处理右边的柱子:
        • 如果 height[right] 大于等于 right_max,更新 right_max
        • 否则,计算当前 right 指针位置的雨水量并累加到 trapped_water
        • 移动 right 指针向左。
  3. 输出结果:

    • 返回累积的总雨水量 trapped_water

 C++核心代码:

class Solution {
public:
    int trap(vector<int>& height) {
        if(height.empty()) return 0;
        int left = 0;
        int right = height.size()-1;
        int left_max=0;
        int right_max=0;
        int trapped_water=0;
        while(left<=right){
            if(height[left]<=height[right]){
                if(height[left]>= left_max){
                    left_max = height[left];
                }
                else{
                    trapped_water += left_max-height[left];
                }
                ++left;
            }
            else{
                if(height[right] >= right_max){
                    right_max = height[right];

                }
                else{
                    trapped_water += right_max-height[right];
                }
                --right;
            }
        }
        return trapped_water;

    }
};

;