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

C++:
#include <iostream>
#include <string>
#include <stack>

using namespace std;

int main()
{
    string s = "a+b(x-2)-3((a+1)+(y-2))";
    cout <<"hello vozer"<<endl;
 
    stack< int > st;
 
    for(int i = 0; i < s.size(); i++) {
        if(s[i] == '(') {
            st.push(i);
        }
        else if(s[i] == ')') {
            if(st.empty()) {
                cout <<"-1"<<endl;
                return 0;
            } else {
                int x = st.top();
                st.pop();
                cout << s.substr(x,i-x+1) <<endl;
            }
        }
    }
    return 0;
}

Sơ là vậy, tự sửa lại cho đúng yêu cầu đi...
 
C++:
#include <iostream>
#include <string>
#include <stack>

using namespace std;

int main()
{
    string s = "a+b(x-2)-3((a+1)+(y-2))";
    cout <<"hello vozer"<<endl;
 
    stack< int > st;
 
    for(int i = 0; i < s.size(); i++) {
        if(s[i] == '(') {
            st.push(i);
        }
        else if(s[i] == ')') {
            if(st.empty()) {
                cout <<"-1"<<endl;
                return 0;
            } else {
                int x = st.top();
                st.pop();
                cout << s.substr(x,i-x+1) <<endl;
            }
        }
    }
    return 0;
}

Sơ là vậy, tự sửa lại cho đúng yêu cầu đi...
Bác này ngầu v~
 
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:

#384. (Medium) https://leetcode.com/problems/shuffle-an-array/
#385. (Medium) https://leetcode.com/problems/mini-parser/
#386. (Medium) https://leetcode.com/problems/lexicographical-numbers/
#387. (Easy) https://leetcode.com/problems/first-unique-character-in-a-string/

Mình sẽ share về bài Lexicographical Numbers:
#386. (Medium) https://leetcode.com/problems/lexicographical-numbers/

Phân tích bài toán:
  • Lần này k cần phân tích constraint. Trong đề đã chỉ rõ luôn là yêu cầu độ phức tạp O(n) cho time và O(1) cho space
  • Dễ thấy đây là bài toán liệt kê, để giải bài này thì thường dùng 2 thuật toán cơ bản là "Phương pháp sinh" và "Thuật toán quay lui"
  • Bài này có thể giải bằng cả 2 cách, ở đây mình sẽ minh họa bằng Phương pháp sinh

Một chút về Phương pháp sinh, thuật toán này có pattern đơn giản như sau:
Code:
<Khởi tạo cấu hình đầu tiên>
while True:
    <In ra / lưu lại cấu hình đang có>
    if <đây là cấu hình cuối cùng>:
        break
    <sinh cấu hình tiếp theo từ cấu hình hiện tại>
  • Điểm cốt lõi của phương pháp này là từ một cấu hình, ta suy ra được cấu hình tiếp theo
  • Điều này mang theo ngầm định rằng các cấu hình là có thứ tự, và ta dễ dàng xác định được cấu hình đầu tiên cũng như cuối cùng

Quay lại bài toán, để áp dụng pp sinh, ta cần xác định:
  • Cấu hình ở đây là gì: Rõ ràng đó là các con số, từ 1 -> n
  • Thứ tự của cấu hình: Là thứ tự từ điển, theo như yêu cầu bài toán
  • Cấu hình đầu tiên: Số 1
  • Cấu hình cuối cùng: .... chưa biết
  • Cách thức sinh cấu hình tiếp theo: dĩ nhiên cũng là chưa biết

  • Vậy để giải bài này bằng phương pháp sinh, ta cần giải quyết 2 vấn đề:
    • Cấu hình cuối cùng
    • Cách thức sinh cấu hình tiếp theo
  • Về cấu hình cuối cùng: Vì output chắc chắn có n phần tử, nên thay vì tìm cấu hình cuối cùng, ta có thể đơn giản check khi nào tạo đủ n phần tử là được. Đỡ mệt não.
Solution:
Vậy bài toán chính làm thế nào để tính ra số tiếp theo từ số hiện tại, cách dễ nhất vẫn là run các ví dụ, nhận ra pattern rồi tổng quát hóa thành công thức:
Code:
Giả sử n = 1234, ta thấy đoạn đầu chuỗi biến đổi như sau:
1 => 10 => 100 => 1000 => 1001 => 1002 => 1003 => 1004...
  • Ở đây ta nhận thấy có 2 pattern chính
    • Nhân 10
    • Cộng thêm 1
  • Để đưa được thành công thức, ta cần suy nghĩ xem tại sao có những pattern này, và khi nào thì dùng cái nào
  • Vì ta đang muốn sort theo thứ tự từ điển, nên với một số x bất kì, số tiếp theo chắc chắn là x nối thêm số 0 vào đuôi, tương ứng với việc nhân 10.
  • Nhưng không thể nhân 10 mãi, vì ta có chặn trên là số n, nếu nhân 10 mà lớn hơn n thì ta không nhân thêm được nữa
  • Thay vì thế lúc này ta cộng thêm một, dễ thấy là vì nếu không nối dài thêm được, thì số tiếp theo chính là số đó cộng thêm một. Theo thứ tự toán học bình thường.
  • Đến đây, bạn nào giỏi có thể tiếp tục suy nghĩ về các pattern tiếp theo. Còn mình thường thì cứ code rồi run example, xem sai ở chỗ nào thì từ chỗ đó đoán ra pattern tiếp theo:
