栈(Stack)

堆(Heap or Priority Queue)、栈(Stack)、队列(Queue)、哈希表类(Hashmap、Hashset)

基础知识

各个数据结构的基本原理,增删查改复杂度。

Stack题目

题目:Leetcode 232. Implement Queue using Stacks

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作( pushpoppeekempty):

实现MyQueue类:

  • void push(int x)将元素 x推到队列的末尾
  • int pop()从队列的开头移除并返回元素
  • int peek()返回队列开头的元素
  • boolean empty()如果队列为空,返回 true;否则,返回 false

说明:

  • 你只能使用标准的栈操作。也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)模拟一个栈,只要是标准的栈操作。

进阶:

  • 你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。

示例:

输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
题解
class MyQueue {
public:
	/** Initialize your data structure here. */
	stack<int> stack1;
	stack<int> stack2;

	MyQueue() {
		
	}

	/** Push element x to the back of queue. */
	void push(int x) {
		stack1.push(x);
	}

	/** Removes the element from in front of queue and returns that element. */
	int pop() {
		while (!stack1.empty()) {
			stack2.push(stack1.top());
			stack1.pop();
		}
		int num = stack2.top();
		stack2.pop();
		while (!stack2.empty()) {
			stack1.push(stack2.top());
			stack2.pop();
		}
		return num;
	}

	/** Get the front element. */
	int peek() {
		while (!stack1.empty()) {
			stack2.push(stack1.top());
			stack1.pop();
		}
		int num = stack2.top();
		while (!stack2.empty()) {
			stack1.push(stack2.top());
			stack2.pop();
		}
		return num;
	}

	/** Returns whether the queue is empty. */
	bool empty() {
		return stack1.empty();
	}
};
题目:Leetcode 150. Evaluate Reverse Polish Notation

根据 逆波兰表示法,求表达式的值。

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

说明:

  • 整数除法只保留整数部分。
  • 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

示例 1:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

示例 2:

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

示例 3:

输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:
该算式转化为常见的中缀算术表达式为:
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

提示:

  • 1 <= tokens.length <= 104
  • tokens[i]要么是一个算符("+""-""*""/"),要么是一个在范围[-200, 200]内的整数

逆波兰表达式:

逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

  • 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 )
  • 该算式的逆波兰表达式写法为( ( 1 2 + ) ( 3 4 + ) * )

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成1 2 + 3 4 + *也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
题解

int atoi(const char *str); 把参数str所指向的字符串转换为一个int型整数
const char *c_str(); 返回一个指向正规C字符串的指针常量, 内容与本string串相同。

class Solution {
public:
	bool isNumber(string& token) {
		if (token == "+" || token == "-" || token == "*" || token == "/") {
			return false;
		}
		return true;
	}

	int evalRPN(vector<string>& tokens) {
		stack<int> stk;
		for (int i = 0; i < tokens.size(); i++) {
			string token = tokens[i];
			if (isNumber(token)) {
				// int atoi(const char *str); 把参数str所指向的字符串转换为一个int型整数
				// const char *c_str(); 返回一个指向正规C字符串的指针常量, 内容与本string串相同
				// 下面两行代码等同于:stk.push(atoi(token.c_str()));
				const char *str = &token[0];
				stk.push(atoi(str));
			} else {
				int num1 = stk.top();
				stk.pop();
				int num2 = stk.top();
				stk.pop();
				switch (token[0]) {
				case '+': stk.push(num1 + num2); break;
				case '-': stk.push(num2 - num1); break;
				case '*': stk.push(num1 * num2); break;
				case '/': stk.push(num2 / num1); break;
				}
			}
		}
		return stk.top();
	}
};
题目:Leetcode 224. Basic Calculator II (I, II, III, IV)

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

示例 1:

输入:s = "1 + 1"
输出:2

示例 2:

输入:s = " 2-1 + 2 "
输出:3

示例 3:

输入:s = "(1+(4+5+2)-3)+(6+8)"
输出:23

提示:

  • 1 <= s.length <= 3 * 105
  • s 由数字、'+''-''('')'、和 ' ' 组成
  • s 表示一个有效的表达式
题解

