leetcode刷题 (8.30) 栈和队列

1. 括号匹配

20

题目:给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

输入:s = "()[]{}"
输出:true
输入:s = "{[]}"
输出:true

思路:左push右pop

字符串中为左括号时,右括号入栈比直接左括号入栈要简单,因为到时候只要匹配栈顶元素相不相等即可。

匹配的情况为:字符串遍历完,栈也空了。

有三种不匹配的情况,一定要考虑全:

  1. 字符串中左边括号多余——字符串完,栈非空。
    在这里插入图片描述

  2. 括号不多余,但是类型不对应——栈中没有字符串要匹配的内容。
    在这里插入图片描述

  3. 字符串中右边括号多余——栈空,字符串未遍历完。
    在这里插入图片描述

笔记:Python有两种方法:

  1. 同C++仅用栈
  2. 一种栈+字典

C++

class Solution {
public:
    bool isValid(string s) {
        // 首先把s长度为奇数的淘汰
        if (s.size() % 2 != 0) return false;
        stack<char> st;
        for (int i = 0; i < s.size(); i++){
            if (s[i] == '(') st.push(')');
            else if (s[i] == '[') st.push(']');
            else if (s[i] == '{') st.push('}');
            // 2 or 3: 字符串没完,栈空   类型不匹配
            else if (st.empty() || st.top() != s[i]) return false;
            else st.pop();
        }
        // 都空就匹配,栈非空就是1
        return st.empty();
    }
};

Python

# 方法一,仅使用栈,更省空间
class Solution:
    def isValid(self, s: str) -> bool:
        stack = []

        for i in s:
            if i == '(':
                stack.append(')')
            elif i == '[':
                stack.append(']')
            elif i == '{':
                stack.append('}')
            elif not stack or stack[-1] != i:
                return False
            else:
                stack.pop()
        
        return True if not stack else False
# 方法二,使用字典
class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        mapping = {
            '(': ')',
            '[': ']',
            '{': '}'
        }

        for i in s:
            if i in mapping.keys():
                stack.append(mapping[i])
            elif not stack or stack[-1] != i:
                return False
            else:
                stack.pop()
        return True if not stack else False

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

1047

题目:给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

输入:"abbaca"
输出:"ca"

思路:消消乐,i与栈顶元素相同就消除,不同就入栈。注意C++最后return的值还要新建一个string来放,不能直接return stack。

笔记:C++ string front()函数表示获取字符串第一个字符,back()函数表示获取字符串最后一个字符.

注:Python中list是有pop()函数的,而且还返回值。

C++

class Solution {
public:
    string removeDuplicates(string s) {
        stack<char> st;
        for (int i = 0; i < s.size(); i++){
            if (!st.empty() && st.top() == s[i]){
                st.pop();
            }
            else st.push(s[i]);           
        }

        string result = "";
        while(!st.empty()){
            result += st.top();
            st.pop();
        }
        reverse (result.begin(), result.end());
        return result;
    }
};

法二,不创建栈,直接用string做栈

class Solution {
public:
    string removeDuplicates(string S) {
        string result;
        for(char s : S){
            if(!result.empty() && result.back() == s){
                // 从string尾pop()
                result.pop_back();
            }
            else {
                result.push_back(s);
            }
        }
        return result;
    }
};

Python

class Solution:
    def removeDuplicates(self, s: str) -> str:
        res = []
        for i in s:
            if res and res[-1] == i:
                res.pop()
            else:
                res.append(i)
        return "".join(res)

Python不用栈的话,可以用双指针,fast控制遍历list(s),slow用来相同就回退。

class Solution:
    def removeDuplicates(self, s: str) -> str:
        res = list(s)
        slow = fast = 0
        length = len(res)

        while fast < length:
            # 如果一样直接换,不一样会把后面的填在slow的位置
            res[slow] = res[fast]
            
            # 如果发现和前一个一样,就退一格指针
            if slow > 0 and res[slow] == res[slow - 1]:
                slow -= 1
            else:
                slow += 1
            fast += 1
            
        return ''.join(res[0: slow])

3. 逆波兰(后缀)表达式求值

150

题目:有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

笔记

C++
stoi()函数可以将string类型转为int型,负数也可以,遇到任何不是数字的字符就丢弃后面,所以会舍弃小数。

