thảo luận Leetcode mỗi ngày

Bác làm 400 bài Leetcode. Vậy chắc qua Google làm được rồi còn gì.
Trước em hẹn lịch pv vòng 2 (vòng phone thuật toán) với 1 recruiter google mà có vẻ bị nó bùng, đến ngày thì nó bảo submit cv trên trang career của google.

Phỏng vấn thì cần kỹ năng giải thích tường tận (bằng tiếng anh) thì mới ghi điểm được. Em làm contest đôi khi cũng làm được 4 bài, đủ thời gian, nhưng vừa làm vừa giải thích bảo vệ cách của mình thì khó, lại không được submit xem đúng hay sai.
 
30% thì nhiều quá, mình thấy đếm trên đầu ngón tay các công ty có 1 vòng thuật toán.

Như big tech thì là 50% đến 75% vòng cuối, trước đấy có 1 vòng nữa cũng thuật toán. Tất cả để kiểm tra tư duy của 1 engineer

Nhiều công ty chỉ đào sâu đã làm gì, muốn ứng viên thạo việc, nhớ dai và code như 1 cái máy.
Ý là kết quả của vòng thuật toán, chiếm không đến 30% kết quả chung của quá trình phỏng vấn. Nhưng newbie hay focus quá vào thuật toán vì vòng này mang tính sàng lọc, không làm được = toang.
 
Ý là kết quả của vòng thuật toán, chiếm không đến 30% kết quả chung của quá trình phỏng vấn. Nhưng newbie hay focus quá vào thuật toán vì vòng này mang tính sàng lọc, không làm được = toang.
Thì ý em là 90% công ty thuật toán chiếm 0% kết quả, hoặc chỉ cho vài câu rất dễ để sàng lọc, không cần học gì cũng qua.
Còn 1 số công ty có vòng thuật toán kiểu bài leetcode như axon, teko,…
1 số thì chỉ hỏi lý thuyết, cũng là nhớ dai chứ chả đánh giá được gì.
Theo kinh nghiệm cá nhân thôi.
 