​建立两个栈(st_num和st_signs),来存储遇到括号前的符号sign(初始值为1,代表正,-1代表负),和括号外面已经算过的结果ans(初始值值为0),从栈顶到栈底依次是从内层到外层的结果。
扫描字符串,遇到空格直接跳过,遇到正负号则更新正负号,遇到左括号,将当前的结果和符号存入对应的栈中,遇到右括号,更新运算结果。
​为什么要这样做?
假设没有括号的情况下,我们可以直接在当前得到的结果上累加。但是有括号就不同了,我们要先把之前算过的结果存起来,然后再单独算计算括号里面的结果,直到遇到反括号,将当前括号内的结果(当前括号内的结果是要包括括号前的符号的)和上一层的结果(上次的ans)加起来,上一层的结果即为st_num的栈顶元素。

作者:maple-z
链接:https://leetcode-cn.com/problems/basic-calculator/solution/tu-jie-shuang-zhan-suan-fa-by-maple-z-qe2x/

在这里插入图片描述

class Solution {
     public int calculate(String s) {
        int ans = 0;
        char[] str = s.toCharArray();
        int len = str.length;
        Stack<Integer> st_num = new Stack<>();
        Stack<Integer> st_signs = new Stack<>();
        int sign = 1;//正负号,运算符号
        for(int i = 0; i<len; i++){
            if(str[i] == ' ') {
                continue;
            }
            if(str[i] == '+' || str[i] == '-') {
                sign = str[i] == '+' ? 1 : -1;
            } else if(str[i] >= '0' && str[i] <= '9') {//数字
                int num = str[i] - '0';
                while(i < len-1 && str[i+1] >= '0' && str[i+1] <= '9') {//将这个数字找完
                    num = num * 10 + (str[++i] - '0');
                }
                ans += sign * num;
            }else if(str[i] == '('){//左括号,暂存结果
                st_num.push(ans);
                st_signs.push(sign);
                ans = 0;
                sign = 1;
            } else {
                ans = st_num.pop() + ans * st_signs.pop();//右括号更新结果
            }
        }
        return ans;
    }
}
题目:Leetcode 20. Valid Parentheses

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

有效字符串需满足:

​ 1、左括号必须用相同类型的右括号闭合。
​ 2、左括号必须以正确的顺序闭合。

示例 1:

输入:s = "()"
输出:true

示例 2:

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

示例 3:

输入:s = "(]"
输出:false

示例 4:

输入:s = "([)]"
输出:false

示例 5:

输入:s = "{[]}"
输出:true
题解
class Solution {
public:
    bool isValid(string s) {
        int n = s.size();
        if (n % 2 == 1) {
            return false;
        }

        unordered_map<char, char> pairs = {
            {')', '('},
            {']', '['},
            {'}', '{'}
        };
        stack<char> stk;
        for (char ch: s) {
            if (pairs.count(ch)) { // pair中存在key = ch
                if (stk.empty() || stk.top() != pairs[ch]) {
                    return false;
                }
                stk.pop();
            } else {
                stk.push(ch); // 后遇到的左括号放入栈顶
            }
        }
        return stk.empty();
    }
};
题目:Leetcode 1472. Design Browser History

你有一个只支持单个标签页的浏览器,最开始你浏览的网页是homepage,你可以访问其他的网站url,也可以在浏览历史中后退steps步或前进steps步。

请你实现BrowserHistory类:

  • BrowserHistory(string homepage) ,用 homepage 初始化浏览器类。

  • void visit(string url) 从当前页跳转访问 url 对应的页面 。执行此操作会把浏览历史前进的记录全部删除。

  • string back(int steps)在浏览历史中后退steps步。如果你只能在浏览历史中后退至多x步且steps> x,那么你只后退x步。请返回后退至多steps步以后的url

  • string forward(int steps)在浏览历史中前进steps步。如果你只能在浏览历史中前进至多x步且 steps > x,那么你只前进x步。请返回前进至多steps步以后的url

示例:

输入:
["BrowserHistory","visit","visit","visit","back","back","forward","visit","forward","back","back"]
[["leetcode.com"],["google.com"],["facebook.com"],["youtube.com"],[1],[1],[1],["linkedin.com"],[2],[2],[7]]
输出:
[null,null,null,null,"facebook.com","google.com","facebook.com",null,"linkedin.com","google.com","leetcode.com"]

