thảo luận [Học Tập] Topic thuật toán

  • Người tạo chủ đề Người tạo chủ đề unknowpc90
  • Ngày bắt đầu Ngày bắt đầu
Bài này giải DP như nào vậy mn:
cho một mảng, và một giới hạn M, tìm số lượng tối thiểu số trong mảng để có tổng lớn nhất không vượt quá M.

ví dụ: Mảng = {4,5,7,10,12}, M = 20

thì có 4+5+10 =10 và 7 + 12 =19 nên chọn 7 và 12 vì số lượng số là 2, ít hơn.
Thử suy nghĩ bth trước nếu k dùng DP thì fen sẽ giải ntn ?
Sau đó, fen nhận thấy cách làm brute-force sẽ phải giải đi giải lại các bài toán con
Tìm cách lưu lại các bài toán con đó để improve performance ?
Cuối cùng là nghĩ cách làm bottom up cho nó đậm chất DP :D

Cố lên mai fen :D. Tui có list các bài DP cho beginner, nếu fen cần thì hộp tui gửi cho.
 
Hic, fen hiểu nhầm ý tuôi. Ý tui là thay vì LCS thì LIS đỡ hại não hơn. Nhưng mà dù xao thì phen thẩm đc cái LIS thì giỏi hơn tui rồi. Hồi đó tui mất cả tháng mới hiểu nổi con hàng này T_T.
tui đã hiểu đâu fen :(, hiểu thì đã biết cách sửa giữa strict và loosely rồi, mới đang tìm code trên mạng thui, dần dần chắc mới hiểu sâu được
 
@Lập Trình Viên Số Khổ
Thấy thím nói đến bài LIS nhiều quá nên cũng mò làm thử. Đây là cách t tự làm, cũng k có gì đặc biệt lắm.
C++:
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> caches(nums.size(), 0);
        int maxLen = 0;
        for (int i = 0; i < nums.size(); i++){
            int curVal = lengthOfLISRecursive(nums, caches, i);
            maxLen = max(maxLen, curVal);
        }
        return maxLen;
    }
    
    int lengthOfLISRecursive(vector<int>& nums, vector<int> &caches, int pos) {
        if (pos == nums.size() - 1) return 1;
        if (caches[pos] > 0) return caches[pos];
        
        int maxLen = 1;
        for (int i = pos + 1; i < nums.size(); i++){
            if (nums[i] <= nums[pos]) continue;
            int curVal = 1 + lengthOfLISRecursive(nums, caches, i);
            maxLen = max(maxLen, curVal);
        }
        caches[pos] = maxLen;
        return maxLen;
    }
};
Tuy nhiên cách này của t độ phức tạp là O(n^2), chưa được tối ưu. Thấy có cách O(nlogn) mà k nghĩ ra được, cũng chưa có tgian coi solution. :D
 
cái này là bài kinh điển cho 2D segment tree (ST với các ST ở các node) rồi.

gọi dp là độ dài của dãy tăng nhau to nhất tới pair i có (xi, yi) đang xét.

với mỗi pair (xi, yi), lấy max của ST trong đoạn (0,0) tới (xi - 1, yi - 1) rồi update (xi, yi) với giá trị đó + 1. muốn in ra thì truy vết lại.

vì giới hạn tới 10^9 nên phải thực hiện nén mảng (các quan hệ <, >, = vẫn giữ nguyên giữa các phần tử nhưng chiều dài của vùng thay đổi lại, vd 1 10^6 10^9 10^5 nén thành 1 3 4 2).

sau khi nén xong thì mỗi chiều vẫn còn cỡ 10^5, khá to nên phải cài đặt segment tree động ở các node để có thể có đủ bộ nhớ.


nên code LIS bth bằng segment tree 1D trước rồi chuyển đến bài này thì đơn giản hơn.
chưa thấy code lis = segment tree baoh
b implement thử bài pair cho mình xem với :D :D
 
Tiếp tục chuyên mục mỗi ngày một leetcode. Các bài mình làm ngày hôm nay:

#432. (Hard) https://leetcode.com/problems/all-oone-data-structure/
#433. (Medium) https://leetcode.com/problems/minimum-genetic-mutation/
#434. (Easy) https://leetcode.com/problems/number-of-segments-in-a-string/
#435. (Medium) https://leetcode.com/problems/non-overlapping-intervals/

Mình sẽ share về bài Non-overlapping Intervals:
#435. (Medium) https://leetcode.com/problems/non-overlapping-intervals/