Python

  1. Python中isdigit()函数可以直接判断字符串是不是数字,但是遇到负数返回False,因为 ‘-’ 它看做字符。

  2. 这题要注意看题目:两个整数之间的除法只保留整数部分。
    不是四舍五入,但又不能直接用//,因为负数-0.04会被算为-1,但应该为0.
    正确做法:int(b / a)

注意:Python中除法
//: 表示地板除(不管小数):5 // 3 = 1 (5 ÷ 3 = 1.6666666666666667)
/: 结果为浮点数
round(a/b): 表示四舍五入除

  1. Python 的 eval()函数用于去掉“”然后执行转化后的语句。
  2. Python中 f'{}' 相当于format{}函数。

C++

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for (int i = 0; i < tokens.size(); i++) {
            if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
                int num1 = st.top();
                st.pop();
                int num2 = st.top();
                st.pop();
                if (tokens[i] == "+") st.push(num2 + num1);
                if (tokens[i] == "-") st.push(num2 - num1);
                if (tokens[i] == "*") st.push(num2 * num1);
                if (tokens[i] == "/") st.push(num2 / num1);
            } else {
                st.push(stoi(tokens[i]));
            }
        }
        int result = st.top();
        st.pop(); // 把栈里最后一个元素弹出(其实不弹出也没事)
        return result;
    }
};

Python

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []

        for i in tokens:
            if i not in ['+','-','*','/']:
                stack.append(i)
            else:
                num2 = int(stack.pop())
                num1 = int(stack.pop())
                if i == '+':
                    stack.append(num1 + num2)
                elif i == '-':
                    stack.append(num1 - num2)
                elif i == '*':
                    stack.append(num1 * num2)
                elif i == '/':
                    stack.append(int(num1 / num2))
        return int(stack[0])
class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for i in tokens:
            if i not in {"+", "-", "*", "/"}:
                stack.append(i)
            else:
                num2, num1 = stack.pop(), stack.pop()
                stack.append(
                    int(eval(f'{num1} {i} {num}'))   # 第一个出来的在运算符后面
                )
        return int(stack.pop()) # 如果一开始只有一个数,那么会是字符串形式的

4. 滑动窗口的最大值

239

题目:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

思路

为什么不可以设一个maxV的值,每次移动的时候对比一下nums[i]和maxV谁大,大就放到result里呢?

注意审题!题目要求的是当前窗口内的最大值,而上述方法若滑动时最大值被pop出去了,后面maxV仍然不会改。所以,要么暴力,每次都在窗口内找一遍,要么对队列进行排序。

单调队列
单调队列的 push 方法依然在队尾添加元素,但是要把前面比新元素小的元素都删掉

笔记

class deque {
    // 在队头插入元素 n
    void push_front(int n);
    // 在队尾插入元素 n
    void push_back(int n);
    // 在队头删除元素
    void pop_front();
    // 在队尾删除元素
    void pop_back();
    // 返回队头元素
    int front();
    // 返回队尾元素
    int back();
}

C++

class Solution {
private:
    // 单调队列
    class MyQueue{
    public:
        deque<int> que;
        // 实际上, 只有当要弹最大值时, 才会真正用到pop(), 比最大值小的数之前push()的时候就弹出去了
        void pop(int x){
            if (!que.empty() && x == que.front()){
                que.pop_front();   //双端队列,从前端弹出
            }
        }

        // 保持push()进来后que是从大到小的顺序,即把x前比它小的数全部pop出去
        void push(int x){
            while (!que.empty() && x > que.back()){
                que.pop_back();  // 因为这里x是和队尾比,所以pop也是先pop队尾
            }
            que.push_back(x);
        }

        int front(){
            return que.front();
        }
    };

public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;
        // 先把前k个元素push进来
        for(int i = 0; i < k; i++){
            que.push(nums[i]);
        }
        result.push_back(que.front());
        for(int i = k; i < nums.size(); i++){
            que.pop(nums[i - k]);  // 滑动窗口移除最前面元素
            que.push(nums[i]);
            result.push_back(que.front());
        }
        return result;
    }
};

Python

