「程式碼隨想錄演算法訓練營」第十天 | 棧與佇列 part2

云雀AC了一整天發表於2024-07-13

150. 逆波蘭表示式求值

題目連結:https://leetcode.cn/problems/evaluate-reverse-polish-notation/
題目難度:中等
文章講解:https://programmercarl.com/0150.逆波蘭表示式求值.html
影片講解:https://www.bilibili.com/video/BV1kd4y1o7on
題目狀態:多次修改 bug 後透過

個人思路:

透過進行解決。
先判斷佇列中的元素是否為數字型別。若為數字型別,壓入棧;若不是數字型別,則肯定是運算子,使用switch結構進行運算,將棧中的頭兩個元素彈出,進行運算子運算,運算後將結果在壓入棧中。最後返回棧頂元素。

修改 bug 的過程

  1. 棧的空間:當棧內有兩個或兩個以上的元素的時候才能進行運算子運算。
  2. 符號判斷:在判斷佇列元素是否為數字的時候,忘記還有負數這一項,需要先判斷元素的第一個元素是否為-且元素是否含有多個元素,若是,則從元素中的下一個元素判斷是否為數字;若不是,則直接判斷。

程式碼實現:

class Solution {
public:
    bool isNum(const string &s) {
        if(s[0] == '-' && s.size() > 1) {
            for(int i = 1; i < s.size(); ++i) {
                if(!isdigit(s[i])) return false;
            }
        } else if(s[0] == '-' && s.size() == 1) {
            return false;
        } else {
            for(auto &elem: s) {
                if(!isdigit(elem)) return false;
            }
        }
        return !s.empty();
    }

    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for(auto &token: tokens) {
            if(isNum(token)) {
                st.push(stoi(token));
            }
            else if(!st.empty() && st.size() >= 2){
                int op1 = st.top(); st.pop();
                int op2 = st.top(); st.pop();
                int res = 0;
                switch(token[0]) {
                    case '+': res = op2 + op1; break;
                    case '-': res = op2 - op1; break;
                    case '*': res = op2 * op1; break;
                    case '/': res = op2 / op1; break;
                }
                st.push(res);
            }
        }
        return st.top();
    }
};

239. 滑動視窗最大值

題目連結:https://leetcode.cn/problems/sliding-window-maximum/
題目難度:困難
文章講解:https://programmercarl.com/0239.滑動視窗最大值.html
影片講解:https://www.bilibili.com/video/BV1XS4y1p7qj
題目狀態:有思路,但敗在了超時上,學習到了單調佇列

思路:

一開始想,啊?困難題就這?根本難不倒我,最後卻在編譯的過程中敗在了超時上,而且毫無思路去最佳化,最後學習了單調數列,利用單調數列解決這個問題異常輕鬆。

先看動圖理解以下思路:

單調佇列的構建:

  • 首先建立一個MyQue類來實現單調函式,分別有三個函式:pop()push()front()
  • push():這個不是簡單的 push,當單調佇列中存在的元素小於要 push 的元素,就需要將它們都彈出佇列(使用 pop_back(),因為從佇列的back開始遍歷的),而把要 push 的元素 push 進來。此時,單調佇列中的隊頭就是 push 進來的最大元素。
  • pop():當要彈出佇列元素的時候,要彈出的元素小於隊頭元素時,表明這個元素早已在 push 的時候已經被彈出佇列,不需要進行任何操作,只有當彈出元素和隊頭元素相等的時候,才需要把隊頭元素彈出(使用pop_front(),因為隊頭在佇列的front處)。
  • front():這個就是返回單調佇列的隊頭元素,也是此時佇列中最大的一個元素。

透過構建的單調佇列實現滑動視窗中的最大值,視窗每滑動一次,就需要把對應的左邊元素從單調佇列中pop出來,在把對應的右邊的元素push到單調佇列中去。每滑動一次,就使用一個陣列去記錄單調佇列中的隊頭元素(因為這個元素時此時滑動視窗中的最大值),最後返回陣列即可。

程式碼實現:

class Solution {
public:
    class MyQueue {
    public:
        void pop(int value) {
            if(!_que.empty() && value == _que.front()) _que.pop_front();
        }
        void push(int value) {
            while(!_que.empty() && value > _que.back()) _que.pop_back();
            _que.push_back(value);
        }
        int front() {
            return _que.front();
        }
    private:
        deque<int> _que;
    };

    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue mq;
        vector<int> res;
        for(int i = 0; i < k; ++i) {
            mq.push(nums[i]);
        }
        res.push_back(mq.front());
        for(int i = k; i < nums.size(); ++i) {
            mq.pop(nums[i - k]);
            mq.push(nums[i]);
            res.push_back(mq.front());
        }
        return res;
    }
};

347. 前 K 個高頻元素

題目連結:https://leetcode.cn/problems/top-k-frequent-elements/
題目難度:中等
文章講解:https://programmercarl.com/0347.前K個高頻元素.html
影片講解:https://www.bilibili.com/video/BV1Xg41167Lz
題目狀態:有思路,透過 ChatGPT 最佳化程式碼後透過

個人思路:

建立一個map,來儲存字串中出現的每個元素對應的次數,然後對map的值進行排序,獲得前k個元素的鍵,前k個鍵就是字串中前k個高頻元素。

程式碼實現:

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        map<int, int> countMap;
        for(auto &num : nums) countMap[num]++;

        vector<pair<int, int>> freq(countMap.begin(), countMap.end());
        sort(freq.begin(), freq.end(), [](const pair<int, int> &a, const pair<int, int> &b){ return a.second > b.second; });

        vector<int> res;
        for(int i = 0; i < k; ++i) {
            res.push_back(freq[i].first);
        }
        return res;
    }
};

優先順序佇列的思想:

透過小頂堆記錄前k個頻率的元素,每次小頂堆會將最小的元素彈出,最後小頂堆裡積累的就是前k個最大元素。

程式碼實現(有點沒看懂):

class Solution {
public:
    // 小頂堆
    class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 要統計元素出現頻率
        unordered_map<int, int> map; // map<nums[i],對應出現的次數>
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 對頻率排序
        // 定義一個小頂堆,大小為k
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

        // 用固定大小為k的小頂堆,掃面所有頻率的數值
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { // 如果堆的大小大於了K,則佇列彈出,保證堆的大小一直為k
                pri_que.pop();
            }
        }

        // 找出前K個高頻元素,因為小頂堆先彈出的是最小的,所以倒序來輸出到陣列
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;

    }
};

相關文章