- 有效的括号
难度:简单
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
示例 1:
输入:s = “()”
输出:true
示例 2:
输入:s = “()[]{}”
输出:true
示例 3:
输入:s = “(]”
输出:false
示例 4:
输入:s = “([)]”
输出:false
示例 5:
输入:s = “{[]}”
输出:true
提示:
1 <= s.length <= 104
s 仅由括号 ‘()[]{}’ 组成
解题思路:
使用map集合以及栈,其中map的作用是先将有效括号对存储起来,然后依次遍历字符串:
(1)当遇到右括号时(来自map当中的key),弹出栈顶的左括号,两者比较,如果栈为空或者两者不相等说明不匹配,如果匹配,则将栈顶元素出栈
(2)当遇到左括号时,将其加入栈中
最后判断栈中的是否为空,如果是有效的括号,一定匹配,最后栈中一定为空
class Solution {
public boolean isValid(String s) {
int n=s.length();
if(n % 2 != 0){
return false;
}
Map<Character,Character> pairs=new HashMap<Character,Character>();
pairs.put(')','(');
pairs.put(']','[');
pairs.put('}','{');
Deque<Character> stack=new LinkedList<Character>();
for(int i=0;i<n;i++){
char ch=s.charAt(i);
if(pairs.containsKey(ch)){
if(stack.isEmpty() || stack.peek() != pairs.get(ch)){
return false;
}
stack.pop();
}else{
stack.push(ch);
}
}
return stack.isEmpty();
}
}
2 二叉树的中序遍历(难度:简单)
题目:给定一个二叉树的根节点 root ,返回它的 中序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
示例 4:
输入:root = [1,2]
输出:[2,1]
示例 5:
输入:root = [1,null,2]
输出:[1,2]
提示:
树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
解题思路:
递归方式:中序遍历(左子树–根节点—右子树)
写一个递归函数,然后:传入的左子树节点,将根节点加入结果集中,传入右子树节点
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<Integer>();
inorder(root,res);
return res;
}
public void inorder(TreeNode root,List<Integer> res){
if(root==null){
return ;
}
inorder(root.left,res);
res.add(root.val);
inorder(root.right,res);
}
}
非递归解法:
定义一个栈,将根节点压入栈中,然后判断左子树上的节点,若存在然后将其压入栈中,当不存在时将栈顶元素栈,然后重复。。。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
//定义一个栈,将根节点压入栈中,然后判断左子树上的节点,若存在然后将其压入栈中,当不存在时将栈顶元素栈,然后重复
List<Integer> res=new ArrayList<Integer>();
Deque<TreeNode> stack=new LinkedList<TreeNode>();
while(root != null || !stack.isEmpty()){
while(root != null){ //不停将根节点左子树上的节点加入栈中
stack.push(root);
root = root.left;
}
root=stack.pop();//弹出没有左子树节点
res.add(root.val);
root = root.right;//以当前节点的右子树root继续,,,
}
return res;
}
}
- 二叉树的前序遍历(难度:简单)
题目:给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
示例 4:
输入:root = [1,2]
输出:[1,2]
示例 5:
输入:root = [1,null,2]
输出:[1,2]
提示:
树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100
递归解法:前序遍历(根节点–左子树节点—右子树节点)
(1)写一个递归函数,先确定出口,当处理的root为null时,直接返回
(2)然后先将root加入结果集中,再递归处理左子树节点,最后递归 处理右子树节点
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Deque<TreeNode> stack =new LinkedList<TreeNode>();
preorder(root,res);
return res;
}
public void preorder(TreeNode root,List<Integer> res){
if(root == null){
return;
}
res.add(root.val);
preorder(root.left,res);
preorder(root.right,res);
}
}
非递归解法:
先将根节点加入结果集中,然后将其加入栈中,再去处理左节点,当左子节点为null时,弹出栈顶元素,并处理其右子节点。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Deque<TreeNode> stack =new LinkedList<TreeNode>();
while(root != null || !stack.isEmpty()){
while(root != null){
res.add(root.val);//先将该节点加入结果集中
stack.push(root);
root = root.left; //处理其左子节点
}//当左子节点为null时,跳出while,去处理栈中节点的右子节点
root=stack.pop();//当左子树节点为空时,弹出栈顶元素,因为已经将该元素加入结果集中了,直接处理其右子树节点即可
root=root.right;
}
return res;
}
}
- 二叉树的后序遍历(难度:简单)
题目:给定一个二叉树,返回它的 后序 遍历。
示例:
输入: [1,null,2,3]
1
2
/
3
输出: [3,2,1]
递归解法:后序遍历(左节点–右节点–根节点)
(1)写一个递归函数,先确定出口
(2)首先处理根节点的左子树,然后处理根节点的右子树,最后将根节点加入结果集中
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<Integer>();
afterorfer(root,res);
return res;
}
public void afterorfer(TreeNode root,List<Integer> res){
if(root==null){
return ;
}
afterorfer(root.left,res);
afterorfer(root.right,res);
res.add(root.val);
}
}
非递归解法:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<Integer>();
Deque<TreeNode> stack=new LinkedList<TreeNode>();
TreeNode prev = null;
while(root != null || !stack.isEmpty()){
while(root != null){
stack.push(root);//根节点入栈
root=root.left;//左子节点入栈
}
root = stack.pop();//左边所有子节已入栈,处理栈顶元素
if(root.right == null || root.right == prev){//如果栈顶元素无右子树右子树已处理
res.add(root.val); //将其加入结果集中
prev = root ; //后序用于判断是否已经处理过右子树
root = null;
}else{
stack.push(root); //如果栈顶元素的右子树不为空,将其加入栈中
root = root.right; //右子节点入栈
}
}
return res;
}
}
5.最小栈(难度:简单)
题目:设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
示例:
输入:
[“MinStack”,“push”,“push”,“push”,“getMin”,“pop”,“top”,“getMin”]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
解题思路:
初始化一个正常的栈,和一个用于始终存储最小值的栈
push:将值push进正常栈,最小值栈中push的值是:min(最小栈中栈顶元素,要入栈的值)
pop:分别将正常栈和最小值栈的元素使用pop()弹出即可
top:使用peek()获取正常栈中的栈顶元素
getMin:使用peek()获取最小值栈中的栈顶元素
class MinStack {
/** initialize your data structure here. */
//使用辅助栈来存储最小值
Deque<Integer> stack;
Deque<Integer> minStack;
public MinStack() {
stack = new LinkedList<Integer>();
minStack = new LinkedList<Integer>();
minStack.push(Integer.MAX_VALUE);
}
public void push(int val) {
stack.push(val);
minStack.push(Math.min(minStack.peek(),val));
}
public void pop() {
stack.pop();
minStack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
- 用队列实现栈(难度:简单)
题目:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通队列的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
注意:
你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
[“MyStack”, “push”, “push”, “top”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
class MyStack {
Deque<Integer> queue1;
Deque<Integer> queue2;
/** Initialize your data structure here. */
//准备两个队列:队列1和队列2,然后分三步来模拟栈
//第一步:每次将元素先放入队列2中
//第二步:将队列1中的元素依次出队加入队列2中
//第三步:交换队列1和队列2
public MyStack() {
queue1 =new LinkedList<Integer>();
queue2=new LinkedList<Integer>();
}
public void push(int x) {
queue2.offer(x);
while(!queue1.isEmpty()){
queue2.offer(queue1.poll());
}
Deque<Integer> temp=queue1;
queue1=queue2;
queue2=temp;
}
public int pop() {
return queue1.poll();
}
public int top() {
return queue1.peek();
}
public boolean empty() {
return queue1.isEmpty();
}
}
- 用栈实现队列(难度:简单)
题目:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
push:将元素入栈1即可
pop:判断栈2是否为空,如果为空将栈1中的元素全部出栈,然后压入栈2,这样入栈1的顺序是12345,出栈1的顺序是54321,入栈2的顺序是54321,这样出栈2的顺序即为12345,这样就和入栈1时一致,也就模拟了出队列
peek:与pop()原理一致
empty():判断两个栈是否都是空
class MyQueue {
Deque<Integer> stack1;
Deque<Integer> stack2;
public MyQueue() {
stack1 = new LinkedList<Integer>();
stack2 = new LinkedList<Integer>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public int peek() {
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.peek();
}
public boolean empty() {
return stack2.isEmpty() && stack1.isEmpty();
}
}
- 下一个更大元素 I(难度:简单)
给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
示例 1:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
示例 2:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
解法1:
根据题意,先在nums2中找到nums[i]所在的位置,记录该位置,在nums2中从记录的位置开始向右查找第一个比nums[i]大的数
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int len1 = nums1.length;
int len2 = nums2.length;
int[] res=new int[len1];
int k=0;
for(int i = 0 ; i < len1 ; i++){
for(int j = 0 ;j < len2 ; j++){
if(nums1[i] == nums2[j]){
k=j;
break;
}
}
for( ; k <= len2-1 ; k++){
if(nums2[k] > nums1[i]){
res[i] = nums2[k];
break;
}
res[i] = -1;
}
}
return res;
}
}
解法2:
将nums2及其右边第一个比它大的数以键值对的形式存储在hashmap中,然后以nums1作为查询条件去hashmap中查找
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int len1 = nums1.length;
int len2 = nums2.length;
int[] res=new int[len1];
HashMap<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int i = 0 ; i < len2 ; i++){
for(int k = i ; k < len2 ; k++){
if(nums2[k] > nums2[i]){
map.put(nums2[i],nums2[k]);
break;
}
map.put(nums2[i],-1);
}
}
for(int j = 0 ; j < len1 ; j++){
res[j] = map.get(nums1[j]);
}
return res;
}
}
解法3:
使用栈和hashMap
如果栈为空,则将元素存入栈中(说明当前是第一个元素,或者右边不存在比当前元素更大的值)
当栈不为空时,弹出栈顶元素和当前元素比较,如果栈顶元素(当前元素左边的元素)小于当元素,则将其存入hashMap中
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int len1 = nums1.length;
int len2 = nums2.length;
int[] res=new int[len1];
Deque<Integer> stack=new ArrayDeque<Integer>();
HashMap<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int i = 0 ;i < len2 ; i++){
while(!stack.isEmpty() && stack.peekLast() < nums2[i]){
map.put(stack.removeLast(),nums2[i]);
}
stack.addLast(nums2[i]);
}
for(int j = 0 ;j < len1 ; j++){
res[j] = map.getOrDefault(nums1[j],-1);
}
return res;
}
}
- 棒球比赛(难度:简单)
题目:你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则:
整数 x - 表示本回合新获得分数 x
“+” - 表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。
“D” - 表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。
“C” - 表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。
请你返回记录中所有得分的总和。
示例 1:
输入:ops = [“5”,“2”,“C”,“D”,"+"]
输出:30
解释:
“5” - 记录加 5 ,记录现在是 [5]
“2” - 记录加 2 ,记录现在是 [5, 2]
“C” - 使前一次得分的记录无效并将其移除,记录现在是 [5].
“D” - 记录加 2 * 5 = 10 ,记录现在是 [5, 10].
“+” - 记录加 5 + 10 = 15 ,记录现在是 [5, 10, 15].
所有得分的总和 5 + 10 + 15 = 30
示例 2:
输入:ops = [“5”,"-2",“4”,“C”,“D”,“9”,"+","+"]
输出:27
解释:
“5” - 记录加 5 ,记录现在是 [5]
“-2” - 记录加 -2 ,记录现在是 [5, -2]
“4” - 记录加 4 ,记录现在是 [5, -2, 4]
“C” - 使前一次得分的记录无效并将其移除,记录现在是 [5, -2]
“D” - 记录加 2 * -2 = -4 ,记录现在是 [5, -2, -4]
“9” - 记录加 9 ,记录现在是 [5, -2, -4, 9]
“+” - 记录加 -4 + 9 = 5 ,记录现在是 [5, -2, -4, 9, 5]
“+” - 记录加 9 + 5 = 14 ,记录现在是 [5, -2, -4, 9, 5, 14]
所有得分的总和 5 + -2 + -4 + 9 + 5 + 14 = 27
示例 3:
输入:ops = [“1”]
输出:1
解题思路:
初始化一个栈
(1)对于数字,直接入栈
(2)对于C,弹出栈顶元素
(3)对于D,弹出栈顶元素后乘以2,再将该值入栈
(4)对于+,先保存弹出的栈顶元素(pop()),然后弹出下一个元素(peek()),之后再将最近弹出的两个值相加,最后将后一个弹出的元素入栈,再将两者的和入栈。
(5)使用循环将栈中的所有元素相加
class Solution {
public int calPoints(String[] ops) {
int sum = 0;
Stack<Integer> stack =new Stack<>();
for(String op:ops){
if( op.equals("C") ){
stack.pop();
}else if( op.equals("D")){
stack.push( 2 * stack.peek() );
}else if( op.equals("+")){
int top= stack.pop();
int temp=stack.peek();
stack.push(top);
stack.push(top + temp);
}else{
stack.push(Integer.parseInt(op));
}
}
while(!stack.isEmpty()){
sum += stack.pop();
}
return sum;
}
}
- 比较含退格的字符串(难度:简单)
题目:给定 S 和 T 两个字符串,当它们分别被输入到空白的文本编辑器后,判断二者是否相等,并返回结果。 # 代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
示例 1:
输入:S = “ab#c”, T = “ad#c”
输出:true
解释:S 和 T 都会变成 “ac”。
示例 2:
输入:S = “ab##”, T = “c#d#”
输出:true
解释:S 和 T 都会变成 “”。
示例 3:
输入:S = “a##c”, T = “#a#c”
输出:true
解释:S 和 T 都会变成 “c”。
示例 4:
输入:S = “a#c”, T = “b”
输出:false
解释:S 会变成 “c”,但 T 仍然是 “b”。
解法1:
定义两个栈,分别将字母入栈,遇到#,弹出栈顶元素,,最后比较两个栈的长度是否一致,不一直接返回false,一致再逐个比较是否相等,不相等直接返回false
class Solution {
public boolean backspaceCompare(String s, String t) {
Deque<Character> stackS=new LinkedList<Character>();
Deque<Character> stackT=new LinkedList<Character>();
for(int i=0;i<s.length();i++){
if(s.charAt(i) != '#'){
stackS.push(s.charAt(i));
}else if(s.charAt(i) == '#' && !stackS.isEmpty()){
stackS.pop();
}else if(s.charAt(i) == '#' && stackS.isEmpty()){
continue;
}
}
for(int j=0;j<t.length();j++){
if(t.charAt(j) !='#'){
stackT.push(t.charAt(j));
}else if(t.charAt(j) == '#' && !stackT.isEmpty()){
stackT.pop();
}else if(t.charAt(j) == '#' && stackT.isEmpty()){
continue;
}
}
while(!stackS.isEmpty() || !stackT.isEmpty()){
if(stackS.size() != stackT.size()){
return false;
}
Character ch1=stackS.pop();
Character ch2=stackT.pop();
if(ch1 != ch2){
return false;
}
}
return true;
}
}
解法2:
(1)定义两个指针S,T,分别指向字符串的末尾,
(2)定义变量,分别记录两个字符串中的#个数skipS,skipT
(3)先对S从后往前判断,有三种情况:
a:如果是#,将skipS++,并前移一位继续处理
b:如果不是#,且skipS>0,说明有字母可以抵消,skipS–,并前移一位继续处理
c:否则说明S字符串中没有可以抵消的字母了,必须在最后与字符串T进行比较,因此,结束S字符串的循环,进入T字符串的循环
(4)判断字符串S和字符串T经第三步的抵消后(可以间知道:S和T字符串的长度一致),剩余字符串是否相等,不相等直接返回false;如果经过第三步的抵消后,S或T中剩余的字符串长度不一致,直接返回false。
class Solution {
public boolean backspaceCompare(String s, String t) {
int i = s.length()-1 , j = t.length()-1;
int skipS = 0 ,skipT = 0 ;
while( i >= 0 || j >= 0){
while(i >= 0){
if(s.charAt(i) == '#' ){
skipS++;
i--;
}else if(skipS > 0){
skipS--;
i--;
}else{
break;
}
}
while(j >= 0){
if(t.charAt(j) == '#'){
skipT++;
j--;
}else if( skipT > 0){
skipT--;
j--;
}else{
break;
}
}
if( i >= 0 && j >= 0){
if(s.charAt(i) != t.charAt(j)){
return false;
}
}else{
if(i >= 0 || j >= 0){
return false;
}
}
i--;
j--;
}
return true;
}
}