class MyQueue:
    def __init__(self):
        self.queue = []

    def pop(self, x):
        if self.queue and x == self.queue[0]:
            self.queue.pop(0)

    def push(self, x):
        while self.queue and x > self.queue[-1]:
            self.queue.pop()
        self.queue.append(x)

    def front(self):
        return self.queue[0]

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        que = MyQueue()
        result = []
        for i in range(k):
            que.push(nums[i])
        result.append(que.front())
        for i in range(k, len(nums)):
            que.pop(nums[i - k])
            que.push(nums[i])
            result.append(que.front())
        return result

5. 前 K 个高频元素

347

题目:给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]          

思路

笔记

Python中的dict相当于C++中的unordered_map

d[k]=d.get(k,0)+1

用于统计字典d中k键出现的次数,默认值为0。
get()函数原本用于获得k键对应的值。

Python中heapq库是最小堆

# 往堆里面填入值
heapq.heappush(heap, item)
# 将列表转换为堆
heapq.heapify(list)
# 将堆顶元素pop出来
heapq.heappop(heap)

C++ 优先队列

priority_queue<Type, Container, Functional>;

Type是要存放的数据类型

Container是实现底层堆的容器,必须是数组实现的容器,如vector、deque

Functional是比较方式/比较函数/优先级

priority_queue<Type>;

此时默认的容器是vector,默认的比较方式是大顶堆

//小顶堆
priority_queue <int,vector<int>,greater<int> > q;
//大顶堆
priority_queue <int,vector<int>,less<int> >q;
//默认大顶堆
priority_queue<int> a;

自定义比较方式
当数据类型并不是基本数据类型,而是自定义的数据类型时,就不能用greater或less的比较方式了,而是需要自定义比较方式

在此假设数据类型是自定义的水果:

struct fruit
{
	string name;
	int price;
};

有两种自定义比较方式的方法,如下

  1. 重载运算符
    重载”<”
    注意:从大到小排建小顶堆,从小到大排建大顶堆。
  • 若希望水果价格高为优先级高,则
//大顶堆
struct fruit
{
	string name;
	int price;
	friend bool operator < (fruit f1,fruit f2)
	{
		return f1.peice < f2.price;
	}
};
  • 若希望水果价格低为优先级高
//小顶堆
struct fruit
{
	string name;
	int price;
	friend bool operator < (fruit f1,fruit f2)
	{
		return f1.peice > f2.price;  //此处是>
	}
};
  1. 仿函数
    高优先级
//大顶堆
struct myComparison
{
	bool operator () (fruit f1,fruit f2)
	{
		return f1.price < f2.price;
	}
};

C++

class Solution {
public:
    // 小顶堆
    class mycomparison{
    public:
        bool operator()(pair<int, int> &lh, pair<int, int> &rh){
            return lh.second > rh.second;
        }
    };

    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 1.统计元素出现的频率
        unordered_map<int, int> map;   // map<nums[i], 出现的次数>
        // 也可以写为:for(auto& c:nums)
        for(int i = 0; i < nums.size(); i++){
            map[nums[i]]++;
        }

        // 2.创建优先队列
        priority_queue<pair<int,int>, vector<pair<int,int>>, mycomparison> pri_que;

        // 3.遍历map中的元素
        // 不管大小,先入队,优先队列会自己排序
        // 若队列元素超过k,把栈顶元素弹出
        for (unordered_map<int,int>::iterator it = map.begin(); it != map.end(); it++){
            pri_que.push(*it);
            if(pri_que.size() > k){
                pri_que.pop();
            }
        }

        //另一种写法:
        //for(auto& a:map){
        //   pri_que.push(a);
        //    if(pri_que.size() > k){
        //        pri_que.pop();
        //    }
        //}

        // 4.倒序输出结果
        vector<int> result(k);
        for(int i = k - 1; i >= 0; i--){
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;
    }
};

Python

import heapq
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        # 统计各元素的频率
        freq = {}
        for i in range(len(nums)):
            freq[nums[i]] = freq.get(nums[i], 0) + 1

        # 定义小顶堆
        pri_que = []

        # 遍历字典,填入小顶堆
        for i, f in freq.items():
            # 注意:这里是按频率从大到小来排的,所以元组用(f,i)
            heapq.heappush(pri_que, (f, i)) 
            if len(pri_que) > k:
                heapq.heappop(pri_que)
        
        # 倒序输出结果
        result = [0] * k
        for i in range(k-1, -1, -1):
            result[i] = heapq.heappop(pri_que)[1]
        return result
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值