Python:
class Solution:
    def lexicalOrder(self, n: int) -> List[int]:
        res = []
        k = 1 # Cấu hình đầu tiên
        while len(res) < n:
            res.append(k)
            k = self.nextNumber(k, n)
             
        return res
 
    def nextNumber(self, k, n):
        if 10 * k <= n:
            return 10 * k
     
        return k + 1

Chạy thử với n = 1234. Kết quả dĩ nhiên là sai rồi, nhưng ta nhìn kĩ xem sai ở đâu:
Code:
Output: ...1009 => 1010 => 1011....
Expected: ...1009 => 101 => 1010 => 1011....
  • Ơ tự dưng lại có số 101 nhảy vào giữa nhỉ ? Nhưng ngẫm lại thì đúng rồi, theo thứ tự từ điển thì 1009 < 101 < 1010
  • Vậy làm thế nào ta biết được khi nào sẽ có mấy con số vô duyên này chen vào giữa ?
  • Nhìn kĩ thì có vẻ vấn đề nằm ở con số 9 cuối cùng. Nhận thấy nếu một số kết thúc bằng số 9, thì số tiếp theo sẽ bị chen vào giữa. Tại sao ?
  • Vì 9 + 1 = 10, nên khi +1 sẽ làm thay đổi số phía trước, đồng thời reset số cuối cùng thành 0. Mà theo thứ tự từ điển, ta thấy số abc9 < ab(c+1) < ab(c+1)0
  • À, vậy pattern tiếp theo là khi gặp một số kết thúc bằng số 9, thì ta bỏ số 9 đó đi rồi +1 vào đuôi
  • Nó cũng sẽ tự động đúng cho số tiếp theo luôn, vì số nhỏ sau đó sẽ được nhân 10 để tạo thành số tiếp theo với số 0 ở cuối.
  • Ok. Vậy implement logic này rồi chạy lại nào
Python:
    def nextNumber(self, k, n):
        if 10 * k <= n:
            return 10 * k
     
        if k % 10 == 9:
            k //= 10
         
        return k + 1

Chỗ sai tiếp theo ?
Code:
Output: ...1233 => 1234 => 1235 => 1236....
Expected: ...1233 => 1234 => 124 => 1240....
  • Vấn đề lần này là khi đạt đến số n. Hiện tại thì mình cứ +1 lên nên sai là đương nhiên.
  • 1234 => 124. Khi đạt đến số n thì ta không tăng tiếp được nữa, nên phải đổi qua cách xử lý khác.
  • Nghĩ một hồi thì thấy cách xử lý cũng giống hệt trường hợp kết thúc bằng số 9.
  • Lại update code rồi chạy lại:
Python:
    def nextNumber(self, k, n):
        if 10 * k <= n:
            return 10 * k
     
        if k % 10 == 9 or k >= n:
            k //= 10
         
        return k + 1

Next problem?
Code:
Output: ...199 => 20 => 200 => 201....
Expected: ...199 => 2 => 20 => 200....
  • Ban nãy mình suy nghĩ đơn giản quá rồi, nếu k phải chỉ có 1 số 9, mà là 2 hoặc nhiều số 9 ở đuôi hơn thì sao
  • Đáp án là cứ chia 10 mãi tới khi nào k gặp số 9 nữa thì thôi. Đổi if thành while
Python:
    def nextNumber(self, k, n):
        if 10 * k <= n:
            return 10 * k
     
        while k % 10 == 9 or k >= n:
            k //= 10
         
        return k + 1

Đến đây thì đúng với case 1234 rồi. Thử với một vài sample khác.
Code:
1
2
9
10
99
100
123
999
1000
2000
9696
9999
10000
Passed all! Thế thì Submit thôi.

Python:
class Solution:
    def lexicalOrder(self, n: int) -> List[int]:
        res = []
        k = 1 # Cấu hình đầu tiên
        while len(res) < n:
            res.append(k)
            k = self.nextNumber(k, n)
             
        return res
 
    def nextNumber(self, k, n):
        if 10 * k <= n:
            return 10 * k
     
        while k % 10 == 9 or k >= n:
            k //= 10
         
        return k + 1
 
Last edited:
@_Gia_Cat_Luong_
Mình xin bổ sung thêm solution theo cách đệ quy, viết bằng C++ của mình trong bài này: https://leetcode.com/problems/lexicographical-numbers


C++:
class Solution {
public:
    vector<int> lexicalOrder(int n) {
        vector<int> result;
        recursiveLexicalOrder(n, result);
        return result;
    }
    
    void recursiveLexicalOrder(int n, vector<int>& result, int base = 0){
        for (int i = base == 0 ? 1:0; i < 10; i++){
            if (base*10+i <= n) {
                result.push_back(base*10+i);
                recursiveLexicalOrder(n,result, base*10+i);
            }
        }
    }
};
 
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:

#384. (Medium) https://leetcode.com/problems/shuffle-an-array/
#385. (Medium) https://leetcode.com/problems/mini-parser/
#386. (Medium) https://leetcode.com/problems/lexicographical-numbers/
#387. (Easy) https://leetcode.com/problems/first-unique-character-in-a-string/

Mình sẽ share về bài Lexicographical Numbers:
#386. (Medium) https://leetcode.com/problems/lexicographical-numbers/

Phân tích bài toán:
  • Lần này k cần phân tích constraint. Trong đề đã chỉ rõ luôn là yêu cầu độ phức tạp O(n) cho time và O(1) cho space
  • Dễ thấy đây là bài toán liệt kê, để giải bài này thì thường dùng 2 thuật toán cơ bản là "Phương pháp sinh" và "Thuật toán quay lui"
  • Bài này có thể giải bằng cả 2 cách, ở đây mình sẽ minh họa bằng Phương pháp sinh

