雙指標

n1ce2cv發表於2024-10-07

雙指標

  • 同向指標
  • 快慢指標
  • 從兩端向中間的指標
  • 其他

922. 按奇偶排序陣列 II

#include <vector>

using namespace std;

class Solution {
public:
    // 時間複雜度 O(n),額外空間複雜度 O(1)
    vector<int> sortArrayByParityII(vector<int> &nums) {
        int even = 0;
        int odd = 1;
        while (even < nums.size() - 1 && odd < nums.size()) {
            // 找到偶數下標,但元素是奇數的位置
            while (even < nums.size() - 1 && ((nums[even] & 1) == 0)) even += 2;
            // 找到奇數下標,但元素是偶數的位置
            while (odd < nums.size() && ((nums[odd] & 1) != 0)) odd += 2;
            // 交換
            if (even < nums.size() - 1 && odd < nums.size()) swap(nums[even], nums[odd]);
        }
        return nums;
    }
};

287. 尋找重複數

  • 要求 不修改 陣列 nums 且只用常量級 O(1) 的額外空間。
#include <vector>

using namespace std;

class Solution {
public:
    // 時間複雜度 O(n),額外空間複雜度 O(1)
    // 類似環形連結串列找入口節點,nums 陣列類似靜態連結串列
    int findDuplicate(vector<int> &nums) {
        int slow = 0, fast = 0;
        slow = nums[slow];
        fast = nums[nums[fast]];
        while (slow != fast) {
            // slow = slow.next
            slow = nums[slow];
            // fast = fast.next.next
            fast = nums[nums[fast]];
        }
        fast = 0;
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }
        return slow;
    }
};

42. 接雨水

  • 按行積累
#include <vector>

using namespace std;

class Solution {
public:
    // 按行累積,每次累加當前行上能接多少水(超時)
    int trap(vector<int> &height) {
        int n = height.size();
        // 找最大高度
        int maxHeight = 0;
        for (const auto &item: height)
            maxHeight = max(maxHeight, item);

        int res = 0;
        // 每次找一層,一格一格的加
        for (int level = 1; level <= maxHeight; ++level) {
            int i = 0;
            // 找到第一個不低於當前 lever 的作為左邊界
            while (i < n && height[i] < level) i++;
            // 找不到左邊界,這層以及上面的層都接不了水
            if (i >= n) break;

            for (int water = 0; i < n; i++) {
                if (height[i] < level) {
                    // 已有左邊界,並且比當前層低,說明這個格子 (i, lever) 可以放水
                    water++;
                } else if (height[i] >= level) {
                    // 找到大於或等於當前層的右邊界,就把之前累積的水加到結果中,並清空 water
                    // 當前的右邊界變成下一個左邊界,在繼續尋找下一個右邊界
                    res += water;
                    water = 0;
                }
            }
        }
        return res;
    }
};
  • 按列積累
#include <vector>

using namespace std;

class Solution {
public:
    // 時間複雜度 O(n),額外空間複雜度 O(n)
    // 按列累積,每次累加當前列上能接多少水
    int trap(vector<int> &height) {
        int n = height.size();
        int res = 0;

        // 記錄當前元素左邊的最大值
        int leftMax = height[0];
        // 可以在後續累積雨水時遍歷陣列的操作中,用 leftMax 最佳化掉 leftMaxArr
        vector<int> leftMaxArr(n);
        // 更新每個元素左邊的最大值
        for (int i = 1; i <= n - 2; ++i) {
            leftMaxArr[i] = leftMax;
            leftMax = max(leftMax, height[i]);
        }

        // 記錄當前元素右邊的最大值
        int rightMax = height[n - 1];
        vector<int> rightMaxArr(n);
        for (int i = n - 2; i >= 1; i--) {
            rightMaxArr[i] = rightMax;
            rightMax = max(rightMax, height[i]);
        }

        // 只有左邊最高的和右邊最高的,二者中的較小者比當年的列高,當前列才能接得住水
        for (int i = 1; i <= n - 2; ++i)
            res += max(0, min(leftMaxArr[i], rightMaxArr[i]) - height[i]);
        return res;
    }
};
  • 雙指標最佳化掉 leftMaxArr、rightMaxArr(最優解)
#include <vector>

using namespace std;

class Solution {
public:
    // 時間複雜度 O(n),額外空間複雜度 O(1)
    int trap(vector<int> &height) {
        int n = height.size();
        int l = 1;
        int r = n - 2;
        // 左邊最高
        int lMax = height[0];
        // 右邊最高
        int rMax = height[n - 1];

        int res = 0;
        while (l <= r) {
            if (lMax <= rMax) {
                // 左邊的最高柱子較低些
                res += max(0, lMax - height[l]);
                lMax = max(lMax, height[l++]);
            } else {
                // 右邊的最高柱子較低些
                res += max(0, rMax - height[r]);
                rMax = max(rMax, height[r--]);
            }
        }
        return res;
    }
};
#include <vector>
#include <stack>

using namespace std;

class Solution {
public:
    int trap(vector<int> &height) {
        int res = 0;
        stack<int> stk;

        for (int i = 0; i < height.size(); i++) {
            // 當前高度大於棧頂高度,說明之前的地方有可能能接水
            // 持續出棧,直到棧頂高度大於等於當前高度或者棧為空
            while (!stk.empty() && height[i] > height[stk.top()]) {
                int top = height[stk.top()];
                stk.pop();
                if (stk.empty()) break;
                // 兩個柱子間的距離,不包含兩端
                int distance = i - stk.top() - 1;
                // height[stk.top()] 為左邊界,height[i] 為右邊界
                int smaller = min(height[stk.top()], height[i]);
                res += distance * (smaller - top);
            }
            stk.emplace(i);
        }
        return res;
    }
};

