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
e năm 1 bác ạ, vừa mới thi xong c, tự lần mò c++ biết mỗi mấy cái hàm với cái vector, map vớ vẩn @@, mấy cái auto gì kia e k biết. Nói chung là tuy là c++ mà e code như c thôi, chẳng khác tí gì :sweat:
Nếu vậy thì là tốt rồi. Ráng lên chú e. Có gì cần giúp đỡ thì cứ lên đây hỏi. :D
Cái auto đơn giản là để compiler nó tự suy luận ra kiểu của dữ liệu, dựa vào value mình init.
 
thế thím có hướng nào cho bài palindromic subsequence ko
Link test: LC
Sol:
C++:
class Solution {
public:
    int longestCommonSubseq(string& s1,string& s2){
        int n1 = s1.size(), n2 = s2.size();
        vector<vector<int>> dp(n1+1,vector<int>(n2+1));
        for(int i=1;i<=n1;i++){
            for(int j=1;j<=n2;j++){
                dp[i][j]=s1[i-1]==s2[j-1] ? dp[i-1][j-1] + 1 : max(dp[i][j-1],dp[i-1][j]);
            }
        }
        return dp[n1][n2];
    }
    int longestPalindromeSubseq(string s) {
        string rev = s;
        reverse(s.begin(),s.end());
        return longestCommonSubseq(s,rev);
    }
};
 
Vãi. Mới học mà làm được như thế này rồi à. Ghê vậy.

Huhuhu. Thấy bản thân yếu kém quá
9h30 có cuộc thi nhé fen, cố làm được bài 1
Link test: LC
Sol:
C++:
class Solution {
public:
    int longestCommonSubseq(string& s1,string& s2){
        int n1 = s1.size(), n2 = s2.size();
        vector<vector<int>> dp(n1+1,vector<int>(n2+1));
        for(int i=1;i<=n1;i++){
            for(int j=1;j<=n2;j++){
                dp[i][j]=s1[i-1]==s2[j-1] ? dp[i-1][j-1] + 1 : max(dp[i][j-1],dp[i-1][j]);
            }
        }
        return dp[n1][n2];
    }
    int longestPalindromeSubseq(string s) {
        string rev = s;
        reverse(s.begin(),s.end());
        return longestCommonSubseq(s,rev);
    }
};
hay fen, cần 1 lời chứng minh, tại sao string và restring lấy LCS là ra đáp án?
 
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:

#388. (Medium) https://leetcode.com/problems/longest-absolute-file-path/
#389. (Easy) https://leetcode.com/problems/find-the-difference/
#390. (Medium) https://leetcode.com/problems/elimination-game/
#391. (Hard) https://leetcode.com/problems/perfect-rectangle/
#392. (Easy) https://leetcode.com/problems/is-subsequence/

Mình sẽ share về bài Find the Difference:
#389. (Easy) https://leetcode.com/problems/find-the-difference/

Phân tích bài toán:
  • 0 <= s.length <= 1000: Tầm này thì chạy O(n^2) cũng được. Nhưng đáp án thì chỉ O(n) thôi.
  • Bài này easy nên cứ target trước trong đầu là solution đơn giản, không cần đi xa xôi
  • Chuỗi t được tạo ra bằng cách shuffle chuỗi s => Số lượng các ký tự của chuỗi t và s là giống nhau
  • Sau đó thêm một ký tự mới vào t => Đây là điểm khác biệt cần tìm
  • Vậy ta chỉ cần đếm các ký tự của s và của t, sau đó so sánh số lượng, ký tự nào khác biệt thì đó chính là kết quả cần tìm

Solution:
  • Đếm các ký tự của chuỗi s
  • Với mỗi ký tự của chuỗi t:
    • Trừ biến đếm của ký tự đó đi một
    • Nếu biến đếm nhỏ hơn 0 => Đó là ký tự cần tìm

Python:
class Solution:
    def findTheDifference(self, s: str, t: str) -> str:
        count = defaultdict(int)
        for ch in s:
            count[ch] += 1
         
        for ch in t:
            if count[ch] <= 0:
                return ch
            count[ch] -= 1
         
        return None