Một chút về Phương pháp sinh, thuật toán này có pattern đơn giản như sau:
Code:
<Khởi tạo cấu hình đầu tiên>
while True:
    <In ra / lưu lại cấu hình đang có>
    if <đây là cấu hình cuối cùng>:
        break
    <sinh cấu hình tiếp theo từ cấu hình hiện tại>
  • Điểm cốt lõi của phương pháp này là từ một cấu hình, ta suy ra được cấu hình tiếp theo
  • Điều này mang theo ngầm định rằng các cấu hình là có thứ tự, và ta dễ dàng xác định được cấu hình đầu tiên cũng như cuối cùng

Quay lại bài toán, để áp dụng pp sinh, ta cần xác định:
  • Cấu hình ở đây là gì: Rõ ràng đó là các con số, từ 1 -> n
  • Thứ tự của cấu hình: Là thứ tự từ điển, theo như yêu cầu bài toán
  • Cấu hình đầu tiên: Số 1
  • Cấu hình cuối cùng: .... chưa biết
  • Cách thức sinh cấu hình tiếp theo: dĩ nhiên cũng là chưa biết

  • Vậy để giải bài này bằng phương pháp sinh, ta cần giải quyết 2 vấn đề:
    • Cấu hình cuối cùng
    • Cách thức sinh cấu hình tiếp theo
  • Về cấu hình cuối cùng: Vì output chắc chắn có n phần tử, nên thay vì tìm cấu hình cuối cùng, ta có thể đơn giản check khi nào tạo đủ n phần tử là được. Đỡ mệt não.
Solution:
Vậy bài toán chính làm thế nào để tính ra số tiếp theo từ số hiện tại, cách dễ nhất vẫn là run các ví dụ, nhận ra pattern rồi tổng quát hóa thành công thức:
Code:
Giả sử n = 1234, ta thấy đoạn đầu chuỗi biến đổi như sau:
1 => 10 => 100 => 1000 => 1001 => 1002 => 1003 => 1004...
  • Ở đây ta nhận thấy có 2 pattern chính
    • Nhân 10
    • Cộng thêm 1
  • Để đưa được thành công thức, ta cần suy nghĩ xem tại sao có những pattern này, và khi nào thì dùng cái nào
  • Vì ta đang muốn sort theo thứ tự từ điển, nên với một số x bất kì, số tiếp theo chắc chắn là x nối thêm số 0 vào đuôi, tương ứng với việc nhân 10.
  • Nhưng không thể nhân 10 mãi, vì ta có chặn trên là số n, nếu nhân 10 mà lớn hơn n thì ta không nhân thêm được nữa
  • Thay vì thế lúc này ta cộng thêm một, dễ thấy là vì nếu không nối dài thêm được, thì số tiếp theo chính là số đó cộng thêm một. Theo thứ tự toán học bình thường.
  • Đến đây, bạn nào giỏi có thể tiếp tục suy nghĩ về các pattern tiếp theo. Còn mình thường thì cứ code rồi run example, xem sai ở chỗ nào thì từ chỗ đó đoán ra pattern tiếp theo:
Python:
class Solution:
    def lexicalOrder(self, n: int) -> List[int]:
        res = []
        k = 1 # Cấu hình đầu tiên
        while len(res) < n:
            res.append(k)
            k = self.nextNumber(k, n)
            
        return res
 
    def nextNumber(self, k, n):
        if 10 * k <= n:
            return 10 * k
    
        return k + 1

Chạy thử với n = 1234. Kết quả dĩ nhiên là sai rồi, nhưng ta nhìn kĩ xem sai ở đâu:
Code:
Output: ...1009 => 1010 => 1011....
Expected: ...1009 => 101 => 1010 => 1011....
  • Ơ tự dưng lại có số 101 nhảy vào giữa nhỉ ? Nhưng ngẫm lại thì đúng rồi, theo thứ tự từ điển thì 1009 < 101 < 1010
  • Vậy làm thế nào ta biết được khi nào sẽ có mấy con số vô duyên này chen vào giữa ?
  • Nhìn kĩ thì có vẻ vấn đề nằm ở con số 9 cuối cùng. Nhận thấy nếu một số kết thúc bằng số 9, thì số tiếp theo sẽ bị chen vào giữa. Tại sao ?
  • Vì 9 + 1 = 10, nên khi +1 sẽ làm thay đổi số phía trước, đồng thời reset số cuối cùng thành 0. Mà theo thứ tự từ điển, ta thấy số abc9 < ab(c+1) < ab(c+1)0
  • À, vậy pattern tiếp theo là khi gặp một số kết thúc bằng số 9, thì ta bỏ số 9 đó đi rồi +1 vào đuôi
  • Nó cũng sẽ tự động đúng cho số tiếp theo luôn, vì số nhỏ sau đó sẽ được nhân 10 để tạo thành số tiếp theo với số 0 ở cuối.
  • Ok. Vậy implement logic này rồi chạy lại nào
Python:
    def nextNumber(self, k, n):
        if 10 * k <= n:
            return 10 * k
    
        if k % 10 == 9:
            k //= 10
        
        return k + 1

Chỗ sai tiếp theo ?
Code:
Output: ...1233 => 1234 => 1235 => 1236....
Expected: ...1233 => 1234 => 124 => 1240....
  • Vấn đề lần này là khi đạt đến số n. Hiện tại thì mình cứ +1 lên nên sai là đương nhiên.
  • 1234 => 124. Khi đạt đến số n thì ta không tăng tiếp được nữa, nên phải đổi qua cách xử lý khác.
  • Nghĩ một hồi thì thấy cách xử lý cũng giống hệt trường hợp kết thúc bằng số 9.
  • Lại update code rồi chạy lại:
