Leetcode Logo

LeetCode Daily Challenge – Day 3: Check if a Parentheses String Can Be Valid

👉 Mục tiêu: Mỗi ngày giải 1 bài LeetCode, kiên trì tạo thói quen, từng bước chinh phục các kỹ năng lập trình.

Link: https://leetcode.com/problems/check-if-a-parentheses-string-can-be-valid/description/?envType=daily-question&envId=2025-01-12

MySolution :https://leetcode.com/problems/check-if-a-parentheses-string-can-be-valid/solutions/6267099/beginner-friendly-c-solution-to-parenthesis-valid

Intuition

Xin chào anh em lập trình viên! Hôm nay mình sẽ deep dive vào một bài toán rất hay về xử lý xâu dấu ngoặc trên LeetCode. Bài này không chỉ kiểm tra tính hợp lệ của xâu dấu ngoặc thông thường, mà còn cho phép ta “hack” một số vị trí! 😎

Approach

Đầu tiên, mình sẽ chia sẻ cách tư duy và giải quyết bài toán này một cách chi tiết:

1. Phân Tích Bài Toán

Trước khi bắt đầu code, chúng ta cần hiểu rõ các trường hợp có thể xảy ra:

C++
TEST CASES:
1. s = "(()", locked = "001" -> false (độ dài lẻ)

2. s = "(())", locked = "1111" -> true (đã hợp lệ)

3. s = "))()", locked = "0000" -> true (có thể đổi thành "(())")

4. s = ")(", locked = "11" -> false (không đổi được, bị khóa)

5. s = "())(", locked = "0011" -> true (đổi 2 kí tự đầu)

2. Logic Xử Lý Chi Tiết

Bước 1: Kiểm Tra Điều Kiện Ban Đầu

  • Độ dài xâu phải chẵn (vì số lượng ‘(‘ và ‘)’ phải bằng nhau)
  • Khởi tạo 2 stack để theo dõi:
  • openStack: lưu vị trí các ‘(‘ bị khóa
  • freeStack: lưu vị trí các ký tự có thể thay đổi

Bước 2: Duyệt Xâu và Xử Lý

  • Khi gặp ký tự tự do (locked[i] = ‘0’):
  • Lưu vào freeStack để dùng sau
  • Khi gặp ‘(‘ bị khóa:
  • Lưu vào openStack
  • Khi gặp ‘)’ bị khóa:
  • Ưu tiên ghép với ‘(‘ trong openStack
  • Nếu không có ‘(‘, dùng ký tự tự do từ freeStack
  • Nếu không có cả hai -> invalid

Bước 3: Xử Lý Stack Còn Lại

  • Ghép các ‘(‘ còn thừa với ký tự tự do
  • Chú ý: ký tự tự do phải nằm sau ‘(‘

Complexity

Time complexity: $$O(n)$$ – một lần duyệt qua xâu

Space complexity: $$O(n)$$ – hai stack trong trường hợp xấu nhất

Code

C++
class Solution {
public:
    bool canBeValid(string s, string locked) {
        // Bước 1: Check điều kiện cơ bản - độ dài phải chẵn
        if (s.length() % 2 != 0) return false;

        // Khởi tạo hai stack để theo dõi
        stack<int> openStack, freeStack;

        // Bước 2: Duyệt từng ký tự trong xâu
        for (int i = 0; i < s.length(); i++) {
            if (locked[i] == '0') {
                // Case 1: Ký tự có thể thay đổi -> lưu vào freeStack
                freeStack.push(i);
            } 
            else if (s[i] == '(') {
                // Case 2: Dấu mở ngoặc bị khóa -> lưu vào openStack
                openStack.push(i);
            } 
            else { // Case 3: s[i] == ')' và bị khóa
                // Thử ghép với dấu mở ngoặc gần nhất
                if (!openStack.empty()) {
                    openStack.pop();
                }
                // Không có '(' -> dùng ký tự tự do
                else if (!freeStack.empty()) {
                    freeStack.pop();
                }
                // Không có cách nào để ghép -> invalid
                else {
                    return false;
                }
            }
        }

        // Bước 3: Xử lý các dấu mở ngoặc còn thừa
        while (!openStack.empty() && !freeStack.empty()) {
            // Check vị trí: ký tự tự do phải nằm sau dấu mở ngoặc
            if (openStack.top() > freeStack.top()) return false;
            openStack.pop();
            freeStack.pop();
        }

        // Cuối cùng: nếu còn dấu mở ngoặc -> invalid
        return openStack.empty();
    }
};

Chương Trình Test

C++
int main() {
    Solution sol;

    // Test case 1: Có thể biến đổi
    cout << "Test 1: " << (sol.canBeValid("())", "010") ? "TRUE" : "FALSE") << endl;
    // Kết quả mong đợi: False

    // Test case 2: Không thể biến đổi
    cout << "Test 2: " << (sol.canBeValid(")(", "11") ? "TRUE" : "FALSE") << endl;
    // Kết quả mong đợi: FALSE

    // Test case phức tạp
    cout << "Test 3: " << (sol.canBeValid("())(())", "0000000") ? "TRUE" : "FALSE") << endl;
    // Kết quả mong đợi: TRUE

    return 0;
}

Tips & Tricks Thêm

  1. Luôn xử lý edge cases trước (như độ dài lẻ)
  2. Sử dụng stack giúp code clean và dễ maintain
  3. Chú ý thứ tự ưu tiên khi ghép cặp dấu ngoặc

Vậy là đã xong Day 3 ae ạ ! Hy vọng bài viết này giúp ích cho anh em! Nếu có thắc mắc gì, comment bên dưới nhé! 🚀

Follow blog của tụi mình để đón những bài viết tiếp theo về thuật toán và competitive programming nhé! Happy coding! 💻✨

Code Editor cho ae test, chạy chơi thôi nha ae =)))

quyminhhuy0@gmail.com
quyminhhuy0@gmail.com
Articles: 5
Subscribe
Notify of
guest

2 Comments
Inline Feedbacks
View all comments
chisboiz

gud, chúc em thành công

Anonymous

Hay qua a

2
0
Would love your thoughts, please comment.x
()
x