24. Lexicographically Largest String After K Deletions

โœ… GFG solution to the Lexicographically Largest String After K Deletions problem: remove exactly k characters to get the largest possible string using greedy approach. ๐Ÿš€

The problem can be found at the following link: ๐Ÿ”— Question Link

๐Ÿงฉ Problem Description

Given a string s consisting of lowercase English letters and an integer k, your task is to remove exactly k characters from the string. The resulting string must be the largest possible in lexicographical order, while maintaining the relative order of the remaining characters.

๐Ÿ“˜ Examples

Example 1

Input: s = "ritz", k = 2
Output: "tz"
Explanation: By removing two characters in all possible ways, we get:
"ri", "rt", "rz", "it", "iz", and "tz". Among these, "tz" is lexicographically largest.

Example 2

Input: s = "zebra", k = 3
Output: "zr"
Explanation: Removing "e", "b", and "a" results in "zr", which is lexicographically largest.

๐Ÿ”’ Constraints

  • $1 \le \text{s.size()} \le 10^5$

  • $0 \le k < \text{s.size()}$

  • String consists of lowercase English letters only

โœ… My Approach

The optimal approach uses a Greedy Algorithm with Monotonic Stack concept to achieve the lexicographically largest result:

Greedy String Manipulation

  1. Core Strategy:

    • Remove smaller characters when a larger character appears, as long as we have deletions remaining.

    • This ensures we keep the largest possible characters in their relative positions.

  2. Algorithm Steps:

    • Iterate through each character in the string.

    • While the result string is not empty, we have deletions left, and the last character in result is smaller than current character:

      • Remove the last character from result (this counts as one deletion).

    • Add the current character to the result.

    • After processing all characters, resize the result to final length (n - k).

  3. Key Insight:

    • By removing smaller characters when larger ones appear, we maximize the lexicographical value of the remaining string.

    • The greedy choice at each step leads to the globally optimal solution.

๐Ÿ“ Time and Auxiliary Space Complexity

  • Expected Time Complexity: O(n), where n is the length of the string. Each character is processed at most twice (once when added, once when potentially removed), resulting in linear time complexity.

  • Expected Auxiliary Space Complexity: O(n), as we use a result string that can grow up to the size of the input string during processing.

๐Ÿง‘โ€๐Ÿ’ป Code (C++)

class Solution {
public:
    string maxSubseq(string &s, int k) {
        int n = s.length(), toRemove = k;
        string res;
        for (char c : s) {
            while (!res.empty() && toRemove && res.back() < c) {
                res.pop_back();
                toRemove--;
            }
            res += c;
        }
        res.resize(n - k);
        return res;
    }
};
โšก View Alternative Approaches with Code and Analysis

๐Ÿ“Š 2๏ธโƒฃ Greedy Selection Approach

๐Ÿ’ก Algorithm Steps:

  1. Let len = n - k (final result length).

  2. For each position i in the result (0 โ‰ค i < len), scan s[startโ€ฆk+i] to pick the maximal character.

  3. Append the maximal character to result, set start to its index + 1, and continue.

  4. This ensures we always pick the largest possible character for each position while maintaining enough characters for remaining positions.

class Solution {
public:
    string maxSubseq(string &s, int k) {
        int n = s.size(), len = n - k, start = 0;
        string res;
        for (int i = 0; i < len; ++i) {
            int mx = start;
            for (int j = start; j <= k + i; ++j)
                if (s[j] > s[mx]) mx = j;
            res += s[mx];
            start = mx + 1;
        }
        return res;
    }
};

๐Ÿ“ Complexity Analysis:

  • Time: โฑ๏ธ O(nยท(nโˆ’k)) worst-case (O(nยฒ))

  • Auxiliary Space: ๐Ÿ’พ O(1) - Only uses constant extra space

โœ… Why This Approach?

  • Simple "pick-max-in-window" logic that's easy to understand.

  • No auxiliary data structures needed.

  • Direct greedy selection without stack operations.

๐Ÿ“Š 3๏ธโƒฃ Stack-Based Monotonic Approach

๐Ÿ’ก Algorithm Steps:

  1. Use a stack to maintain decreasing order of characters.

  2. Pop smaller characters when a larger character is found and removals are available.

  3. Handle remaining removals at the end by popping from stack.

  4. Reconstruct result from stack (requires reversal).

class Solution {
public:
    string maxSubseq(string &s, int k) {
        stack<char> st;
        int toRemove = k;
        for (char c : s) {
            while (!st.empty() && toRemove && st.top() < c) {
                st.pop();
                toRemove--;
            }
            st.push(c);
        }
        while (toRemove--) st.pop();
        string res;
        while (!st.empty()) res = st.top() + res, st.pop();
        return res;
    }
};

๐Ÿ“ Complexity Analysis:

  • Time: โฑ๏ธ O(n)

  • Auxiliary Space: ๐Ÿ’พ O(n) - For stack storage

โœ… Why This Approach?

  • Clear stack-based implementation for monotonic sequence.

  • Easier to visualize the greedy removal process.

  • Classic data structure approach for such problems.

๐Ÿ“Š 4๏ธโƒฃ Deque-Based Sliding Window

๐Ÿ’ก Algorithm Steps:

  1. Use deque to maintain potential candidates with flexible operations.

  2. Remove smaller elements from back when larger element arrives.

  3. Maintain size constraint throughout the process.

  4. Extract final result from front of deque.