Python:
    def nextNumber(self, k, n):
        if 10 * k <= n:
            return 10 * k
    
        if k % 10 == 9 or k >= n:
            k //= 10
        
        return k + 1

Next problem?
Code:
Output: ...199 => 20 => 200 => 201....
Expected: ...199 => 2 => 20 => 200....
  • Ban nãy mình suy nghĩ đơn giản quá rồi, nếu k phải chỉ có 1 số 9, mà là 2 hoặc nhiều số 9 ở đuôi hơn thì sao
  • Đáp án là cứ chia 10 mãi tới khi nào k gặp số 9 nữa thì thôi. Đổi if thành while
Python:
    def nextNumber(self, k, n):
        if 10 * k <= n:
            return 10 * k
    
        while k % 10 == 9 or k >= n:
            k //= 10
        
        return k + 1

Đến đây thì đúng với case 1234 rồi. Thử với một vài sample khác.
Code:
1
2
9
10
99
100
123
999
1000
2000
9696
9999
10000
Passed all! Thế thì Submit thôi.

Python:
class Solution:
    def lexicalOrder(self, n: int) -> List[int]:
        res = []
        k = 1 # Cấu hình đầu tiên
        while len(res) < n:
            res.append(k)
            k = self.nextNumber(k, n)
            
        return res
 
    def nextNumber(self, k, n):
        if 10 * k <= n:
            return 10 * k
    
        while k % 10 == 9 or k >= n:
            k //= 10
        
        return k + 1
học chăm quá fen, đáng học tập
 
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.
 
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.
hay đó thím :beauty:
 
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.

Bài này mình cũng có thể cộng tất cả các ký tự trong t rồi trừ cho s. Lúc này O(1) space.

C++:
class Solution {
public:
    char findTheDifference(string s, string t) {
        int sum = 0;
        for (auto& x : t) sum += x;
        for (auto& x : s) sum -= x;
        return sum;
    }
};
 
Lâu không ai post gì mình share 1 bài medium leetcode, đề bài khá đơn giản: cho 1 string, tìm palindromic substring lớn nhất (palindromic string là string mà viết xuôi viết ngược như nhau).
Các thím thử sức xem làm trong bao lâu, mình để gợi ý và lời giải ở dưới.
Thím nào có lời giải hay hơn thì đóng góp luôn.
https://leetcode.com/problems/longest-palindromic-substring/
Capture.PNG


Dùng quy hoạch động, cải tiến từ bài toán longest common substring.
Lời giải của mình:
- Để tìm longest palindromic substring của s, ta có thể dựa vào ý tưởng tìm longest common substring của s và chuỗi đảo của chính nó (rs), vì khi đảo lại s thì longest palindromic substring không đổi
VD: s là xxxabcdcba và chuỗi đảo rsabcdcbaxxx, khi cho vào thuật toán lc substr sẽ tìm được abcdcba.

- Tuy nhiên longest common substring chưa chắc đã là palindromic string
VD: s là xxxabcdxxxdcba thì rs là abcdxxxdcbaxxx, khi tìm lcsubstr của 2 chuỗi sẽ ra dcba hoặc abcd, sai so với yêu cầu.
Vì thế khi tìm được 1 chuỗi con chung dài nhất, sẽ check xem chuỗi có phải là palindromic string không.

Thuật toán:

1. Áp dụng lcsubstr, tìm 1 chuỗi con chung của 2 s và rs.
2. Khi được 1 chuỗi con chung:
2.1 Nếu độ dài chuỗi <= độ dài longest palindromic substring hiện tại, quay lại bước 1.
2.2 Check xem chuỗi có phải palindromic string không, nếu không quay lại bước 1.
2.3 Update độ dài lớn nhất và vị trí của chuỗi trong s hoặc rs.
3. Sử dụng vị trí và độ dài tìm được để trả về chuỗi kết quả.

Code hơi messy chút vì viết cho chạy luôn, tối ưu thì sẽ chạy nhanh hơn và đỡ tốn memory hơn.
C++:
class Solution {
public:
    string longestPalindrome(string s) {
        string rs = std::string(s);
        std::reverse(rs.begin(),rs.end());
     
        int n = s.size();
     
        //if (n <= 1) return s;
     
        int arr[n+2][n+2];
        memset(arr, 0, sizeof arr);
        int maxVal = 0, maxX = 0, maxY = 0;
     
        for (int i = 1; i <= n+1; ++i) {
            for (int j = 1; j <= n+1; ++j) {
                if (i < n+1 && j < n+1 && s[i-1] == rs[j-1]) {
                    arr[i][j] = arr[i-1][j-1] + 1;
                }
                else {
                    if (arr[i-1][j-1] > maxVal) {
                        int size = arr[i-1][j-1];
                        bool check = true;
                        for (int pl = 0; pl <= size/2; ++pl) {
                            if (s[i-2-pl] != s[i-1-size+pl]) {
                                check = false;
                                break;
                            }
                        }
                     
                        if (check) {
                            maxX = i-2;
                            maxY = j-2;
                            maxVal = arr[i-1][j-1];
                        }
                    }
                }
            }
        }
     
        string ans = s.substr(maxX - maxVal + 1,maxVal);
     
        return ans;
    }
};
 
