代码随想录Day 11–13 | 栈与队列Part 2

文章介绍了栈在有效括号匹配、删除字符串相邻重复项和逆波兰表达式求值问题中的应用,以及队列在实现滑动窗口最大值和找出前K个高频元素问题中的解决方案。通过这些例子展示了栈和队列数据结构在算法设计中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


20. 有效的括号

思路:当遇到左括号时将对应的右括号压入栈中,当遇到非左括号时,若栈为空或栈顶对应字符与当前字符不同,则返回false。

class Solution {
public:
    bool isValid(string s) {
        stack<char> stk;
        for (int i = 0; i < s.size(); i++){
            if (s[i] == '{') stk.push('}');
            else if (s[i] == '[') stk.push(']');
            else if (s[i] == '(') stk.push(')');
            else if (stk.empty() || s[i] != stk.top()) return false;
            else stk.pop();
        }
        if (!stk.empty()) return false;
        return true; 
    }
};

标准答案中在最开始先判断字符串的长度,若为奇数,则直接返回false。加入提前终止判断后程序开销大幅降低。另外,最后两行可以合并为一行:return st.empty()


1047. 删除字符串中的所有相邻重复项

思路:栈的一大功能就是在遍历某个容器时,记录上一个遍历的元素是什么。

class Solution {
public:
    string removeDuplicates(string s) {
        stack<char> stk;
        for (int i = 0; i < s.size(); i++){
            if (!stk.empty() && s[i] == stk.top())
                stk.pop();
            else
                stk.push(s[i]);
        }
        string result = "";
        while (!stk.empty()) {
            result += stk.top();
            stk.pop();
        }
        reverse (result.begin(), result.end()); 
        return result;
    }
};

此外,在由栈生成字符串的代码实现方面,学习string的初始化、拼接时使用加和操作,并复习reverse函数的使用方法。

上面代码的效率不是最优的,在力扣官方解答中,直接使用了字符串自带的push_back、pop_back函数:

class Solution {
public:
    string removeDuplicates(string s) {
        string stk;
        for (char ch : s) {
            if (!stk.empty() && stk.back() == ch) {
                stk.pop_back();
            } else {
                stk.push_back(ch);
            }
        }
        return stk;
    }
};

150. 逆波兰表达式求值

思路:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> stk; 
        for (int i = 0; i < tokens.size(); i++) {
            if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
                int num1 = stk.top();
                stk.pop();
                int num2 = stk.top();
                stk.pop();
                if (tokens[i] == "+") stk.push(num2 + num1);
                if (tokens[i] == "-") stk.push(num2 - num1);
                if (tokens[i] == "*") stk.push(num2 * num1);
                if (tokens[i] == "/") stk.push(num2 / num1);
            } else {
                stk.push(stoi(tokens[i]));
            }
        }
        int result = stk.top();
        stk.pop();
        return result;
    }
};

这里调用了现成的函数来实现字符串向整型的转化,根据具体转化的类型int、long、long long分别有对应的库函数stoi()、stol()、stoll()。


队列

239. 滑动窗口最大值

思路:一开始的思路是维护一个最大值的索引,每次入队的值和最大值做比较,如果最大值索引超出窗口范围,则重新排序。这种做法的问题是当最大值超出索引范围后,定位新的最大值仍需要重新排序。在数组是单调递减的情况下,每一次都需要重新排序获取最大值下标,时间复杂度和暴力解法相同,为O(nk)。

那么如何在弹出最大值之后确定下一个最大值对应的索引位置?此时需要利用的一个关键性质是:对于滑动窗口里的两元素nums[i]和nums[j],如果 i < j 且 nums[i] < nums[j],则 i 必不可能是最大值对应的索引。也就是说,每次新来的元素并不需要和现有窗口里所有的元素做比较,只需要和有希望未来成为最大值的元素做比较,并在这些元素中淘汰掉比它小的元素。使用这种思路构建队列,储存的是元素的索引值,索引是单调递增的,而其在数组里对应的值是单调递减的。这种队列称为单调队列。

力扣官方的代码实现如下,其中deque是一个双向队列,C++中deque是stack和queue默认的底层实现容器。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        deque<int> q;
        for (int i = 0; i < k; ++i) {
            while (!q.empty() && nums[i] >= nums[q.back()]) {
                q.pop_back();
            }
            q.push_back(i);
        }

        vector<int> ans = {nums[q.front()]};
        for (int i = k; i < n; ++i) {
            while (!q.empty() && nums[i] >= nums[q.back()]) {
                q.pop_back();
            }
            q.push_back(i);
             //这里判断当下最大元素是否超出窗口范围,力扣官方给的是while判断,实际上由于for循环每次只推进一格,用if判断一次即可。
            while (q.front() <= i - k) {
                q.pop_front();
            }
            ans.push_back(nums[q.front()]);
        }
        return ans;
    }
};

347. 前 K 个高频元素

思路:基本思路是使用unordered_map统计各元素出现的频次,再想办法将这些频次排序,取前k个元素返回。总的时间复杂度为O(nlgn)。而实际上题中只需要知道前k个有哪些,并不需要知道具体的大小顺序,所以可以借助一个大小为k的小顶堆进行排序。

力扣官方的代码如下,需要进一步学习小顶堆的优先队列实现。

class Solution {
public:
    static bool cmp(pair<int, int>& m, pair<int, int>& n) {
        return m.second > n.second;
    }

    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> occurrences;
        for (auto& v : nums) {
            occurrences[v]++;
        }

        // pair 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(&cmp)> q(cmp);
        for (auto& [num, count] : occurrences) {
            if (q.size() == k) {
                if (q.top().second < count) {
                    q.pop();
                    q.emplace(num, count);
                }
            } else {
                q.emplace(num, count);
            }
        }
        vector<int> ret;
        while (!q.empty()) {
            ret.emplace_back(q.top().first);
            q.pop();
        }
        return ret;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值