Ý là kết quả của vòng thuật toán, chiếm không đến 30% kết quả chung của quá trình phỏng vấn. Nhưng newbie hay focus quá vào thuật toán vì vòng này mang tính sàng lọc, không làm được = toang.
Mình pvan trước giờ cũng gần 9 công ty, đi làm 3 công ty, trong đó hầu hết 7 công ty là phỏng vấn live coding trên máy và viết trên bảng, cãi lộn với interviewers (basic app + algo), từ easy tới medium, tới hard, còn lại 2 công ty chỉ hỏi chung chung (3, 4 năm trc)
Nhưng công ty mà đi làm lâu nhất thì là công ty pvan hỏi nhiều algo nhất mà làm thì ít algo nhất, còn mấy cty còn lại thì gần như k hỏi nhiều algo, vô làm toàn ae tự train nhau, tính năng quan trọng hơn chất lượng =((
 
trong related topic có union find nên em cài đặt thử
C++:
class Solution {
public:
    inline int findParent(vector<int> &p, int i) {
        while (p[i] != -1) {
            i = p[i];
        }
        return i;
    }
 
    int longestConsecutive(vector<int>& nums) {
        unordered_map<int, int> m;
        vector<int> l(nums.size(), 1), p(nums.size(), -1); // l[i] is length of CS from nums[i], p[i] is index of parent of index i
        int ans = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (m.find(nums[i]) == m.end()) {
                m[nums[i]] = i;
                l[i] = 1;
           
                if (m.find(nums[i] + 1) != m.end()) {
                    p[m[nums[i] + 1]] = i;
                    l[i] += l[m[nums[i] + 1]];
                }
           
                ans = max(ans, l[i]);
           
                if (m.find(nums[i] - 1) != m.end()) {
                    int p0 = findParent(p, m[nums[i] - 1]);
                    p[i] = p0;
                    l[p0] += l[i];
                    ans = max(ans, l[p0]);
                }
            }
        }
   
        return ans;
    }
};
Python:
from typing import List


class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        nums, prev = set(nums), dict()

        def find(n):
            if n in prev and prev[n] != n-1:
                return prev[n]
            if n-1 in nums:
                prev[n] = find(n-1)
                return prev[n]
            else:
                return n-1

        best = 0
        for i in nums:

            best = max(best, i - find(i))
            print("i: {}, prev: {}".format(i, prev))

        return best


nums = [100, 4, 200, 1, 3, 2]
s = Solution()
s.longestConsecutive(nums)

Code:
i: 1, prev: {}
i: 2, prev: {2: 0}
i: 3, prev: {2: 0, 3: 0}
i: 100, prev: {2: 0, 3: 0}
i: 4, prev: {2: 0, 3: 0, 4: 0}
i: 200, prev: {2: 0, 3: 0, 4: 0}


Bác giải thích cho em về cái Union sao nó lại hoạt động vậy, trên đây là thuật toán tinh giản, em copy được ở discussion.

Nó chỉ bao gồm mỗi một function find và một dict tên là prev. Nhưng lúc print các kết quả prev ra thì thấy không đúng với cái tên của nó nhỉ. Ví dụ prev của 4 lại ra 0, prev của 3 cũng là 0, prev của 2 cũng là 0:misdoubt::oh:. Vì prev thì 4:3, 3:2, 2:1 mới hợp lí chứ nhỉ
 
Ko biết gọi phương pháp tên gì, nhưng nghĩ ra trong đầu thế nào thì code vậy.
Ý tưởng là dùng unordered map với các dãy số liền nhau (1,2,3,4) thì 2 biên (1,4) sẽ lưu độ dài của dãy (4). Duyệt từng phần tử 1 của mảng, nếu có trong map rồi thì bỏ qua, nếu cận trái m[n-1] và cận phải m[n+1] có giá trị thì cập nhật lại giá trị 2 biên. Tính max các giá trị biên và trả về. Code nợ tí edit bù.

int longestConsecutive(const vector<int> &nums) {
unordered_map<int, int> m;
int result = 1;
for (auto n : nums) {
if (m.find(n) != m.end())
continue;
auto hasLeft = m.find(n - 1) != m.end();
auto hasRight = m.find(n + 1) != m.end();
m[n] = 1;
if (hasLeft && hasRight) {
auto range = 1 + m[n - 1] + m[n + 1];
result = max(result, range);
m[n - m[n - 1]] = range;
m[n + m[n + 1]] = range;
continue;
}
if (hasLeft) {
m[n] = m[n - 1] + 1;
m[n - m[n] + 1] = m[n];
result = max(result, m[n]);
continue;
}
if (hasRight) {
m[n] = m[n + 1] + 1;
m[n + m[n] - 1] = m[n];
result = max(result, m[n]);
}
}
return result;
}
 
Last edited:
Python:
from typing import List


class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        nums, prev = set(nums), dict()

        def find(n):
            if n in prev and prev[n] != n-1:
                return prev[n]
            if n-1 in nums:
                prev[n] = find(n-1)
                return prev[n]
            else:
                return n-1

        best = 0
        for i in nums:

            best = max(best, i - find(i))
            print("i: {}, prev: {}".format(i, prev))

        return best


nums = [100, 4, 200, 1, 3, 2]
s = Solution()
s.longestConsecutive(nums)

Code:
i: 1, prev: {}
i: 2, prev: {2: 0}
i: 3, prev: {2: 0, 3: 0}
i: 100, prev: {2: 0, 3: 0}
i: 4, prev: {2: 0, 3: 0, 4: 0}
i: 200, prev: {2: 0, 3: 0, 4: 0}


Bác giải thích cho em về cái Union sao nó lại hoạt động vậy, trên đây là thuật toán tinh giản, em copy được ở discussion.

Nó chỉ bao gồm mỗi một function find và một dict tên là prev. Nhưng lúc print các kết quả prev ra thì thấy không đúng với cái tên của nó nhỉ. Ví dụ prev của 4 lại ra 0, prev của 3 cũng là 0, prev của 2 cũng là 0:misdoubt::oh:. Vì prev thì 4:3, 3:2, 2:1 mới hợp lí chứ nhỉ
yes, tên sai đó. Thường thì tên là parent.
 
Python:
from typing import List


class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        nums, prev = set(nums), dict()

        def find(n):
            if n in prev and prev[n] != n-1:
                return prev[n]
            if n-1 in nums:
                prev[n] = find(n-1)
                return prev[n]
            else:
                return n-1

        best = 0
        for i in nums:

            best = max(best, i - find(i))
            print("i: {}, prev: {}".format(i, prev))

        return best


nums = [100, 4, 200, 1, 3, 2]
s = Solution()
s.longestConsecutive(nums)

Code:
i: 1, prev: {}
i: 2, prev: {2: 0}
i: 3, prev: {2: 0, 3: 0}
i: 100, prev: {2: 0, 3: 0}
i: 4, prev: {2: 0, 3: 0, 4: 0}
i: 200, prev: {2: 0, 3: 0, 4: 0}


Bác giải thích cho em về cái Union sao nó lại hoạt động vậy, trên đây là thuật toán tinh giản, em copy được ở discussion.

Nó chỉ bao gồm mỗi một function find và một dict tên là prev. Nhưng lúc print các kết quả prev ra thì thấy không đúng với cái tên của nó nhỉ. Ví dụ prev của 4 lại ra 0, prev của 3 cũng là 0, prev của 2 cũng là 0:misdoubt::oh:. Vì prev thì 4:3, 3:2, 2:1 mới hợp lí chứ nhỉ
cái prev\[i\] chính là số đầu tiên trong dãy liên tiếp đó bác (prev\[i\]...i] -> độ dài của dãy là i - prev\[i\]. tại mỗi bước họ tìm xem số đầu tiên của dãy tăng mà kết thúc là i là bao nhiêu, i này tìm trong set
 
Cách 1: dùng hash map.

  • Duyệt từ đầu đến cuối
  • dùng hashmap để đánh dấu
  • Kiểm tra xem a[i.] + 1 và a[i.] - 1 đã có trong hashmap chưa. Nếu đã có thì nối các chuỗi lại với nhau

Độ phức tạp: O(n) time, O(n) space

C++:
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        std::unordered_map<int, int> len;
       
        int maxl = 0;
       
        for (auto i : nums) {
                      
            auto ii = &len[i];
           
            if (*ii > 0) continue;
            auto l = (*ii = 1);
            int *s_end = ii, *s_begin = ii;
           
            if (auto it = len.find(i+1); it != end(len)) {
                s_end = &((it->second == 1) ? it : len.find(i + it->second))->second;
                l += it->second;
            };
           
            if (auto it = len.find(i-1); it != end(len)) {
                s_begin = &((it->second == 1) ? it : len.find(i - it->second))->second;
                l += it->second;
            };
           
            *s_begin = *s_end = l;
           
            if (l > maxl) maxl = l;
        }
       
        return maxl;
    }
};