Phân tích bài toán:
  • 1 <= intervals.length <= 10^5: O(nlogn)
  • Như kinh nghiệm bao năm, từ O(nlogn) trở lên mà thấy việc sort không làm mất bản chất bài toán thì cứ tìm cách sort lại dữ liệu rồi tính sau
  • Ta thấy dữ liệu có 2 chiều, nên ta cần suy nghĩ nên sort lại theo chiều nào ?
  • Đề bài yêu cầu loại bỏ các interval sao cho phần còn lại không overlap lên nhau ? Hay nếu nhìn khác đi thì nếu 2 interval overlap vào nhau, chắc chắn phải có một cái bị loại bỏ.
  • Vậy 2 interal overlap với nhau khi nào, điều kiện là: start1 < end2 && start2 < end1
  • Nếu áp dụng trên dữ liệu đã sắp xếp thì sao ? Giả sử ta sắp xếp sao cho (start1, end1) nằm trước (start2, end2):
    • Nếu sắp xếp theo start, ta mặc định đã có start1 < start2 < end2. Nên chỉ cần kiểm tra điều kiện start2 < end1 là được
    • Nếu sắp xếp theo end, ta cũng sẽ có điều tương tự: start1 < end1 < end2.
  • Vậy tóm lại là sort theo cái nào cũng được, và để kiểm tra overlap giữa 2 interval ta chỉ cần kiểm tra start2 < end1 là được
  • Vậy ta nên làm gì khi phát hiện overlap ? Chắc chắn là phải bỏ đi 1 interval rồi, nhưng bỏ đi cái nào ?
  • Nếu ai yêu thích DP có thể thực hiện ngay tại đây, thay vì phải tìm cách bỏ đi 1 cái, ta cứ thử cả 2 trường hợp, xem cái nào tốt hơn thì giữ lại.
  • Tuy nhiên, nếu tinh ý hơn ta có thể nhận thấy một quy tắc greedy đơn giản là cái nào có end lớn hơn thì sẽ bị bỏ đi. Các bạn có thể tự suy nghĩ vì sao phương pháp này vẫn tối ưu toàn cục.
  • Mặc dù là sort theo start hay end đều được, nhưng bên dưới ta cần xử lý thêm là cái nào có end lớn hơn sẽ bị bỏ đi, nên sort theo end sẽ tối ưu hơn một chút. Vì interval nằm sau chắc chắn sẽ có end lớn hơn, đỡ phải kiểm tra.
  • Vì bài toán không yêu cầu phải trả về mảng kết quả sau khi remove, nên ta chỉ cần lưu lại các giá trị cần thiết và tracking bằng một biến đếm là được

Solution:
  • Sort lại mảng interval theo end
  • Khởi tạo giá trị lastTime ban đầu bằng -math.inf
  • Lặp với mỗi interval đã sort:
    • Nếu start < lastTime: Interval này phải bị bỏ đi => tăng biến đếm
    • Ngược lại: Interval này được giữ lại, cập nhật lại lastTime = end
  • Trả về biến đếm

Python:
class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key=lambda i: i[1])
     
        lastTime = -math.inf
        count = 0
        for start, end in intervals:
            if start < lastTime:
                count += 1
            else:
                lastTime = end
         
        return count
 
Nói rõ chỗ binary search ra được ko bác. Em chưa hiểu chỗ binary search thì sao mình tính tổng 3 con số được
Chắc ý thím này là sort, sau đó viết 2 vòng for để pick ra 2 số, số còn lại dùng binary search do dãy đã đc sort. Làm vậy thì độ phức tạp là O(n^2logn). Khả năng cao timeout.
 
Chắc ý thím này là sort, sau đó viết 2 vòng for để pick ra 2 số, số còn lại dùng binary search do dãy đã đc sort. Làm vậy thì độ phức tạp là O(n^2logn). Khả năng cao timeout.
O(n^2logn) thì cũng k khác mấy với O(n^2) nên chắc k timeout đâu fen. Nhưng bài này dùng hashmap có thể làm O(n^2). Bất lợi là tốn O(n) space.

Vậy giờ làm sao bác. Bác cho em gợi ý đi, để em vượt qua bài này với
Nếu bạn lặp với mỗi cặp số trong mảng (O(n^2)). Bạn sẽ có tổng a + b. Bài toán trở thành kiểm tra xem trong mảng có tồn tại số c sao cho a + b +c = target hay không. Bạn có thể tự nghĩ thêm làm sao để kiểm tra điều này, với gợi ý là dùng binary search (O(logn)) hoặc Hashmap (O(1)).
 