Last edited:
Lâu không ai post gì mình share 1 bài medium leetcode, đề bài khá đơn giản: cho 1 string, tìm palindromic substring lớn nhất (palindromic string là string mà viết xuôi viết ngược như nhau).
Các thím thử sức xem làm trong bao lâu, mình để gợi ý và lời giải ở dưới.
Thím nào có lời giải hay hơn thì đóng góp luôn.
https://leetcode.com/problems/longest-palindromic-substring/
View attachment 685718

Dùng quy hoạch động, cải tiến từ bài toán longest common substring.
Lời giải của mình:
- Để tìm longest palindromic substring của s, ta có thể dựa vào ý tưởng tìm longest common substring của s và chuỗi đảo của chính nó (rs), vì khi đảo lại s thì longest palindromic substring không đổi
VD: s là xxxabcdcba và chuỗi đảo rsabcdcbaxxx, khi cho vào thuật toán lc substr sẽ tìm được abcdcba.

- Tuy nhiên longest common substring chưa chắc đã là palindromic string
VD: s là xxxabcdxxxdcba thì rs là abcdxxxdcbaxxx, khi tìm lcsubstr của 2 chuỗi sẽ ra dcba hoặc abcd, sai so với yêu cầu.
Vì thế khi tìm được 1 chuỗi con chung dài nhất, sẽ check xem chuỗi có phải là palindromic string không.

Cách này hơi phức tạp, cũng là qhđ O(n^2) time, O(n^2) mem nhưng có thể đơn giản hơn.
Đặt p[m][n] = true / false, thể hiện đoạn từ m đến n có là palindrome hay không. Mặc định cho substring độ dài 0 và 1 là palindrome.
Tính chất: p[m-1][n+1] = true <==> p[m][n] = true && s[m-1] = s[n+1]
 
Cách này hơi phức tạp, cũng là qhđ O(n^2) time, O(n^2) mem nhưng có thể đơn giản hơn.
Đặt p[m][n] = true / false, thể hiện đoạn từ m đến n có là palindrome hay không. Mặc định cho substring độ dài 0 và 1 là palindrome.
Tính chất: p[m-1][n+1] = true <==> p[m][n] = true && s[m-1] = s[n+1]
Cách này hay đó thím, thời gian thì chắc chắn nhanh hơn. Thím thử implement chưa, mình đang ko biết duyệt mảng p như nào cho hiệu quả nhất, vì sẽ biết giá trị của các pii trước xong tính các ô lân cận, nên chắc phải dùng queue.
 
Lâu không ai post gì mình share 1 bài medium leetcode, đề bài khá đơn giản: cho 1 string, tìm palindromic substring lớn nhất (palindromic string là string mà viết xuôi viết ngược như nhau).
Các thím thử sức xem làm trong bao lâu, mình để gợi ý và lời giải ở dưới.
Thím nào có lời giải hay hơn thì đóng góp luôn.
https://leetcode.com/problems/longest-palindromic-substring/
View attachment 685718

Dùng quy hoạch động, cải tiến từ bài toán longest common substring.
Lời giải của mình:
- Để tìm longest palindromic substring của s, ta có thể dựa vào ý tưởng tìm longest common substring của s và chuỗi đảo của chính nó (rs), vì khi đảo lại s thì longest palindromic substring không đổi
VD: s là xxxabcdcba và chuỗi đảo rsabcdcbaxxx, khi cho vào thuật toán lc substr sẽ tìm được abcdcba.

- Tuy nhiên longest common substring chưa chắc đã là palindromic string
VD: s là xxxabcdxxxdcba thì rs là abcdxxxdcbaxxx, khi tìm lcsubstr của 2 chuỗi sẽ ra dcba hoặc abcd, sai so với yêu cầu.
Vì thế khi tìm được 1 chuỗi con chung dài nhất, sẽ check xem chuỗi có phải là palindromic string không.

Thuật toán:

1. Áp dụng lcsubstr, tìm 1 chuỗi con chung của 2 s và rs.
2. Khi được 1 chuỗi con chung:
2.1 Nếu độ dài chuỗi <= độ dài longest palindromic substring hiện tại, quay lại bước 1.
2.2 Check xem chuỗi có phải palindromic string không, nếu không quay lại bước 1.
2.3 Update độ dài lớn nhất và vị trí của chuỗi trong s hoặc rs.
3. Sử dụng vị trí và độ dài tìm được để trả về chuỗi kết quả.

Code hơi messy chút vì viết cho chạy luôn, tối ưu thì sẽ chạy nhanh hơn và đỡ tốn memory hơn.
C++:
class Solution {
public:
    string longestPalindrome(string s) {
        string rs = std::string(s);
        std::reverse(rs.begin(),rs.end());
     
        int n = s.size();
     
        //if (n <= 1) return s;
     
        int arr[n+2][n+2];
        memset(arr, 0, sizeof arr);
        int maxVal = 0, maxX = 0, maxY = 0;
     
        for (int i = 1; i <= n+1; ++i) {
            for (int j = 1; j <= n+1; ++j) {
                if (i < n+1 && j < n+1 && s[i-1] == rs[j-1]) {
                    arr[i][j] = arr[i-1][j-1] + 1;
                }
                else {
                    if (arr[i-1][j-1] > maxVal) {
                        int size = arr[i-1][j-1];
                        bool check = true;
                        for (int pl = 0; pl <= size/2; ++pl) {
                            if (s[i-2-pl] != s[i-1-size+pl]) {
                                check = false;
                                break;
                            }
                        }
                     
                        if (check) {
                            maxX = i-2;
                            maxY = j-2;
                            maxVal = arr[i-1][j-1];
                        }
                    }
                }
            }
        }
     
        string ans = s.substr(maxX - maxVal + 1,maxVal);
     
        return ans;
    }
};
Muốn nhanh hơn nữa thì search Keyword: Manacher :big_smile:

