算法(十四)数组之双指针

leetcode

[hot] 4. 寻找两个正序数组的中位数 [optional]

题目

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

示例 3:
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000

示例 4:
输入:nums1 = [], nums2 = [1]
输出:1.00000

示例 5:
输入:nums1 = [2], nums2 = []
输出:2.00000

题解

合并两个有序数组,记录第mid个元素,如果两个元素的长度和为偶数,需要特殊处理。

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int length1 = nums1.size();
        int length2 = nums2.size();
        int length = length1 + length2;
        int mid = length / 2;
        int left = 0, right = 0;
        int count = 0;
        int res = 0;
        while (left < length1 || right < length2) {
            int left_value = left < length1 ? nums1[left] : INT_MAX;
            int right_value = right < length2 ? nums2[right] : INT_MAX;
            int tmp = min(left_value, right_value);
            if (left_value < right_value) {
                ++left;
            } else {
                ++right;
            }
            if (length % 2 == 1) {
                if (count == mid) {
                    return tmp; 
                }
            } else if (count == mid - 1) {
                res = tmp;
            } else if (count == mid) {
                // double类型转换一定要施加在分子上
                return (double)(tmp + res) / 2;
            }
            ++count;
        }

        return -1.0;
    }
};

复杂度

时间:O(N)
空间:O(1)

题解2

二分法排除元素,优化时间复杂度,示例代码如下所示:

class Solution {
public:
    double core(vector<int>& nums1, vector<int>& nums2, int k) {
        int idx1 = 0;
        int idx2 = 0;
        int len1 = nums1.size();
        int len2 = nums2.size();
        while (true) {
            if (idx1 == len1) {
                return nums2[idx2 + k - 1];
            } 
            if (idx2 == len2) {
                return nums1[idx1 + k - 1];
            }
            if (k == 1) {
                return min(nums1[idx1], nums2[idx2]);
            }
            int n_idx1 = min(idx1 + k / 2 - 1, len1 - 1);
            int n_idx2 = min(idx2 + k / 2 - 1, len2 - 1);
            if (nums1[n_idx1] < nums2[n_idx2]) {
                k -= n_idx1 - idx1 + 1;
                idx1 = n_idx1 + 1;
            } else {
                k -= n_idx2 - idx2 + 1;
                idx2 = n_idx2 + 1;
            }
        }

        return -1;
    } 
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int length = nums1.size() + nums2.size();
        if (length % 2 == 1) {
            return core(nums1, nums2, (length + 1) / 2);
        } else {
            return (core(nums1, nums2, length / 2) + core(nums1, nums2, length / 2 + 1)) / 2.0;
        }
    }
};

复杂度

时间: O ( l o g ( m + n ) ) O(log(m + n)) O(log(m+n))
空间: O ( 1 ) O(1) O(1)

[hot] 11. 盛最多水的容器

题目

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

在这里插入图片描述

题解

这里的正确性证明把解题思路阐述的很清楚。

算法需要有两点需要注意:

  1. 短板一定要往里移动,这样才能保证有可能找到更大的蓄水面积;长板向里移动没办法基于现状找到更大的蓄水面积,因为短板才是影响蓄水面积的关键。
  2. 向外移动没有用,因为向外移动的状态在之前已经遍历过了。

示例代码如下:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int length = height.size();
        int l = 0, r = length - 1;
        int res = 0;
        while (l < r) {
            res = max(res, min(height[r], height[l]) * (r - l));
            if (height[l] <= height[r]) {
                ++l;
            } else {
                --r;
            }
        }

        return res;
    }
};

复杂度

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

[hot] 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]
输出:[]

题解

寻找多个数字作为目标时,不能用二分法。本题采用类似two_sum的解决方法,思路如下:

  1. 升序排序。
  2. 遍历一个元素时,寻找该元素与其后的两个元素组成的三个元素是否和为0。
  3. 遍历过程中注意重复问题。

示例代码如下:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        if (nums.empty()) {
            return result;
        }

        int length = nums.size();
        sort(nums.begin(), nums.end()); // 排序是核心

        for (int k = 0; k < length; ++k) {
            if (nums[k] > 0) {
                break;
            }

			// nums[k] == nums[k - 1]很重要,如果判断条件为nums[k] == nums[k + 1],则会导致误跳过的情况,bad case为[-1, -1, 2, 4]
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            int i = k + 1;
            int j = length - 1;
            int target = -nums[k];
            while (i < j) {
                if (nums[i] + nums[j] == target) {
                    result.emplace_back(vector<int>({nums[k], nums[i], nums[j]}));
                    while (i < j && nums[i] == nums[i + 1]) { // 边界条件细节
                        ++i;
                    }
                    while (i < j && nums[j] == nums[j - 1]) { // 边界条件细节
                        --j;
                    }
                    ++i;
                    --j;
                } else if (nums[i] + nums[j] < target) {
                    ++i;
                } else {
                    --j;
                }
            }
        }
        return result;
    }
};

复杂度