Note:
  • Đây là bài dùng map cơ bản, vì map cho phép truy xuất / thay đổi dữ liệu với độ phức tạp là O(1) nên rất phù hợp với những bài đếm dạng này
  • Nhưng vì chỉ có tối đa 26 ký tự, các bạn có thể dùng mảng thay vì dùng map để tối ưu cũng được
  • Solution có hơi khác so với kết quả của việc phân tích, nhưng ý tưởng vẫn giống nhau. Các bạn chỉ cần suy nghĩ tí là sẽ hiểu.
C++:
class Solution {
public:
    char findTheDifference(string s, string t) {
        int x=0,y=0;
    for(int i=0;i<s.length();i++){
        x+=(int)(s[i]);
    }
    for(int i=0;i<t.length();i++){
        y+=(int)(t[i]);
    }
        return (char)(y-x);
    }
};
xài c thuần, ý tưởng của em: chuyển hết kí tự trong 2 string về tổng int, trừ xem chênh bao nhiều, đổi cái chênh về char. :byebye: bài này sort rồi so sánh cũng ổn
 
Nếu vậy thì là tốt rồi. Ráng lên chú e. Có gì cần giúp đỡ thì cứ lên đây hỏi. :D
Cái auto đơn giản là để compiler nó tự suy luận ra kiểu của dữ liệu, dựa vào value mình init.
vâng bác, em giờ mới bắt đầu học. Giờ bắt đầu tìm hiểu con trỏ các thứ, mong được các bác chỉ giáo
 
bài dễ quá mà fen, sắp xếp từ lớn xuống bé xong lấy dần dần cho nó >1/2 sum là được
Đù. Giải được nè
1628347755983.png
Nhưng mà tại sao là sum /2 vậy fen?
 
Bài này cách đơn giản nhất là dùng greedy nhé. Lấy sum của nguyên dãy. Sau đó sort theo thứ tự giảm dần. Rồi lấy ra n phần tử đầu tiên > sum/2. Code như này:
C++:
class Solution {
public:
    vector<int> minSubsequence(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        sort(nums.begin(), nums.end(), greater());
     
        int sumSub = 0;
        auto it = nums.begin();
        while (sumSub <= sum/2){
            sumSub += *it;
            it++;
        }
        return vector(nums.begin(), it);
    }
};

Runtime: 10 ms, faster than 14.73% of C++ online submissions for Minimum Subsequence in Non-Increasing Order.

Cách này chưa được tối ưu lắm. Có tgian mình sẽ nghĩ cách tốt hơn. :D

Accumulate Sum là đúng rồi, thím chậm chắc do array copy qua lại thôi. Code bằng Go

Mã:
func minSubsequence(nums []int) []int {
    totalSum := calculateSum(nums)

    sort.Slice(nums, func(i,j int) bool{
        return nums[i] > nums[j]
    })
  
    accumulateSum := 0
    for i := range nums {
        accumulateSum += nums[i]
        if accumulateSum <= totalSum - accumulateSum {
            continue
        }
      
        return nums[0:i+1]
    }
  
    return nums
}


func calculateSum(nums []int) int {
    sum := 0
  
    for _, num := range nums {
        sum += num
    }
  
    return sum
}

Runtime: 3 ms, faster than 100.00% of Go online submissions for Minimum Subsequence in Non-Increasing Order.

Edit: Nếu dùng counting sort thì có thể nhanh hơn đấy. Vì số bị chặn ở range 1->100
 
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:

#419. (Medium) https://leetcode.com/problems/battleships-in-a-board/
#420. (Hard) https://leetcode.com/problems/strong-password-checker/
#421. (Medium) https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/
#423. (Medium) https://leetcode.com/problems/reconstruct-original-digits-from-english/

Mình sẽ share về bài Battleships in a Board:
#419. (Medium) https://leetcode.com/problems/battleships-in-a-board/