via theNEXTvoz for iPhone
 
Lâu không ai post gì mình share 1 bài medium leetcode, đề bài khá đơn giản: cho 1 string, tìm palindromic substring lớn nhất (palindromic string là string mà viết xuôi viết ngược như nhau).
Các thím thử sức xem làm trong bao lâu, mình để gợi ý và lời giải ở dưới.
Thím nào có lời giải hay hơn thì đóng góp luôn.
https://leetcode.com/problems/longest-palindromic-substring/
View attachment 685718

Dùng quy hoạch động, cải tiến từ bài toán longest common substring.
Lời giải của mình:
- Để tìm longest palindromic substring của s, ta có thể dựa vào ý tưởng tìm longest common substring của s và chuỗi đảo của chính nó (rs), vì khi đảo lại s thì longest palindromic substring không đổi
VD: s là xxxabcdcba và chuỗi đảo rsabcdcbaxxx, khi cho vào thuật toán lc substr sẽ tìm được abcdcba.

- Tuy nhiên longest common substring chưa chắc đã là palindromic string
VD: s là xxxabcdxxxdcba thì rs là abcdxxxdcbaxxx, khi tìm lcsubstr của 2 chuỗi sẽ ra dcba hoặc abcd, sai so với yêu cầu.
Vì thế khi tìm được 1 chuỗi con chung dài nhất, sẽ check xem chuỗi có phải là palindromic string không.

Thuật toán:

1. Áp dụng lcsubstr, tìm 1 chuỗi con chung của 2 s và rs.
2. Khi được 1 chuỗi con chung:
2.1 Nếu độ dài chuỗi <= độ dài longest palindromic substring hiện tại, quay lại bước 1.
2.2 Check xem chuỗi có phải palindromic string không, nếu không quay lại bước 1.
2.3 Update độ dài lớn nhất và vị trí của chuỗi trong s hoặc rs.
3. Sử dụng vị trí và độ dài tìm được để trả về chuỗi kết quả.

Code hơi messy chút vì viết cho chạy luôn, tối ưu thì sẽ chạy nhanh hơn và đỡ tốn memory hơn.
C++:
class Solution {
public:
    string longestPalindrome(string s) {
        string rs = std::string(s);
        std::reverse(rs.begin(),rs.end());
  
        int n = s.size();
  
        //if (n <= 1) return s;
  
        int arr[n+2][n+2];
        memset(arr, 0, sizeof arr);
        int maxVal = 0, maxX = 0, maxY = 0;
  
        for (int i = 1; i <= n+1; ++i) {
            for (int j = 1; j <= n+1; ++j) {
                if (i < n+1 && j < n+1 && s[i-1] == rs[j-1]) {
                    arr[i][j] = arr[i-1][j-1] + 1;
                }
                else {
                    if (arr[i-1][j-1] > maxVal) {
                        int size = arr[i-1][j-1];
                        bool check = true;
                        for (int pl = 0; pl <= size/2; ++pl) {
                            if (s[i-2-pl] != s[i-1-size+pl]) {
                                check = false;
                                break;
                            }
                        }
                  
                        if (check) {
                            maxX = i-2;
                            maxY = j-2;
                            maxVal = arr[i-1][j-1];
                        }
                    }
                }
            }
        }
  
        string ans = s.substr(maxX - maxVal + 1,maxVal);
  
        return ans;
    }
};


C++:
class Solution {
public:
    string longestPalindrome(string s) {
      
        if (s.size() == 0) return "";
      
        int n = s.size();
        vector<vector<int>> dP (n, vector<int> (n, false));
      
        for (int i = 0; i < n; i++) {
            dP[i][i] = true;
            if(i == n-1) break;
            dP[i][i+1] =  (s[i] == s[i+1] );
        }
      
        for (int i = n-3; i >= 0; i--) {
            for (int j = i+2; j <n; j++) {
                dP[i][j] =  (dP[i+1][j-1] && (s[i] == s[j]));
            }
        }
      
      
        string strRes = "";
        int maxRes = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i; j <n; j++) {
                if(dP[i][j]) {
                    if( maxRes < (j-i+1)) {
                        maxRes = j-i+1;
                        strRes = s.substr(i,j-i+1);
                    }
                }
            }
        }
      
        return strRes;
    }
};
Có thể optimize lại code 1 chút cho tối ưu hơn, ví dụ tránh substr nhiều lần.
 
Cách này hay đó thím, thời gian thì chắc chắn nhanh hơn. Thím thử implement chưa, mình đang ko biết duyệt mảng p như nào cho hiệu quả nhất, vì sẽ biết giá trị của các pii trước xong tính các ô lân cận, nên chắc phải dùng queue.

Thực ra còn không cần phải lưu mảng p. Vì có thể từ một vị trí i trượt ra hai phía để test, không khớp thì dừng lại. Tức là O(1) space.

Sent from Xiaomi M2007J20CG using vozFApp
 
Lâu không ai post gì mình share 1 bài medium leetcode, đề bài khá đơn giản: cho 1 string, tìm palindromic substring lớn nhất (palindromic string là string mà viết xuôi viết ngược như nhau).
Các thím thử sức xem làm trong bao lâu, mình để gợi ý và lời giải ở dưới.
Thím nào có lời giải hay hơn thì đóng góp luôn.
https://leetcode.com/problems/longest-palindromic-substring/
View attachment 685718

