最長遞增子序列

n1ce2cv發表於2024-10-14

最長遞增子序列

300. 最長遞增子序列

  • 普通解法
#include <vector>

using namespace std;

class Solution {
public:
    // 時間複雜度 O(n^2)
    int lengthOfLIS(vector<int> &nums) {
        int n = nums.size();
        // dp[i]: 以 nums[i] 結尾的最長遞增子序列
        vector<int> dp(n);
        int res = 0;
        for (int i = 0; i < n; ++i) {
            dp[i] = 1;
            for (int j = 0; j < i; ++j) {
                if (nums[j] < nums[i])
                    dp[i] = max(dp[i], dp[j] + 1);
            }
            res = max(res, dp[i]);
        }
        return res;
    }
};
  • 最優解
#include <vector>

using namespace std;

class Solution {
public:
    // 大於等於 target 的左邊界
    int binarySearch(vector<int> &ends, int len, int target) {
        int left = 0;
        int right = len - 1;
        int mid;
        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (ends[mid] >= target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }

    // 時間複雜度 O(n * logn)
    int lengthOfLIS(vector<int> &nums) {
        int n = nums.size();
        // ends[i] 表示所有長度為 i + 1 的遞增子序列的最小結尾
        // [0, len-1] 是有效區,有效區內的數字一定嚴格升序
        vector<int> ends(n);
        // len 表示 ends 陣列目前的有效區長度
        int len = 0;
        for (int i = 0, pos; i < n; ++i) {
            pos = binarySearch(ends, len, nums[i]);
            if (pos == len) {
                // 找不到就擴充 ends
                ends[len++] = nums[i];
            } else {
                // 找到了就更新成更小的 nums[i]
                ends[pos] = nums[i];
            }
        }
        return len;
    }
};
  • 最長不下降子序列
#include <vector>

using namespace std;

class Solution {
public:
    // 大於 target 的左邊界
    int binarySearch(vector<int> &ends, int len, int target) {
        int left = 0;
        int right = len - 1;
        int mid;
        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (ends[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }

    // 時間複雜度 O(n * logn)
    int lengthOfLIS(vector<int> &nums) {
        int n = nums.size();
        // ends[i] 表示所有長度為 i + 1 的不下降子序列的最小結尾
        // [0, len-1] 是有效區,有效區內的數字非遞減
        vector<int> ends(n);
        // len 表示 ends 陣列目前的有效區長度
        int len = 0;
        for (int i = 0, pos; i < n; ++i) {
            pos = binarySearch(ends, len, nums[i]);
            if (pos == len) {
                // 找不到就擴充 ends
                ends[len++] = nums[i];
            } else {
                // 找到了就更新成更小的 nums[i]
                ends[pos] = nums[i];
            }
        }
        return len;
    }
};

354. 俄羅斯套娃信封問題

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

class Solution {
public:
    // 找大於等於的左邊界
    int binarySearch(vector<int> &ends, int len, int target) {
        int left = 0;
        int right = len - 1;
        int mid;
        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (ends[mid] >= target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }

    int maxEnvelopes(vector<vector<int>> &envelopes) {
        // 寬度從小到大,寬度一樣,高度從大到小
        sort(begin(envelopes), end(envelopes),
             [](vector<int> &v1, vector<int> &v2) {
                 return v1[0] == v2[0] ? v1[1] > v2[1] : v1[0] < v2[0];
             });

        int n = envelopes.size();
        // ends[i] 表示長度為 i + 1 的子序列的最小末尾元素
        // 在有效區內嚴格遞增
        vector<int> ends(n);
        // ends 陣列的有效長度
        int len = 0;

        for (int i = 0, pos; i < n; ++i) {
            int target = envelopes[i][1];
            pos = binarySearch(ends, len, target);
            if (pos == len) {
                // 找不到就擴充 ends
                ends[len++] = target;
            } else {
                // 找到了就更新成更小的 nums[i]
                ends[pos] = target;
            }
        }

        return len;
    }
};

2111. 使陣列 K 遞增的最少操作次數

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

class Solution {
public:
    // 大於 target 的左邊界
    int binarySearch(vector<int> &ends, int len, int target) {
        int left = 0;
        int right = len - 1;
        int mid;
        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (ends[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }

    // 時間複雜度 O(n * logn)
    int lengthOfLIS(vector<int> &nums) {
        int n = nums.size();
        // ends[i] 表示所有長度為 i + 1 的不下降子序列的最小結尾
        // [0, len-1] 是有效區,有效區內的數字非遞減
        vector<int> ends(n);
        // len 表示 ends 陣列目前的有效區長度
        int len = 0;
        for (int i = 0, pos; i < n; ++i) {
            pos = binarySearch(ends, len, nums[i]);
            if (pos == len) {
                // 找不到就擴充 ends
                ends[len++] = nums[i];
            } else {
                // 找到了就更新成更小的 nums[i]
                ends[pos] = nums[i];
            }
        }
        return len;
    }

    int kIncreasing(vector<int> &arr, int k) {
        int n = arr.size();
        int res = 0;
        // 分為 k 組
        for (int i = 0; i < k; ++i) {
            vector<int> temp;
            for (int j = i; j < n; j += k)
                temp.emplace_back(arr[j]);
            // 累加這一組需要修改的數字
            res += temp.size() - lengthOfLIS(temp);
        }
        return res;
    }
};

646. 最長數對鏈

#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    int binarySearch(vector<int> &ends, int len, int target) {
        int left = 0;
        int right = len - 1;
        int mid;
        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (ends[mid] >= target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }

    int findLongestChain(vector<vector<int>> &pairs) {
        // 按照數對中第一個數增序
        sort(begin(pairs), end(pairs),
             [](vector<int> &v1, vector<int> &v2) {
                 return v1[0] < v2[0];
             });
        int n = pairs.size();
        vector<int> ends(n);
        int len = 0;
        for (int i = 0, pos; i < n; ++i) {
            // 根據數對中第一個數字查
            pos = binarySearch(ends, len, pairs[i][0]);
            if (pos == len) {
                // 插入的是數對中的第二個數字
                ends[len++] = pairs[i][1];
            } else {
                // 改成較小的
                ends[pos] = min(ends[pos], pairs[i][1]);
            }
        }
        return len;
    }
};

P8776 [藍橋杯 2022 省 A] 最長不下降子序列

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

int n, k;

// 求最長不上升子序列長度的二分
// ends[0, len - 1] 為降序,找小於 target 的最左位置
int binarySearch1(vector<int> &ends, int len, int target) {
    int left = 0;
    int right = len - 1;
    int mid;
    while (left <= right) {
        mid = left + ((right - left) >> 1);
        if (ends[mid] < target) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return left;
}

// 求最長不下降子序列長度的二分
// ends[0, len-1] 為升序,找大於 target 的最左位置
int binarySearch2(vector<int> &ends, int len, int target) {
    int left = 0;
    int right = len - 1;
    int mid;
    while (left <= right) {
        mid = left + ((right - left) >> 1);
        if (ends[mid] > target) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return left;
}

// 生成輔助陣列 rightMaxLen
// rightMaxLen[i]: 以 nums[i] 開頭的最長不下降子序列長度
// 等價於從右往左遍歷,以 nums[i] 做結尾的情況下的最長不上升子序列
vector<int> getRightMaxLen(vector<int> &ends, vector<int> &nums) {
    vector<int> rightMaxLen(nums.size());
    int len = 0;
    for (int i = n - 1, pos; i >= 0; i--) {
        pos = binarySearch1(ends, len, nums[i]);
        if (pos == len) {
            // 擴充 endsArr
            ends[len++] = nums[i];
            // 記錄長度
            rightMaxLen[i] = len;
        } else {
            ends[pos] = nums[i];
            rightMaxLen[i] = pos + 1;
        }
    }
    return rightMaxLen;
}

int main() {
    cin >> n >> k;
    vector<int> nums;
    nums.resize(n);
    for (int i = 0; i < n; ++i)
        cin >> nums[i];

    // 生成輔助陣列
    vector<int> ends(n);
    vector<int> rightMaxLen = getRightMaxLen(ends, nums);

    int len = 0;
    int res = 0;
    for (int i = 0, j = k, pos; j < n; i++, j++) {
        // 根據當前劃分點查,劃分點左側連續 k 個位置是要改成 nums[j] 的
        pos = binarySearch2(ends, len, nums[j]);

        // res 由三部分組成
        // 左側:劃分點左側連續 k 個位置再往前的區域中,長度為 pos 的不下降子序列(最大值小於 nums[j])
        // 中間:劃分點左側連續 k 個位置
        // 右側:必須以 nums[j] 開始的不下降子序列的長度
        res = max(res, pos + k + rightMaxLen[j]);

        // 要插入的是 nums[i],所以要再查詢下插入位置
        pos = binarySearch2(ends, len, nums[i]);
        if (pos == len) {
            ends[len++] = nums[i];
        } else {
            ends[pos] = nums[i];
        }
    }
    // 特例:最後 k 個元素都改成左側不下降子序列的最後一個值
    res = max(res, len + k);
    cout << res;
}

相關文章