881. 救生艇

#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    // 時間複雜度 O(n * logn),因為有排序,額外空間複雜度 O(1)
    int numRescueBoats(vector<int> &people, int limit) {
        sort(people.begin(), people.end());
        int res = 0;
        for (int l = 0, r = people.size() - 1; l <= r; res++) {
            int sum = l == r ? people[l] : people[l] + people[r];
            // 如果沒超重,最輕的可以和最重的共用一條船
            if (sum <= limit) l++;
            r--;
        }
        return res;
    }
};

11. 盛最多水的容器

#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    // 時間複雜度 O(n),額外空間複雜度 O(1)
    int maxArea(vector<int> &height) {
        int res = 0;
        for (int l = 0, r = height.size() - 1; l < r;) {
            int water = (r - l) * min(height[l], height[r]);
            res = max(res, water);
            // 每次把短板往中間靠,短板可能變長,總面積才可能變大
            // 如果移動長板,底一定變小,高度不會超過之前的那個短板,高只會原來越低,面積只會變小
            if (height[l] < height[r]) {
                l++;
            } else {
                r--;
            }
        }
        return res;
    }
};

475. 供暖器

#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    // 返回當前的地點 houses[i] 由 heaters[j] 來供暖是否是最優
    // 當前的地點 houses[i] 由 heaters[j] 來供暖,產生的半徑是a
    // 當前的地點 houses[i] 由 heaters[j + 1] 來供暖,產生的半徑是 b
    // 如果 a < b, 說明是最優,供暖不應該跳下一個位置
    // 如果 a >= b, 說明不是最優,應該跳下一個位置
    bool best(vector<int> &houses, vector<int> &heaters, int i, int j) {
        return j == heaters.size() - 1
               || abs(heaters[j] - houses[i]) < abs(heaters[j + 1] - houses[i]);
    }

    // 時間複雜度 O(n * logn),因為有排序,額外空間複雜度 O(1)
    int findRadius(vector<int> &houses, vector<int> &heaters) {
        sort(houses.begin(), houses.end());
        sort(heaters.begin(), heaters.end());

        int res = 0;
        // i 號房屋,j 號供暖器
        for (int i = 0, j = 0; i < houses.size(); i++) {
            while (!best(houses, heaters, i, j)) j++;
            res = max(res, abs(heaters[j] - houses[i]));
        }
        return res;
    }
};

41. 缺失的第一個正數

  • 原地對映:把值為 value 的元素對映到陣列中下標為 value - 1 的位置
#include <vector>

using namespace std;

class Solution {
public:
    // 原地對映:把值為 value 的元素對映到陣列中下標為 value-1 的位置
    // 最佳化了對對映後覆蓋掉的值的處理
    int firstMissingPositive(vector<int> &nums) {
        int n = nums.size();
        for (int i = 0; i < n; ++i) {
            // 如果 nums[i] 也能對映到陣列中,並且尚未對映過
            while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
                // 把 nums[i] 對映到 nums[nums[i] - 1],nums[nums[i] - 1] 放在 nums[i]
                // 然後繼續判斷新的 nums[i] 是否也需要對映
                swap(nums[nums[i] - 1], nums[i]);
            }
        }
        // 遍歷尋找第一個空缺
        for (int i = 0; i < n; ++i)
            if (nums[i] != i + 1)
                return i + 1;
        // 重新對映後,陣列中沒有空缺,說明缺失的第一個正數就是 n + 1
        return n + 1;
    }
};
  • 原地對映:把可以對映到的地方的元素改成負數
#include <vector>
#include <valarray>

using namespace std;

class Solution {
public:
    // 原地對映:把可以對映到的地方的元素改成負數,對映規則還是 value 對映到 nums[value-1]
    int firstMissingPositive(vector<int> &nums) {
        int n = nums.size();
        // 非正數改成 n + 1
        for (int i = 0; i < n; ++i)
            if (nums[i] <= 0)
                nums[i] = n + 1;

        for (int i = 0; i < n; ++i) {
            // 待對映的值
            int value = abs(nums[i]);
            // 如果可以對映到陣列裡,就把對映到的地方的元素改成負數
            if (value <= n) nums[value - 1] = -abs(nums[value - 1]);
        }
        for (int i = 0; i < n; ++i)
            if (nums[i] > 0) 
                return i + 1;
        return n + 1;
    }
};
  • 雙指標
#include <vector>

using namespace std;

class Solution {
public:
    // 時間複雜度 O(n),額外空間複雜度 O(1)
    int firstMissingPositive(vector<int> &nums) {
        // [0, l) 為已經對映的區域,l 是待處理的位置
        int l = 0;
        // [r, ...) 為無法對映的區域
        // r 的第二個含義是這 r 個數最好的情況下能對映到 [0, r-1]
        // 當有一個數字無法對映的時候,就剩下 r-1 個待對映的,最好情況下能對映到 [0, r-2]
        int r = nums.size();
        // [l, r) 為待對映的區域

        while (l < r) {
            if (nums[l] == l + 1) {
                // 已經對映好了,已對映區域右擴
                l++;
            } else if (nums[l] <= l || nums[l] > r || nums[nums[l] - 1] == nums[l]) {
                // 把對映不了的數字或者重複出現的數字,與後面待對映的數字交換,無法對映的區域左擴
                // nums[l] <= l 說明 nums[l] 已經對映好了,已經對映的區域中有重複
                // nums[l] > r 說明無法對映
                // nums[nums[l] - 1] == nums[l] 也是說明 nums[l] 已經對映好了,待對映區域中有重複
                swap(nums[l], nums[--r]);
            } else {
                // 可以對映
                swap(nums[l], nums[nums[l] - 1]);
            }
        }
        return l + 1;
    }
};

相關文章