Phân tích bài toán:
  • 1 <= m, n <= 200: Với cái 2 chiều này thì cứ coi như n = m * n. Độ phức estimate có thể là O(n) hoặc O(nlogn)
  • there are no adjacent battleships: Đây là một điều kiện quan trọng ẩn dấu trong đề mà ta phải nhận ra. Vì nếu các con tàu dính vào nhau thì rất khó / không thể đếm chính xác được
  • Vấn đề của bài toán là mỗi con tàu có kích thước khác nhau và không biết trước. Nên với nhiều ô 'X' có thể thuộc về chung một con tàu.
  • Nhưng vì các còn tàu đều rời nhau, nên bao quanh con tàu chắc chắn là khoảng trống (ô '.')
  • Vậy làm thế nào để ta gom các ô 'X' lại với nhau để biết chúng thuộc về chung một con tàu ?
  • Theo đề, 2 ô nằm kề nhau mà cùng bằng 'X' thì chúng thuộc về chung một con tàu.
  • Đến đây, nếu tưởng tượng matrix là một đồ thị, ta có thể dùng thuật toán đếm miền liên thông để giải bài này với độ phức tạp O(n)
  • Nhưng nếu phân tích kĩ hơn, vì các con tàu đều rời nhau nên:
    • Một ô 'X' thuộc phần thân tàu chắc chắn phải dính với một ô 'X' khác trước đó. Tùy vào con tàu nằm ngang hoặc nằm dọc.
    • Ngoại trừ ô đầu tàu, sẽ không có ô 'X' nào nằm trước kề với nó cả.
  • Vậy ta có thể đếm số con tàu bằng cách đếm các ô đầu tàu

Solution:
  • Lặp qua từng ô trong matrix
  • Kiểm tra nếu đó là ô đầu tàu (bằng 'X' và không có ô bên trên hoặc bên trái kề với nó bằng 'X')
    • Tăng biến đếm lên một

Python:
class Solution:
    def countBattleships(self, board: List[List[str]]) -> int:
        m, n = len(board), len(board[0])
        count = 0
        for r in range(m):
            for c in range(n):
                if board[r][c] == 'X' and ((r == 0) or board[r - 1][c] != 'X') and ((c == 0) or board[r][c - 1] != 'X'): # đây là ô đầu tàu
                    count += 1
                   
        return count
 
Accumulate Sum là đúng rồi, thím chậm chắc do array copy qua lại thôi. Code bằng Go

Mã:
func minSubsequence(nums []int) []int {
    totalSum := calculateSum(nums)

    sort.Slice(nums, func(i,j int) bool{
        return nums[i] > nums[j]
    })
 
    accumulateSum := 0
    for i := range nums {
        accumulateSum += nums[i]
        if accumulateSum <= totalSum - accumulateSum {
            continue
        }
     
        return nums[0:i+1]
    }
 
    return nums
}


func calculateSum(nums []int) int {
    sum := 0
 
    for _, num := range nums {
        sum += num
    }
 
    return sum
}

Runtime: 3 ms, faster than 100.00% of Go online submissions for Minimum Subsequence in Non-Increasing Order.

Edit: Nếu dùng counting sort thì có thể nhanh hơn đấy. Vì số bị chặn ở range 1->100
T thử lại rồi. Cùng 1 solution đó mà lúc chỉ beats 3%, lúc thì beats 90%, :D . Chắc có thể do testcase k đủ lớn, nên nhiều khi tùy vào work load của hệ thống bên leetcode mà nó chạy nhanh chậm khác nhau.
 
Thấy mấy bác toàn dùng C/C++ để giải mấy bài toán.
Tại do mấy bác thích như vậy hay là C/C++ có gì đó đặc biệt nên giải mấy bài thuật toán sẽ tốt hơn vậy ?
 
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:

#424. (Medium) https://leetcode.com/problems/longest-repeating-character-replacement
#427. (Medium) https://leetcode.com/problems/construct-quad-tree/
#429. (Medium) https://leetcode.com/problems/n-ary-tree-level-order-traversal/
#430. (Medium) https://leetcode.com/problems/flatten-a-multilevel-doubly-linked-list/

Mình sẽ share về bài N-ary Tree Level Order Traversal:
#429. (Medium) https://leetcode.com/problems/n-ary-tree-level-order-traversal/

Phân tích bài toán:
  • Cũng không có gì nhiều để nói, đây là bài BFS kinh điển rồi.
  • Các bạn có thể tự google về BFS DFS. Ở đây mình xin giới thiệu 1 link youtube về BFS mà mình thấy khá hay:
  • Ngoài BFS, ta cũng có thể làm cách khác là construct theo từng level. Các bạn có thể tự implement.

Solution:
  • Nếu root node bằng null thì return mảng rỗng luôn
  • Khởi tạo queue với 1 element là root node
  • Lặp cho đến khi vẫn còn queue
    • Lấy một node ra khỏi queue
    • Thêm nó vào mảng output tại vị trí tương ứng
    • Thêm các node con của nó vào queue