时间复杂度: O ( n 2 ) O(n^2) O(n2),n为数组中元素个数,因为两层遍历
空间复杂度: O ( 1 ) O(1) O(1)

16. 最接近的三数之和

题目

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:

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

题解

解题思路和【15. 3sum】几乎是一模一样的,示例代码如下所示:

class Solution {
public:
    void update(int cur) {
        if (abs(cur - target_in) < abs(best - target_in)) {
            best = cur;
        }
    }

    int threeSumClosest(vector<int>& nums, int target) {
        int length = nums.size();
        target_in = target;
        sort(nums.begin(), nums.end());
        for (int k = 0; k < length; ++k) {
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }

            int i = k + 1;
            int j = length - 1;
            while (i < j) {
                int sum = nums[i] + nums[j] + nums[k];
                if (sum == target) {
                    return target;
                }
                update(sum);
               
                if (sum > target) {
                	// 该逻辑放到外面会导致错过最佳值,这里可以跳过
                	// 但是单纯求三数之和不能跳,因为会miss结果
                    while (i < j && nums[j] == nums[j - 1]) {
                        --j;
                    }
                    --j;
                } else {
                	// 该逻辑放到外面会导致错过最佳值
                    while (i < j && nums[i] == nums[i + 1]) {
                        ++i;
                    }
                    ++i;
                }
            }
        }

        return best;
    }
private:
    int best = 1e5;
    int target_in;
};

复杂度

时间复杂度: O ( n 2 ) O(n^2) O(n2) -> 两层遍历
空间复杂度: O ( 1 ) O(1) O(1)

26. 删除有序数组中的重复项

题目

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

题解

遇到相同元素直接跳过,遇到不相同元素扔到前面,示例代码如下所示:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int length = nums.size();
        if (length == 0) {
            return 0;
        }
        int i = 0;
        for (int j = 1; j < length; ++j) {
            if (nums[j] != nums[i]) {
                nums[++i] = nums[j];
            }
        }

        return i + 1;
    }
};

复杂度

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

27. 移除元素

题目

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

题解

与【26. 删除有序数组中的重复项】思路大体类似,但不同点在于如果nums[j]不等于val时,直接用nums[j]替换nums[i](而不是nums[i+1]),具体来说就是把不等于val的元素前扔,示例代码如下所示:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int length = nums.size();
        if (length == 0) {
            return 0;
        }
        int i = 0;
        for (int j = 0; j < length; ++j) {
            if (nums[j] != val) {
                nums[i] = nums[j];
                ++i;
            }
        }

        return i;
    }
};

复杂度

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

题解2

双指针直接覆盖原理,示例代码如下:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int left = 0;
        int right = nums.size();
        while (left < right) {
            if (nums[left] == val) {
                nums[left] = nums[right - 1];
                --right;
            } else {
                ++left;
            }
        }

        return left;
    }
};

[hot] 31. 下一个排列

题目

实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。必须 原地 修改,只允许使用额外常数空间。

示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]

示例 2:
输入:nums = [3,2,1]
输出:[1,2,3]

示例 3:
输入:nums = [1,1,5]
输出:[1,5,1]

示例 4:
输入:nums = [1]
输出:[1]

题解

下述四步即可得到当前排列的下一个排列:

  1. 从后向前寻找第一个升序数对[i, i+1]
  2. 从后向前寻找第一个大于nums[i]的nums[j]
  3. 交换i和j
  4. 从i+1开始,升序排序

示例图如下:
在这里插入图片描述
示例代码如下:

#include <algorithm>
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int length = nums.size();
        int i = length - 2;
        for (; i >= 0; --i) {
            // 没有等于号
            if (nums[i] < nums[i + 1]) {
                break;
            }
        }
        // 谨记i>=0
        if (i >= 0) {
            int j = length - 1;
            // 没有等于号
            for (; j > i; --j) {
                // 没有等于号
                if (nums[j] > nums[i]) {
                    break;
                }
            }
            swap(nums[i], nums[j]);
        }
        reverse(nums.begin() + i + 1, nums.end());
    }
};

复杂度

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

[hot] 75. 颜色分类

题目

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

示例 1:

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:

输入:nums = [2,0,1]
输出:[0,1,2]
示例 3:

输入:nums = [0]
输出:[0]
示例 4:

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

题解1

两遍遍历数组解决问题。第一遍把0换到前面来,第二遍把1换到所有0的后面,示例代码如下所示:

public:
    void sortColors(vector<int>& nums) {
        int length = nums.size();
        if (length == 0) {
            return;
        }
        int ptr = 0;
        for (int i = 0; i < length; ++i) {
            if (nums[i] == 0) {
                swap(nums[i], nums[ptr++]);
            }
        }

        for (int i = ptr; i < length; ++i) {
            if (nums[i] == 1) {
                swap(nums[i], nums[ptr++]);
            }
        }
    }
}

题解2