Dùng quy hoạch động, cải tiến từ bài toán longest common substring.
Lời giải của mình:
- Để tìm longest palindromic substring của s, ta có thể dựa vào ý tưởng tìm longest common substring của s và chuỗi đảo của chính nó (rs), vì khi đảo lại s thì longest palindromic substring không đổi
VD: s là xxxabcdcba và chuỗi đảo rsabcdcbaxxx, khi cho vào thuật toán lc substr sẽ tìm được abcdcba.

- Tuy nhiên longest common substring chưa chắc đã là palindromic string
VD: s là xxxabcdxxxdcba thì rs là abcdxxxdcbaxxx, khi tìm lcsubstr của 2 chuỗi sẽ ra dcba hoặc abcd, sai so với yêu cầu.
Vì thế khi tìm được 1 chuỗi con chung dài nhất, sẽ check xem chuỗi có phải là palindromic string không.

Thuật toán:

1. Áp dụng lcsubstr, tìm 1 chuỗi con chung của 2 s và rs.
2. Khi được 1 chuỗi con chung:
2.1 Nếu độ dài chuỗi <= độ dài longest palindromic substring hiện tại, quay lại bước 1.
2.2 Check xem chuỗi có phải palindromic string không, nếu không quay lại bước 1.
2.3 Update độ dài lớn nhất và vị trí của chuỗi trong s hoặc rs.
3. Sử dụng vị trí và độ dài tìm được để trả về chuỗi kết quả.

Code hơi messy chút vì viết cho chạy luôn, tối ưu thì sẽ chạy nhanh hơn và đỡ tốn memory hơn.
C++:
class Solution {
public:
    string longestPalindrome(string s) {
        string rs = std::string(s);
        std::reverse(rs.begin(),rs.end());
  
        int n = s.size();
  
        //if (n <= 1) return s;
  
        int arr[n+2][n+2];
        memset(arr, 0, sizeof arr);
        int maxVal = 0, maxX = 0, maxY = 0;
  
        for (int i = 1; i <= n+1; ++i) {
            for (int j = 1; j <= n+1; ++j) {
                if (i < n+1 && j < n+1 && s[i-1] == rs[j-1]) {
                    arr[i][j] = arr[i-1][j-1] + 1;
                }
                else {
                    if (arr[i-1][j-1] > maxVal) {
                        int size = arr[i-1][j-1];
                        bool check = true;
                        for (int pl = 0; pl <= size/2; ++pl) {
                            if (s[i-2-pl] != s[i-1-size+pl]) {
                                check = false;
                                break;
                            }
                        }
                  
                        if (check) {
                            maxX = i-2;
                            maxY = j-2;
                            maxVal = arr[i-1][j-1];
                        }
                    }
                }
            }
        }
  
        string ans = s.substr(maxX - maxVal + 1,maxVal);
  
        return ans;
    }
};

Bài này hồi lâu mình cũng có làm rồi tìm hiểu thì có thể giải bằng Manacher's Algorithm trong O(n). Nhưng sau này biết thêm nhiều thuật giải quyết string thì mình thấy Manacher không nên học làm gì. Bởi cái này là một dạng thuật Ad-hoc chỉ có thể dùng được trong một bài toán cụ thể. Thay vào đó thì nên học những dạng thuật toán học 1 làm 10.

Giờ quay lại làm lại mình cũng cố thử giải bài này bằng hash string + binary search.
Mọi người có thể tìm hiểu thêm về hash string ở 2 trang sau:
https://cp-algorithms.com/string/string-hashing.html
https://vnoi.info/wiki/algo/string/hash.md

Đầu tiên để cho đơn giản ta chèn thêm kí tự vào giữa string (ở đây mình dùng '#'). Mục đích của việc này là để khỏi cần phải chia ra 2 trường hợp tâm đối xứng lẻ và chẵn.
Để tiện gọi tên mình sẽ gọi string mới này là ss. Khi đó độ dài của một Palindromic Substring của ss có độ dài n sẽ bằng (n - 1) / 2 hay chính là bán kính đối xứng.

Ví dụ: (Tâm đối xứng gạch chân dưới)
aba => #a#b#a# => (7 - 1) / 2 = 3
abba => #a#b#b#a# => (9 - 1) / 2 = 4

Để sử dụng BS ta có nhận xét sau:
  • Nếu tồn tại một Palindromic Substring có bán kính đối xứng là l. Khi đó sẽ tồn tại các Palindromic Substring có bán kính [1 .. l - 1] trùng tâm.
  • Nếu không tồn tại một Palindromic Substring có bán kính đối xứng là l. Khi đó sẽ không tồn tại các Palindromic Substring có bán kính [l .. N].
Đến đây ta có thể sử dùng BS để tìm một Palindromic Substring có bán kính lớn nhất. Và dùng 2 lần hash xuôi và ngược để so sánh.

Time complexity O(nlogn).

Code:

C++:
class Solution {
public:
    #define ll long long
    const ll MOD = 1e9 + 9;
    static const int MAXN = 2003;
    const int p = 31;
    ll power[MAXN], hashT[MAXN], rhashT[MAXN];
   
    string ss = " #";
    string ans;
   
    void precompute() {
        power[0] = 1;
        hashT[0] = rhashT[ss.size()] = 0;
   
        for (int i = 1; i < ss.size(); ++i)
            power[i] = power[i - 1] * p % MOD;
   
        for (int i = 1; i < ss.size(); ++i)
            hashT[i] = (hashT[i - 1] * p + ss[i] - '#' + 1) % MOD;
   
        for (int i = ss.size() - 1; i >= 1; --i)
            rhashT[i] = (rhashT[i + 1] * p + ss[i] - '#' + 1) % MOD;
    }
   