auto _ = [](){
    cin.sync_with_stdio(false);
    cout.sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    return 0;
}();

Cách 2: dùng radix sort
  • Cộng tất cả các số với 2^30 để biến thành dãy số dương, xử lý vấn đề khi radix sort,
  • Radix sort với radix là 65536, do input < 2^32 nên cần 2 phase,
  • Đếm
Độ phức tạp: O(n log n / log 65536 + 65536) ~ O(n) time (vì n trong khoảng -10^9 - 10^9).
O(10^5 + 65536) space để lưu mảng counting và mảng tạm.

C++:
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        if (nums.empty()) return 0;
       
        std::transform(begin(nums), end(nums), begin(nums), [](int i) { return i + (1<<30); });
        countSort0(nums);
        countSort1(nums);

        //std::cin.get();

        int maxl = 0;
        auto sz = nums.size();
        auto end = std::begin(output) + sz;
        auto it = begin(output);
        while(it != end) {

            auto it2 = it + 1;
            auto l = 1;
            while (it2 != end) {
                auto d = *it2 - *(it2 - 1);
                if (d == 1) ++l;
                else if(d > 1) break;
                ++it2;
            }

            if (l > maxl)
                maxl = l;
            it = it2;
        }
        return maxl;
    }

private:
    // count sort for 16 lsd bits
    void countSort0(vector<int>& a) {
        std::memset(count.data(), 0, count.size() * sizeof(int));
        for (auto i : a) ++count[i & 0xffff];
        for (int i = 1; i < 65536; ++i) count[i] += count[i-1];
        for (auto it = a.rbegin(); it != a.rend(); ++it) {
            output[--count[*it & 0xffff]] = *it;
        }
        std::memcpy(a.data(), output.data(), sizeof(int) * a.size());
    }

    // count sort for 16 msd bits
    void countSort1(vector<int>& a) {
        std::memset(count.data(), 0, count.size() * sizeof(int));
        for (auto i : a) ++count[(i >> 16) & 0xffff];
        for (int i = 1; i < 65536; ++i) count[i] += count[i-1];
        for (auto it = a.rbegin(); it != a.rend(); ++it) {
            output[--count[(*it >> 16) & 0xffff]] = *it;
        }
    }


    static std::array<int, 65536> count;
    static std::array<int, 100'000> output;
};