Python:
class Solution:
    def levelOrder(self, root: 'Node') -> List[List[int]]:
        q = deque([(root, 0)])
        res = []
        while q:
            node, level = q.popleft()
            if not node:
                continue
               
            while level >= len(res):
                res.append([])
               
            res[level].append(node.val)
            for child in node.children:
                q.append((child, level + 1))
       
        return res
 
Xem tệp đính kèm 697828
Đang học LCS, topic có bài này, áp dụng như nào nhỉ,

Trước đi phỏng vấn gặp bài này, nhìn đoán là qhđ nhưng méo biết làm thế là làm đại brute force, 2 vòng for từ 0-n và 0-i, rồi thằng pv hỏi vài câu cũng chém đại là qhđ phải dùng nhiều mem mà performance cũng ko cải thiện nhiều. Cuối cùng rớt cmnl =((
 
Cuối cùng cũng tìm ra mã nguồn cho LIS

C++:
int n;
vector<int> A, p;// predecessor array

void print_LIS(int i) {                          // backtracking routine
    if (p[i] == -1) {
        cout << A[i] << " ";
        return;
    }// base case
    print_LIS(p[i]);                               // backtrack
    cout << A[i] << " ";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    // note: A[n] must be set as the largest value ("INF")
    // so that all LIS (that can start anywhere) will end at n
    cin >> n;
    A.assign(n + 1, 0);
    for (int i = 0; i < n; ++i) {
        cin >> A[i];
    }
    A[n] = INF;                                   // set A[n] = INF

    cout << "n = " << n << ": ";
    for (int i = 0; i < n; ++i)
        cout << A[i] << " ";
    cout << "\n";

    int k = 0, lis_end = 0;
    vi L(n, 0), L_id(n, 0);
    p.assign(n + 1, -1);

    for (int i = 0; i < n; ++i) {                  // O(n)
        int pos = lower_bound(L.begin(), L.begin() + k, A[i]) - L.begin();
        L[pos] = A[i];                               // greedily overwrite this
        L_id[pos] = i;                               // remember the index too
        p[i] = pos ? L_id[pos - 1] : -1;               // predecessor info
        if (pos == k) {                              // can extend LIS?
            k = pos + 1;                                 // k = longer LIS by +1
            lis_end = i;                               // keep best ending i
        }
    }

    cout << "Final LIS is of length " << k << ": ";
    print_LIS(lis_end);

    return 0;
}
 
Cuối cùng cũng tìm ra mã nguồn cho LIS

C++:
int n;
vector<int> A, p;// predecessor array

void print_LIS(int i) {                          // backtracking routine
    if (p[i] == -1) {
        cout << A[i] << " ";
        return;
    }// base case
    print_LIS(p[i]);                               // backtrack
    cout << A[i] << " ";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    // note: A[n] must be set as the largest value ("INF")
    // so that all LIS (that can start anywhere) will end at n
    cin >> n;
    A.assign(n + 1, 0);
    for (int i = 0; i < n; ++i) {
        cin >> A[i];
    }
    A[n] = INF;                                   // set A[n] = INF

    cout << "n = " << n << ": ";
    for (int i = 0; i < n; ++i)
        cout << A[i] << " ";
    cout << "\n";

    int k = 0, lis_end = 0;
    vi L(n, 0), L_id(n, 0);
    p.assign(n + 1, -1);

    for (int i = 0; i < n; ++i) {                  // O(n)
        int pos = lower_bound(L.begin(), L.begin() + k, A[i]) - L.begin();
        L[pos] = A[i];                               // greedily overwrite this
        L_id[pos] = i;                               // remember the index too
        p[i] = pos ? L_id[pos - 1] : -1;               // predecessor info
        if (pos == k) {                              // can extend LIS?
            k = pos + 1;                                 // k = longer LIS by +1
            lis_end = i;                               // keep best ending i
        }
    }

    cout << "Final LIS is of length " << k << ": ";
    print_LIS(lis_end);

    return 0;
}
@thuyduong2007 @_Gia_Cat_Luong_ @clonemasteruwu
cái code kia là dành cho bài strict increase, nếu ko mà ko strict thì sửa code như nào vậy mấy fen
 

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.147
Quay lại
Lên đầu trang