    bool check(int len) {
        for (int i = 1 + len; i <= ss.size() - len - 1; ++i) {
            ll h = (hashT[i] - hashT[i - len - 1] * power[len + 1] + MOD * MOD) % MOD;
            ll rh = (rhashT[i] - rhashT[i + len + 1] * power[len + 1] + MOD * MOD) % MOD;
            if (h == rh) {
                ans = ss.substr(i - len, 2 * len + 1);
                return true;
            }
        }
   
        return false;
    }
   
    string longestPalindrome(string s) {
        ans = s[0];
        for (auto& x : s) ss = ss + x + "#";
        int l = 1, r = s.size();
        precompute();
   
        while (l < r) {
            int m = (l + r + 1) / 2;
   
            if (check(m)) l = m;
            else r = m - 1;
        }
   
        ans.erase(remove(ans.begin(), ans.end(), '#'), ans.end());
        return ans;
    }
};
 
Tiếp tục chuyên mục mỗi ngày một leetcode. 3 bài mình làm ngày hôm nay:

#393. (Medium) https://leetcode.com/problems/utf-8-validation
#394. (Medium) https://leetcode.com/problems/decode-string
#395. (Medium) https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters

Hôm nay lười quá nên mình k share detail về bài nào cả T_T. Chỉ share solution bài Decode String:
#394. (Medium) https://leetcode.com/problems/decode-string

Python:
class Solution:
    def decodeString(self, s: str) -> str:
        stack = []
        text = ''
        num = ''
        for ch in s:
            if ch == '[':
                stack.append((text, int(num) if num else 1))
                text = ''
                num = ''
            elif ch == ']':
                if num or not stack:
                    raise Error('Invalid string')
                prefix, k = stack.pop()
                text = prefix + k * text
                num = ''
            elif ch.isdigit():
                num += ch
            else:
                text += ch
               
        if stack:
            raise Error('Invalid string')
           
        return text
 
Bài này mình cũng có thể cộng tất cả các ký tự trong t rồi trừ cho s. Lúc này O(1) space.

C++:
class Solution {
public:
    char findTheDifference(string s, string t) {
        int sum = 0;
        for (auto& x : t) sum += x;
        for (auto& x : s) sum -= x;
        return sum;
    }
};
cách này có 1 vấn đề là nếu string quá lớn thì cái sum có thể bị tràn nhé.
Bài này có nhiều biến thể, cách làm tốt nhất là dùng xor. Vừa không bị tràn lại chạy nhanh hơn do phép xor tốn ít CPU cycle hơn.

Giải thích: Do phép xor có tính chất sau: x^x=0. Nên khi xor toàn bộ các chữ cái của s và t cho nhau thì những chữ đi theo cặp giống sẽ tạo ra kết quả 0 và cuối cùng chỉ còn lại t.

C++:
class Solution {
public:
    char findTheDifference(string s, string t) {
        char l = 0;
        for (auto& x : s) l ^= x;
        for (auto& x : t) l ^= x;
        return l;
    }
};

Runtime: 0 ms, faster than 100.00% of C++ online submissions for Find the Difference.
 
Tiếp tục chuyên mục mỗi ngày một leetcode. 3 bài mình làm ngày hôm nay:

#393. (Medium) https://leetcode.com/problems/utf-8-validation
#394. (Medium) https://leetcode.com/problems/decode-string
#395. (Medium) https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters

Hôm nay lười quá nên mình k share detail về bài nào cả T_T. Chỉ share solution bài Decode String:
#394. (Medium) https://leetcode.com/problems/decode-string

Python:
class Solution:
    def decodeString(self, s: str) -> str:
        stack = []
        text = ''
        num = ''
        for ch in s:
            if ch == '[':
                stack.append((text, int(num) if num else 1))
                text = ''
                num = ''
            elif ch == ']':
                if num or not stack:
                    raise Error('Invalid string')
                prefix, k = stack.pop()
                text = prefix + k * text
                num = ''
            elif ch.isdigit():
                num += ch
            else:
                text += ch
              
        if stack:
            raise Error('Invalid string')
          
        return text
Thấy thím có vẻ chăm làm, hay lập team cày thử cái August LeetCoding Challenge 2021 với em luôn, nhân tiện nay mùng 1. Mỗi bài mới ra giới hạn thời gian là 1 ngày nên có vẻ thử thách hơn :big_smile:
Ai làm full 30 ngày thì được 1 cái badge với có tỷ lệ nhận T-shirt.

https://leetcode.com/explore/featur...e-2021/613/week-1-august-1st-august-7th/3835/
 
Mình hiện tại đang rất tuyệt vọng với môn Thuật toán này, không hiểu tại sao mình đã học nó gần cả năm nay mà thấy vẫn không có gì tiến bộ, gặp một tình uống thực sự mình rất bí ý tưởng, mà khi có ý tưởng rồi thì lại rất khó biến ý tưởng thành code. Mình khó khăn nhất là với các bài toán về Quy hoạch động, thật sự là mình chả làm được bài nào nếu không xem hướng dẫn trên Hackerrank. Hôm qua tính làm vài bài để luyện trình mà làm cả buổi sáng cũng không nổi 1 bài, mình rất buồn vì bản thân,
Mình cần lời khuyên của mọi người, chỉ cho mình con đường đi với, mình vẫn cố gắng luyện tập nó hằng ngày mà sao vẫn không khá hơn ???
 
Back
Top