Chắc ý thím này là sort, sau đó viết 2 vòng for để pick ra 2 số, số còn lại dùng binary search do dãy đã đc sort. Làm vậy thì độ phức tạp là O(n^2logn). Khả năng cao timeout.
nlogn thôi fen
edit: n^2 nhé các fen, my bad
Python:
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        if nums == [0, 0, 0]:
            return [[0, 0, 0]]
        res = []
        nums.sort()
        ia = 0
        while ia < len(nums) - 2:
            ib = ia + 1
            ic = len(nums) - 1
            while ib < ic:
                s = nums[ia] + nums[ib] + nums[ic]
                if s < 0:
                    ib += 1
                elif s > 0:
                    ic -= 1
                else:
                    a, b, c = nums[ia], nums[ib], nums[ic]
                    while nums[ib] == b and ib < ic:
                        ib += 1
                    while nums[ic] == c and ib < ic:
                        ic -= 1
                    res.append([a, b, c])
                   
            a = nums[ia]
            while ia < len(nums)-2 and nums[ia] == a:
                ia += 1
       
        return res
 
Sửa lần cuối:
O(n^2logn) thì cũng k khác mấy với O(n^2) nên chắc k timeout đâu fen. Nhưng bài này dùng hashmap có thể làm O(n^2). Bất lợi là tốn O(n) space.


Nếu bạn lặp với mỗi cặp số trong mảng (O(n^2)). Bạn sẽ có tổng a + b. Bài toán trở thành kiểm tra xem trong mảng có tồn tại số c sao cho a + b +c = target hay không. Bạn có thể tự nghĩ thêm làm sao để kiểm tra điều này, với gợi ý là dùng binary search (O(logn)) hoặc Hashmap (O(1)).
trong wiki là nó giải bằng 2-pointer
a+b+c=0 tức là a+b=-c.
Cho c là array[i], i từ 1->n thì thành lồng 2-pointer vào vòng for này là được thôi
2-pointer là O(N) thêm 1 vòng for ngoài O(N) nữa, thành O(N^2)
https://en.wikipedia.org/wiki/3SUM#Quadratic_algorithm
 
Sửa lần cuối:
nlogn thôi fen
Python:
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        if nums == [0, 0, 0]:
            return [[0, 0, 0]]
        res = []
        nums.sort()
        ia = 0
        while ia < len(nums) - 2:
            ib = ia + 1
            ic = len(nums) - 1
            while ib < ic:
                s = nums[ia] + nums[ib] + nums[ic]
                if s < 0:
                    ib += 1
                elif s > 0:
                    ic -= 1
                else:
                    a, b, c = nums[ia], nums[ib], nums[ic]
                    while nums[ib] == b and ib < ic:
                        ib += 1
                    while nums[ic] == c and ib < ic:
                        ic -= 1
                    res.append([a, b, c])
                  
            a = nums[ia]
            while ia < len(nums)-2 and nums[ia] == a:
                ia += 1
      
        return res
làm gì có /2 gì mà đòi log N fen
1628524927403.png


bài này thế giới còn chưa giải được nữa là log
 
Nói rõ chỗ binary search ra được ko bác. Em chưa hiểu chỗ binary search thì sao mình tính tổng 3 con số được
bác giải bài 2sum bằng 2pointer chưa? Bài ấy O(n), bài này thêm 1 vòng for ngoài nữa là O(n^2), bài này em làm tuần trước, mấy tuần này bận thi cuối kì quá không code được. Mà e thấy mới làm leetcode 2 3 tuần học được đúng cái trò sort với 2pointer, học vững lý thuyết nhảy vào làm nhiều tác dụng hơn.:too_sad: Trước đọc sách mấy bữa là làm ngon đệ qui, quay lui rồi
C++:
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>>res;
    if(nums.size()==0) return res;
    sort(nums.begin(),nums.end());
    for(int i=0;i<nums.size()-1;++i){
        if(i!=0 && nums[i]==nums[i-1]) continue;
        int l= i+1;
        int r= nums.size()-1;
        while(l<r){
            if(nums[i]+nums[l]+nums[r]==0){
               res.push_back({nums[l],nums[i],nums[r]});
                l++;
                r--;
                while(l<r&&nums[l]==nums[l-1])
                l++;
            }
            else if(nums[i]+nums[l]+nums[r]<0){
                l++;
            }
            else{
                r--;
            }
        }
    }
    return res;
    }
};
 
chưa thấy code lis = segment tree baoh
b implement thử bài pair cho mình xem với :D :D
cài = segtree ăn TLE rồi :amazed: cài bằng cây fenwick thì ăn được

C++:
#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 3;

typedef pair <int, int> ii;

vector<int> node[N], ft[N];
ii vA[N];
int n;

void update(vector<int> &ft, int pos, int val) {
    for ( ; pos <= ft.size(); pos += (pos & -pos)) {
        ft[pos] = max(ft[pos], val);
    }
}

int get(vector<int> &ft, int pos) {
    int ans = 0;
    for ( ; pos > 0; pos -= (pos & -pos)) {
        ans = max(ans, ft[pos]);
    }
    return ans;
}