双指针遍历一遍数组解决问题。p0指针指向0区域的下一个元素,其实p0指向的元素一定是1,这一点很重要。p1指针指向1区域的下一个元素。求解过程见这里,示例代码如下所示:

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int p0 = 0;
        int p1 = 0;
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] == 1) {
                swap(nums[i], nums[p1++]);
            } else if (nums[i] == 0) {
                swap(nums[i], nums[p0]);
                if (p0 < p1) {
                    swap(nums[i], nums[p1]);
                }
                ++p0;
                ++p1;
            } 
        }
    }
};

复杂度

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

80. 删除有序数组中的重复项 II

题目

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后数组的新长度。不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

题解

快慢指针解决问题。慢指针slow记录目标数组长度,fast记录已经检查过的数组长度,示例代码如下所示:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int length = nums.size();
        if (length <= 2) {
            return length;
        }
        int slow = 2;
        int fast = 2;
        while (fast < length) {
        	// slow - 2 相当于给重复的元素一个buffer
            if (nums[slow - 2] != nums[fast]) {
                nums[slow] = nums[fast];
                ++slow;
            }
            ++fast;
        }

        return slow;
    }
};

复杂度

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

167 两数之和 II - 输入有序数组

题目

给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。

你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例 1:

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
示例 2:

输入:numbers = [2,3,4], target = 6
输出:[1,3]
示例 3:

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

题解

首先说明,同时找多个数字为目标的题目,不能用二分法,因为会删除一半数据会导致错过目标值,因而需要采用首尾双指针解决问题,示例代码如下所示:

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

复杂度

时间复杂度: O ( n ) O(n) O(n),n为数组元素个数
空间复杂度: O ( 1 ) O(1) O(1)

[hot] 283. 移动零

题目

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

示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

题解

双指针s和f解决问题,当没有0时s和f一起移动;当有0时s不动,f动;当f遇到非0时,置换f和s,而后s和f一起动。示例代码如下所示:

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

复杂度

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

905. 按奇偶排序数组

题目

给你一个整数数组 nums,将 nums 中的的所有偶数元素移动到数组的前面,后跟所有奇数元素。

返回满足此条件的 任一数组 作为答案。

示例 1:
输入:nums = [3,1,2,4]
输出:[2,4,3,1]
解释:[4,2,3,1]、[2,4,1,3] 和 [4,2,1,3] 也会被视作正确答案。

示例 2:
输入:nums = [0]
输出:[0]

题解

偶数向前扔,示例代码如下所示:

class Solution {
public:
    vector<int> sortArrayByParity(vector<int>& nums) {
        int i = 0;
        for (int j = 0; j < nums.size(); ++j) {
            if (nums[j] % 2 == 0) {
                swap(nums[j], nums[i++]);
            }
        }

        return nums;
    } 
};r

922. 按奇偶排序数组 II

题目

给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数。
对数组进行排序,以便当 A[i] 为奇数时,i 也是奇数;当 A[i] 为偶数时, i 也是偶数。
你可以返回任何满足上述条件的数组作为答案。

示例:

输入:[4,2,5,7]
输出:[4,5,2,7]
解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。

题解

i遍历所有的偶数位置元素,j遍历所有奇数位置元素,如果在i遍历过程中出现奇数,则令j找到一个偶数和i位置的元素互换即可完成目标,示例代码如下所示:

class Solution {
public:
    vector<int> sortArrayByParityII(vector<int>& nums) {
        int j = 1;
        for (int i = 0; i < nums.size(); i += 2) {
            if (nums[i] % 2 == 1) {
                while (nums[j] % 2 == 1) {
                    j += 2;
                }
                swap(nums[i], nums[j]);
            }
        }

        return nums;
    }
};

复杂度

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

977. 有序数组的平方

题目

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

题解

利用数组已经有序的条件,完成一遍遍历解决问题,不用重新排序,示例代码如下所示:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int length = nums.size();
        vector<int> res(length, 0);
        int i = 0;
        int j = length - 1;
        int pos = length - 1;
        while (i <= j) {
            int square_i = nums[i] * nums[i];
            int square_j = nums[j] * nums[j];
            if (square_i > square_j) {
                res[pos--] = square_i;
                ++i;
            } else {
                res[pos--] = square_j;
                --j;
            }
        }

        return res;
    }
};

复杂度

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

剑指offer

57. 和为s的连续正数序列

题目

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

示例 1:

输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:

输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]

题解

正常遍历解决方案,示例代码如下

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        vector<vector<int>> result;
        if (target <= 0) {
            return result;
        }

        int low = 1;
        int high = 2;
        while (low < high) {
            int cur_sum = (low + high) * (high - low + 1) / 2;
            if (cur_sum > target) {
                ++low;
            } else if (cur_sum == target) {
                vector<int> tmp_result;
                for (int i = low; i <= high; ++i) {
                    tmp_result.emplace_back(i);
                }
                result.emplace_back(tmp_result);
                ++low; // 这里++high和++low效果是一样的
            } else {
                ++high;
            }
        }

        return result;
    }
};

复杂度

时间复杂度: O ( t a r g e t ) O(target) O(target)
空间复杂度: O ( 1 ) O(1) O(1)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值