解释:
BrowserHistory browserHistory = new BrowserHistory("leetcode.com");
browserHistory.visit("google.com");       // 你原本在浏览 "leetcode.com" 。访问 "google.com"
browserHistory.visit("facebook.com");     // 你原本在浏览 "google.com" 。访问 "facebook.com"
browserHistory.visit("youtube.com");      // 你原本在浏览 "facebook.com" 。访问 "youtube.com"
browserHistory.back(1);                   // 你原本在浏览 "youtube.com" ,后退到 "facebook.com" 并返回 "facebook.com"
browserHistory.back(1);                   // 你原本在浏览 "facebook.com" ,后退到 "google.com" 并返回 "google.com"
browserHistory.forward(1);                // 你原本在浏览 "google.com" ,前进到 "facebook.com" 并返回 "facebook.com"
browserHistory.visit("linkedin.com");     // 你原本在浏览 "facebook.com" 。 访问 "linkedin.com"
browserHistory.forward(2);                // 你原本在浏览 "linkedin.com" ,你无法前进任何步数。
browserHistory.back(2);                   // 你原本在浏览 "linkedin.com" ,后退两步依次先到 "facebook.com" ,然后到 "google.com" ,并返回 "google.com"
browserHistory.back(7);                   // 你原本在浏览 "google.com", 你只能后退一步到 "leetcode.com" ,并返回 "leetcode.com"
题解

使用一个栈记录浏览历史,使用一个 pos 指针记录当前网页在栈中的位置。每次 back 和 forward 操作都只更新 pos 指针。因为visit操作会把浏览历史前进的记录全部删除,所以每次 visit 先根据 pos 更新下栈顶指针,然后再将 url 入栈。

作者:Time-Limit
链接:https://leetcode-cn.com/problems/design-browser-history/solution/sha-dai-ma-bu-dai-ma-de-xian-shang-tu-by-time-limi/

img

img

visit.gif

/*
 思路:
    1.栈只用来做存储
    2.给栈定义一个访问指针
    3.通过访问指针进行快速跳转和数据删除
*/
class BrowserHistory {
public:
    int pos = -1;//当前网页在栈中的位置
    int top = 0;//栈顶初始位置
    string history[5001];
    BrowserHistory(string homepage){//用主页初始化浏览器
        visit(homepage);//可以直接调用下边的访问主页
    }
    
    void visit(string url) {//跳转访问url界面
        pos++;//指针上移
        top = pos;//栈顶下移,如果访问的是之前的网页则删除当前网页与目标网页之间的记录
        history[top++] = url;//把url界面放在栈顶并把栈顶指针上移
    }
    
    string back(int steps) {//后退
        if(steps > pos) {//后退步数大于已经浏览过的网页数
            steps = pos;//说明只能后退这么多,就只让她后退到首页
        }
        //后退步数大于已经浏览过的网页数时,pos = pos - steps
        pos -= steps;
        return history[pos];
    }
    
    string forward(int steps) {//前进
        steps = min(steps, top - pos - 1);//选择要前进的步数和可以前进的步数两者的最小值
        //pos = pos + steps ,pos指向的是现在浏览的网页,加上要前进的步数,就是要返回的网页
        pos += steps;
        return history[pos];//返回pos所指向的网页
    }
};

题目:Leetcode 1209. Remove All Adjacent Duplicates in String II

给你一个字符串s「k倍重复项删除操作将会从s中选择k个相邻且相等的字母,并删除它们,使被删去的字符串的左侧和右侧连在一起。

你需要对s重复进行无限次这样的删除操作,直到无法继续为止。

在执行完所有删除操作后,返回最终得到的字符串。

本题答案保证唯一。

示例 1:

输入:s = "abcd", k = 2
输出:"abcd"
解释:没有要删除的内容。

示例 2:

输入:s = "deeedbbcccbdaa", k = 3
输出:"aa"
解释: 
先删除 "eee" 和 "ccc",得到 "ddbbbdaa"
再删除 "bbb",得到 "dddaa"
最后删除 "ddd",得到 "aa"

示例 3:

输入:s = "pbbcggttciiippooaais", k = 2
输出:"ps"
题解