std::array<int, 65536> Solution::count{};
std::array<int, 100'000> Solution::output{};

auto _ = [](){
    std::cin.sync_with_stdio(false);
    std::cout.sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    return 0;
}();

Thử submit và tối ưu sơ thì thấy radix nhanh hơn, beat 98%.

1657006593673.png
 
Radix và Counting sort chỉ cho non negative integers thôi bác, còn các loại khác là general
O(n) < O(nlogn) chỉ đúng khi n đủ lớn. Và trong case sort này thì n đủ lớn chắc phải tầm 10^9 trở lên. Ít khi nào có test case được to vậy, nên dùng radix sort thì chỉ để ăn gian O(n) lý thuyết thôi chứ runtime hầu như là chậm hơn built-in sort.
- Radix sort có thể dùng cho nhiều kiểu khác nữa. Chỉ cần đảm bảo điều kiện: gồm nhiều trường integer, các trường có độ ưu tiên cho trước.
Cho nên kể cả số thực dấu phẩy động (float, double), số âm, ... đều có thể dùng được radix sort.
VD với số float 32 bit thì bản chất là nó tạo nên từ 3 integer với độ ưu tiên theo thứ tự sign (bit 0), exponent (bit 1-8), mantissa (bit 9 - 31). Sign thì ngược lại chút vì nó bằng 0 với số dương và 1 với số âm, nhưng vẫn xử lý riêng được.

- Mình đã thử test với đầu vào cỡ 10^8 10^9 thì radix sort nếu chọn base hợp lý sẽ luôn nhanh hơn quick sort hay std::sort.
 
Mình sort mảng xong rồi duyệt. Vì chuỗi tăng dần nên nếu số sau mà nhiều hơn số trước 2 đơn vị thì reset lại count, return count lớn nhất là xong. :byebye:
C#:
 public int LongestConsecutive(int[] nums) {
        if(nums.Length <= 1) return nums.Length;
        Array.Sort(nums);
        int result = 0;
        int count = 1;
        for(int i = 0; i < nums.Length - 1; i++){
            if(nums[i+1] == nums[i] + 1){
                count++;
            }
            if(nums[i+1] > nums[i] + 1){
                count = 1;
            }
            result = Math.Max(result, count);
        }
        return result;
    }
1657008602828.png
 
Cách 1: dùng hash map.

  • Duyệt từ đầu đến cuối
  • dùng hashmap để đánh dấu
  • Kiểm tra xem a[i.] + 1 và a[i.] - 1 đã có trong hashmap chưa. Nếu đã có thì nối các chuỗi lại với nhau

Độ phức tạp: O(n) time, O(n) space

C++:
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        std::unordered_map<int, int> len;
      
        int maxl = 0;
      
        for (auto i : nums) {
                     
            auto ii = &len[i];
          
            if (*ii > 0) continue;
            auto l = (*ii = 1);
            int *s_end = ii, *s_begin = ii;
          
            if (auto it = len.find(i+1); it != end(len)) {
                s_end = &((it->second == 1) ? it : len.find(i + it->second))->second;
                l += it->second;
            };
          
            if (auto it = len.find(i-1); it != end(len)) {
                s_begin = &((it->second == 1) ? it : len.find(i - it->second))->second;
                l += it->second;
            };
          
            *s_begin = *s_end = l;
          
            if (l > maxl) maxl = l;
        }
      
        return maxl;
    }
};

auto _ = [](){
    cin.sync_with_stdio(false);
    cout.sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    return 0;
}();