int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cin >> n;
    vector<int> vX(n), vY(n);
    for (int i = 1; i <= n; ++i) {
        cin >> vX[i - 1] >> vY[i - 1];
        vA[i] = {vX[i - 1], vY[i - 1]};
    }
    sort(vX.begin(), vX.end());
    sort(vY.begin(), vY.end());

     for (int i = 1; i <= n; ++i) {
        vA[i].first = (int) (lower_bound(vX.begin(), vX.end(), vA[i].first) - vX.begin()) + 1;
        vA[i].second = (int) (lower_bound(vY.begin(), vY.end(), vA[i].second) - vY.begin()) + 1;

        // Fake GET
        for (int j = vA[i].first - 1; j > 0; j -= (j & -j)) {
            node[j].push_back(vA[i].second);
        }

        // FAke update
        for (int j = vA[i].first; j <= n; j += (j & -j)) {
            node[j].push_back(vA[i].second);
        }
    }


    for (int i = 1; i <= n; ++i) {
        node[i].push_back(10000032);
        sort(node[i].begin(), node[i].end());
        node[i].erase(unique(node[i].begin(), node[i].end()), node[i].end());
        ft[i].resize(node[i].size() + 1, 0);
    }

    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        int x = vA[i].first, y = vA[i].second;
        int tmp = 1;
        for (int j = x - 1; j > 0; j -= (j & -j)) {
            int currentY = (int) (lower_bound(node[j].begin(), node[j].end(), y) - node[j].begin()) + 1;
            tmp = max(tmp, get(ft[j], currentY - 1) + 1);
        }
        for (int j = x; j <= n; j += (j & -j)) {
            int currentY = (int) (lower_bound(node[j].begin(), node[j].end(), y) - node[j].begin()) + 1;
            update(ft[j], currentY, tmp);
        }
        ans = max(ans, tmp);
        /* cerr << tmp << '\n'; */
    }
    cout << ans << '\n';

    return 0;
}
 
cài = segtree ăn TLE rồi :amazed: cài bằng cây fenwick thì ăn được

C++:
#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 3;

typedef pair <int, int> ii;

vector<int> node[N], ft[N];
ii vA[N];
int n;

void update(vector<int> &ft, int pos, int val) {
    for ( ; pos <= ft.size(); pos += (pos & -pos)) {
        ft[pos] = max(ft[pos], val);
    }
}

int get(vector<int> &ft, int pos) {
    int ans = 0;
    for ( ; pos > 0; pos -= (pos & -pos)) {
        ans = max(ans, ft[pos]);
    }
    return ans;
}

int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cin >> n;
    vector<int> vX(n), vY(n);
    for (int i = 1; i <= n; ++i) {
        cin >> vX[i - 1] >> vY[i - 1];
        vA[i] = {vX[i - 1], vY[i - 1]};
    }
    sort(vX.begin(), vX.end());
    sort(vY.begin(), vY.end());

     for (int i = 1; i <= n; ++i) {
        vA[i].first = (int) (lower_bound(vX.begin(), vX.end(), vA[i].first) - vX.begin()) + 1;
        vA[i].second = (int) (lower_bound(vY.begin(), vY.end(), vA[i].second) - vY.begin()) + 1;

        // Fake GET
        for (int j = vA[i].first - 1; j > 0; j -= (j & -j)) {
            node[j].push_back(vA[i].second);
        }

        // FAke update
        for (int j = vA[i].first; j <= n; j += (j & -j)) {
            node[j].push_back(vA[i].second);
        }
    }


    for (int i = 1; i <= n; ++i) {
        node[i].push_back(10000032);
        sort(node[i].begin(), node[i].end());
        node[i].erase(unique(node[i].begin(), node[i].end()), node[i].end());
        ft[i].resize(node[i].size() + 1, 0);
    }

    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        int x = vA[i].first, y = vA[i].second;
        int tmp = 1;
        for (int j = x - 1; j > 0; j -= (j & -j)) {
            int currentY = (int) (lower_bound(node[j].begin(), node[j].end(), y) - node[j].begin()) + 1;
            tmp = max(tmp, get(ft[j], currentY - 1) + 1);
        }
        for (int j = x; j <= n; j += (j & -j)) {
            int currentY = (int) (lower_bound(node[j].begin(), node[j].end(), y) - node[j].begin()) + 1;
            update(ft[j], currentY, tmp);
        }
        ans = max(ans, tmp);
        /* cerr << tmp << '\n'; */
    }
    cout << ans << '\n';

    return 0;
}
Liệu có thể làm thế này k fen. LIS theo chiều thứ nhất trước, ra 1 mảng LIS con. Sau đó làm LIS theo chiều thứ 2 trên mảng con này => Ra kết quả.
 

Thống kê chủ đề

Ngày tạo
unknowpc90,
Người trả lời cuối
Spaghetti Code,
Trả lời
1.460
Lượt xem
154.155
Quay lại
Lên đầu trang