方法1:暴力法

  1. 记录字符串的长度。
  2. 遍历字符串:
    1. 如果当前字符与前一个相同,计数器加 1。
      1. 否则,重置计数器为 1。
    2. 如果计数器等于k,删除这k个字符。
    3. 如果字符串的长度被改变,从头开始重新遍历字符串

img

string removeDuplicates(string s, int k) {
	int length = -1;
	int count = 1;
	while (length != s.size()) { // 字符串s中没有连续k个相同字符时,跳出while循环
		length = s.size();
		for (int i = 0; i < s.size(); ++i) {
			if (i == 0 || s[i] != s[i - 1]) {
				count = 1;
			} else {
				count++;
				if (count == k) {
					s.erase(i - k + 1, k); // 删除从i-k+1开始的连续k个元
					break;
				}
			}
		}
	}
	return s;
}

方法2:记忆计数

方法1是给可能连续k个相同的首个字符计数,只要s[i - 1] != s[i],就重新寻找下一个可能连续k个相同的首个字符,删除连续k个相同的字符后需要重新从头遍历。

方法2是给每个字符计数,不必每次删除完字符后从头开始重新为每个字符计数。

  1. 初始长度为n的数组counts
  2. 遍历字符串:
    1. 如果当前字符与上一个字符相等,令counts[i] = counts[i - 1] + 1
      1. 否则,令counts[i] = 1
    2. 如果counts[i] = k,删除这k个字符,令i = i - k

复杂度分析

​ 时间复杂度:O(n),其中n是字符串长度。每个字符只处理一次。

​ 空间复杂度:O(n)vector空间。

string removeDuplicates(string s, int k) {
	vector<int> count(s.size());
	for (int i = 0; i < s.size(); i++) {
		if (i == 0 || s[i] != s[i - 1]) {
			count[i] = 1;
		} else {
			count[i] = count[i - 1] + 1;
			if (count[i] == k) {
				s.erase(i - k + 1, k); 素
				i = i - k;
			}
		}
	}
	return s;
}

方法3:栈

当前字符与前一个不同时,往栈中压入 1。否则栈顶元素加 1

复杂度分析

​ 时间复杂度:O(n),其中n是字符串长度。每个字符只处理一次。

​ 空间复杂度:O(n)vector空间。

img

string removeDuplicates(string s, int k) {
	stack<int> stack;
	for (int i = 0; i < s.size(); i++) {
		if (i == 0 || s[i] != s[i - 1]) {
			stack.push(1);
		} else {
			//int top = stack.top() + 1;
			//stack.pop();
			//stack.push(top);
			stack.top()++;

			if (stack.top() == k) {
				stack.pop(); // 连续k个相同的字符删除,该字符的计数也删除
				s.erase(i - k + 1, k);
				i = i - k;
			}
		}
	}
	return s;
}

方法4:双指针

该方法由lee215提出,使用双指针可以优化方法二和三中的字符串操作。使用快慢指针复制字符。每次需要删除 k 个元素时,只需要将慢指针回退 k 个位置。

  1. 初始慢指针 j 等于 0。
  2. 使用快指针 i 遍历字符串:
    1. 令 s[i] = s[j]。
    2. 如果 s[j] = s[j - 1],则栈顶元素加 1。
      1. 否则,栈中压入 1。
    3. 如果计数器等于 k,j = j - k,并弹出栈顶元素。
  3. 返回字符串的前 j 个字符

复杂度分析

​ 时间复杂度:O(n),其中n是字符串长度。每个字符只处理一次。

​ 空间复杂度:O(n)vector空间。

img

string removeDuplicates(string s, int k) {
    auto j = 0; // 慢指针
    stack<int> counts;
    for (auto i = 0; i < s.size(); i++, j++) {
        s[j] = s[i];
        if (j == 0 || s[j] != s[j - 1]) {
            counts.push(1);
        } else {
            counts.top()++;
            if(counts.top() == k) {
            	counts.pop();
            	j = j - k;   
            }
        }
    }
    return s.substr(0, j);
}
题目:Leetcode 1249. Minimum Remove to Make Valid Parentheses

给你一个由()和小写字母组成的字符串s

你需要从字符串中删除最少数目的(或者)(可以删除任意位置的括号),使得剩下的「括号字符串」有效。

请返回任意一个合法字符串。