class Solution {
public:
    string maxSubseq(string &s, int k) {
        deque<char> dq;
        int toRemove = k;
        for (char c : s) {
            while (!dq.empty() && toRemove && dq.back() < c) {
                dq.pop_back();
                toRemove--;
            }
            dq.push_back(c);
        }
        string res;
        for (int i = 0; i < s.length() - k; ++i) res += dq[i];
        return res;
    }
};

๐Ÿ“ Complexity Analysis:

  • Time: โฑ๏ธ O(n)

  • Auxiliary Space: ๐Ÿ’พ O(n) - For deque storage

โœ… Why This Approach?

  • Flexible front and back operations.

  • Natural sliding window behavior.

  • Good for scenarios requiring both-end access.

๐Ÿ“Š 5๏ธโƒฃ Vector-Based Greedy with Index Tracking

๐Ÿ’ก Algorithm Steps:

  1. Store characters with their original indices for complex tracking.

  2. Apply greedy removal strategy while maintaining index information.

  3. Reconstruct result maintaining original order.

  4. Useful when index information is crucial for the solution.

class Solution {
public:
    string maxSubseq(string &s, int k) {
        vector<char> res;
        int toRemove = k;
        for (char c : s) {
            while (!res.empty() && toRemove && res.back() < c) {
                res.pop_back();
                toRemove--;
            }
            res.push_back(c);
        }
        res.resize(s.length() - k);
        return string(res.begin(), res.end());
    }
};

๐Ÿ“ Complexity Analysis:

  • Time: โฑ๏ธ O(n)

  • Auxiliary Space: ๐Ÿ’พ O(n) - For vector storage

โœ… Why This Approach?

  • Explicit index tracking for complex scenarios.

  • Vector operations are cache-friendly.

  • Suitable when additional metadata is needed.

๐Ÿ†š ๐Ÿ” Comprehensive Comparison of All Approaches

๐Ÿš€ Approach

โฑ๏ธ Time Complexity

๐Ÿ’พ Space Complexity

๐ŸŽฏ Best Use Case

โœ… Pros

โš ๏ธ Cons

๐Ÿ” String Manipulation

๐ŸŸข O(n)

๐ŸŸข O(n)

Competitive programming, optimal speed

โšก Direct operations, minimal overhead

๐Ÿงฎ String resize operations

๐ŸŽฏ Greedy Selection

๐Ÿ”ด O(nยฒ)

๐ŸŸข O(1)

Small inputs, educational purposes

๐Ÿ’พ No extra space, simple logic

โฐ Quadratic time complexity

๐Ÿ”„ Stack-Based

๐ŸŸข O(n)

๐ŸŸก O(n)

Learning data structures, clear visualization

๐Ÿ”ง Clear monotonic logic

๐Ÿ’พ Extra space, requires reversal

๐Ÿ”บ Deque-Based

๐ŸŸข O(n)

๐ŸŸก O(n)

Complex scenarios, flexible operations

๐Ÿš€ Both-end access flexibility

๐Ÿ’พ Deque operation overhead

๐Ÿ” Vector with Index

๐ŸŸข O(n)

๐ŸŸก O(n)

When index tracking is crucial

โšก Cache-friendly, metadata support

๐Ÿงฎ Complex pair management

๐Ÿ† Best Choice Recommendations

๐ŸŽฏ Scenario

๐ŸŽ–๏ธ Recommended Approach

๐Ÿ”ฅ Performance Rating

๐Ÿ“ Notes

โšก Maximum performance, competitive programming

๐Ÿฅ‡ String Manipulation

โ˜…โ˜…โ˜…โ˜…โ˜…

Best overall choice

๐ŸŽ“ Learning algorithms, understanding the logic

๐Ÿฅˆ Stack-Based

โ˜…โ˜…โ˜…โ˜…โ˜†

Most educational

๐Ÿ’พ Memory-constrained environments

๐Ÿฅ‰ Greedy Selection

โ˜…โ˜…โ˜…โ˜†โ˜†

O(1) space but O(nยฒ) time

๐Ÿ”ง Complex requirements, debugging needed

๐ŸŽ–๏ธ Vector with Index

โ˜…โ˜…โ˜…โ˜…โ˜†

Easy to modify and debug

๐Ÿ“Š Flexible operations, both-end access needed

๐Ÿ… Deque-Based

โ˜…โ˜…โ˜…โ˜…โ˜†

Good for complex scenarios

๐Ÿง‘โ€๐Ÿ’ป Code (Java)

class Solution {
    public static String maxSubseq(String s, int k) {
        int n = s.length(), toRemove = k;
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < n; i++) {
            while (toRemove > 0 && res.length() > 0 && res.charAt(res.length() - 1) < s.charAt(i)) {
                res.deleteCharAt(res.length() - 1);
                toRemove--;
            }
            res.append(s.charAt(i));
        }
        res.setLength(n - k);
        return res.toString();
    }
}

๐Ÿ Code (Python)

class Solution:
    def maxSubseq(self, s, k):
        n, toRemove, res = len(s), k, []
        for c in s:
            while res and toRemove and res[-1] < c:
                res.pop()
                toRemove -= 1
            res.append(c)
        return ''.join(res[:n - k])

๐Ÿง  Contribution and Support

For discussions, questions, or doubts related to this solution, feel free to connect on LinkedIn: ๐Ÿ“ฌ Any Questions?. Let's make this learning journey more collaborative!

โญ If you find this helpful, please give this repository a star! โญ


๐Ÿ“Visitor Count

Visitor counter

Last updated