Cách 2: dùng radix sort
  • Cộng tất cả các số với 2^30 để biến thành dãy số dương, xử lý vấn đề khi radix sort,
  • Radix sort với radix là 65536, do input < 2^32 nên cần 2 phase,
  • Đếm
Độ phức tạp: O(n log n / log 65536 + 65536) ~ O(n) time (vì n trong khoảng -10^9 - 10^9).
O(10^5 + 65536) space để lưu mảng counting và mảng tạm.

C++:
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        if (nums.empty()) return 0;
      
        std::transform(begin(nums), end(nums), begin(nums), [](int i) { return i + (1<<30); });
        countSort0(nums);
        countSort1(nums);

        //std::cin.get();

        int maxl = 0;
        auto sz = nums.size();
        auto end = std::begin(output) + sz;
        auto it = begin(output);
        while(it != end) {

            auto it2 = it + 1;
            auto l = 1;
            while (it2 != end) {
                auto d = *it2 - *(it2 - 1);
                if (d == 1) ++l;
                else if(d > 1) break;
                ++it2;
            }

            if (l > maxl)
                maxl = l;
            it = it2;
        }
        return maxl;
    }

private:
    // count sort for 16 lsd bits
    void countSort0(vector<int>& a) {
        std::memset(count.data(), 0, count.size() * sizeof(int));
        for (auto i : a) ++count[i & 0xffff];
        for (int i = 1; i < 65536; ++i) count[i] += count[i-1];
        for (auto it = a.rbegin(); it != a.rend(); ++it) {
            output[--count[*it & 0xffff]] = *it;
        }
        std::memcpy(a.data(), output.data(), sizeof(int) * a.size());
    }

    // count sort for 16 msd bits
    void countSort1(vector<int>& a) {
        std::memset(count.data(), 0, count.size() * sizeof(int));
        for (auto i : a) ++count[(i >> 16) & 0xffff];
        for (int i = 1; i < 65536; ++i) count[i] += count[i-1];
        for (auto it = a.rbegin(); it != a.rend(); ++it) {
            output[--count[(*it >> 16) & 0xffff]] = *it;
        }
    }


    static std::array<int, 65536> count;
    static std::array<int, 100'000> output;
};

std::array<int, 65536> Solution::count{};
std::array<int, 100'000> Solution::output{};

auto _ = [](){
    std::cin.sync_with_stdio(false);
    std::cout.sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    return 0;
}();

Thử submit và tối ưu sơ thì thấy radix nhanh hơn, beat 98%.

View attachment 1247784
65536 này lấy ở đâu vậy ạ, em thấy radix sort mô tả là sort trên chữ số từ phải sang trái. Cách cài đặt của bác hơi khác thì phải
 
65536 này lấy ở đâu vậy ạ, em thấy radix sort mô tả là sort trên chữ số từ phải sang trái. Cách cài đặt của bác hơi khác thì phải
65536 = 2^16.
Cũng là từ phải sang trái thôi: 16 bit bên phải rồi đến 16 bit bên trái.
 
bài này có thể quy về 1 bài đồ thị căn bản, nếu coi nó là 1 đồ thị thì ngoài DSU thì có thể dùng DFS+DP. Tất nhiên là không tối ưu lắm vì chạy thừa thãi nhiều, TC thực chất là O(2*n).
C++:
class Solution {
public:
    unordered_map<int,bool>vis;
    unordered_map<int,bool>have;
    unordered_map<int,int>dp;
    void dfs(int u,int x){
        vis[u]=true;
        if(have[u-1]){
            if(vis[u-1]) dp[x]+=dp[u-1];
            else{
                dp[x]++;
                dfs(u-1,x);
            }
        }
    }
    int longestConsecutive(vector<int>& nums) {
        for(auto x:nums) have[x]=true;
        for(auto x:nums){
            if(!vis[x]){
                dp[x]=1;
                dfs(x,x);
            }
        }
        int res=0;
        for(auto x:nums) res=max(res,dp[x]);
        return res;
    }
};
 
Last edited:
Nay chả có gì làm nên code C++ để chạy cho nhanh :shame:

1657066647284.png

https://leetcode.com/submissions/detail/739569272/

C++:
class Solution {
public:
    int fibo[31] = {
        0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55,
        89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765,
        10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040
    };
    int fib(int n) {
        return fibo[n];
    }
};
 
Back
Top