有效「括号字符串」应当符合以下 任意一条 要求:

  • 空字符串或只包含小写字母的字符串
  • 可以被写作 ABA 连接 B)的字符串,其中 AB 都是有效「括号字符串」
  • 可以被写作 (A) 的字符串,其中 A 是一个有效的「括号字符串」

示例 1:

输入:s = "lee(t(c)o)de)"
输出:"lee(t(c)o)de"
解释:"lee(t(co)de)" , "lee(t(c)ode)" 也是一个可行答案。

示例 2:

输入:s = "a)b(c)d"
输出:"ab(c)d"

示例 3:

输入:s = "))(("
输出:""
解释:空字符串也是有效的

示例 4:

输入:s = "(a(b(c)d)"
输出:"a(b(c)d)"

提示:

  • 1 <= s.length <= 10^5
  • s[i] 可能是 '('')' 或英文小写字母
题解

目的:将多余的'('')'改为‘ ’,然后重新拼接。

string minRemoveToMakeValid(string s) {
    stack<int> stack;
    for (int i = 0; i < s.size(); i++) {
        if (s[i] == '(') {
            // 遇到'(',就将其索引入栈
            stack.push(i); 
        }
        if (s[i] == ')') {
            // 将多余的')'改为' ':遇到')'之前还没出现'(' 或是 栈中已经没有与')'配对的'('
            if (stack.empty()) { 
                s[i] = ' ';
            } else {
                // ')'与'('配对一次,栈顶的'('的索引移除一次
                stack.pop(); 
            }
        }
    }
    while (!stack.empty()) { // 将多余的'('改为' '
        s[stack.top()] = ' ';
        stack.pop();
    }
    string ans;
    for (int i = 0; i < s.size(); i++) {
        if (s[i] != ' ') {
            ans += s[i];
        }
    }
    return ans;
}
题目:Leetcode 735. Asteroid Collision

给定一个整数数组asteroids,表示在同一行的行星。

对于数组中的每一个元素,其绝对值表示行星的大小,正负表示行星的移动方向(正表示向右移动,负表示向左移动)。每一颗行星以相同的速度移动。

找出碰撞后剩下的所有行星。碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞。

示例 1:

输入:asteroids = [5,10,-5]
输出:[5,10]
解释:10 和 -5 碰撞后只剩下 10 。 5 和 10 永远不会发生碰撞。

示例 2:

输入:asteroids = [8,-8]
输出:[]
解释:8 和 -8 碰撞后,两者都发生爆炸。

示例 3:

输入:asteroids = [10,2,-5]
输出:[10]
解释:2 和 -5 发生碰撞后剩下 -5。10 和 -5 发生碰撞后剩下 10 。

示例 4

输入:asteroids = [-2,-1,1,2]
输出:[-2,-1,1,2]
解释:-2 和 -1 向左移动,而 1 和 2 向右移动。由于移动方向相同的行星不会发生碰撞,所以最终没有行星发生碰撞。
题解

只有一种情况会发生碰撞:左星大于0,右星小于0。

class Solution {
public:
    vector<int> asteroidCollision(vector<int>& asteroids) {
        vector<int> res;
        res.push_back(asteroids[0]);

        for (int i = 1; i < asteroids.size(); i++) {
            bool flag = true; // flag为true时asteroids[i]入栈
            // 只有栈顶元素为正并且将要入栈的元素为负时才会发生碰撞
            while (!res.empty() && flag && res.back() > 0 && asteroids[i] < 0) {
                // 绝对值小于栈顶元素:不弹出栈顶元素,asteroids[i]不入栈
                if (-asteroids[i] < res.back()) {
                    flag = false;
                }
                // 绝对值大于栈顶元素:弹出栈顶元素,继续比较下一个栈顶元素
                else if (-asteroids[i] > res.back()) { 
                    res.pop_back();
                }
                // 绝对值等于栈顶元素:弹出栈顶元素,asteroids[i]不入栈
                else if (-asteroids[i] == res.back()) {
                    res.pop_back();
                    flag = false;
                }
            }
            if (flag)
                res.push_back(asteroids[i]);
        }
        
        return res;
    }
};

作者:sscicada
链接:https://leetcode-cn.com/problems/asteroid-collision/solution/csi-lu-qing-xi-by-sscicada-ezor/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值