20 有效括号

题目:

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

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 1 <= s.length <= 104
  • s 仅由括号 '()[]{}' 组成

思路

因为题目中给出了s中的括号由(){}[]三者组成。遍历字符串, 如果遇到( 就将) 添加进入栈, 如果遇到[ 就将]添加进入栈, {也是相同的方式。

为什么用这种方式呢, 因为题目中说了

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

所以如果最终出现的括号顺序没错, 那么肯定是先({[ 然后再其他的。

之后在遍历到)]}的时候, 我们就可以直接进行判断, 如果stack.peek() == s[i]那么就弹出栈 .

最后判断栈是否为空, 如果为空说明我们所有的判断都是对的, 刚好全部匹配上。 反之,如果最后栈不为空或者匹配的时候就出现栈为空的现象, 那么就说明不能匹配成功,直接返回false即可

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public boolean isValid(String s) {
if(s.length() % 2 != 0) return false;
Stack<Character>st = new Stack<>();

//弹出匹配
for(int i= 0 ;i< s.length();i++){
if(s.charAt(i) == '('){
st.push(')');
}else if (s.charAt(i) == '{'){
st.push('}');
}else if( s.charAt(i) == '['){
st.push(']');
}else if(st.isEmpty() || st.peek() != s.charAt(i)){
return false;
}else {
st.pop();
}
}
return st.isEmpty();
}
}

150 逆波兰表达式

题目:

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

示例 1:

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

示例 2:

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

示例 3:

1
2
3
4
5
6
7
8
9
10
输入: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 + * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

思路

就是一种简单的使用栈的例子。

遍历整个数组, 如果遇到数字 ,就将数字压入栈, 如果遇到符号, 就从栈顶弹出两个数字,进行运算, 然后将运算的结果再压入栈中。 知道遍历完整个数组为止 。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public int evalRPN(String[] tokens) {
//按照顺序遍历string内容的值, 如果遇到数字就直接压入栈, 如果是符号就从栈中弹出两个数 ,然后进行计算
Stack<Integer> st = new Stack<>();
for(String item : tokens){
if(item.equals("+")){
//遇到符号 弹出两个数, 然后进行运算,将结果再压入栈
int num1 = st.pop();
int num2 = st.pop();
int res = num1 + num2;
st.push(res);
}else if (item.equals("-")){
int num1 = st.pop();
int num2 = st.pop();
int res = -num1 + num2;
st.push(res);
}else if(item.equals("*")){
int num1 = st.pop();
int num2 = st.pop();
int res = num1 * num2;
st.push(res);
}else if(item.equals("/")){
int num1 = st.pop();
int num2 = st.pop();
int res = num2 / num1;
st.push(res);
}else{
//如果不是以上的符号, 那么就一定是数字, 因为题目中限制了
st.push(Integer.valueOf(item));
}
}
return st.pop();
}
}

239 滑动窗口

题目:

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

返回 滑动窗口中的最大值

示例 1:

输入: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

示例 2:

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

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

思路:

本题首先相到的是暴力解法, 通过外层遍历获取每个窗口的位置 ,然后再进行内层循环遍历得到每个窗口中的最大值,思路是可行的, 但是时间复杂度是O(n2) 力扣上是过不去的。

接下来想到的是通过使用队列的方式, 将每个窗口都进行入队列操作, 然后得到的内容再进行, 与此同时再对入队列的每个数进行取最大值操作,然后将得到的最大值写入数组中 。通过这样可以将时间复杂度缩小至O(nlogn)但是再力扣中还是过不了。。。

最终通过别人的题解得到了思路是 使用单调队列。

每次添加进队列的数 都是单调递减的, 如果当前需要添加的数 > 队列中的数 ,那么就需要将队列清空。 必须要保证队列中的数是单调递减的 。 这样一旦滑动窗口, 每个窗口的最大值就算当前队列的头部的。 因为我们在入队列的时候就进行了筛选, 必须保证队列是单调递减的。

与此同时,队列中的数是每个窗口内的数进行入队列, 出队列操作,所以我们操作数的范围因该是[i, i + k] 但是存储的是每个数在数组中的索引 ,同时记录的数组范围是 [0 ,i - k + 1 ] 所以选择的数 i - k + 1 必须大于 才能被加入res数组中。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayDeque<Integer> deque = new ArrayDeque<>();
int n = nums.length;
int[] res = new int[n - k + 1];
int idx = 0;
for(int i = 0; i < n; i++) {
// 根据题意,i为nums下标,是要在[i - k + 1, i] 中选到最大值,只需要保证两点
// 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
while(!deque.isEmpty() && deque.peek() < i - k + 1){
deque.poll();
}
// 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
//入队列当前数的索引。
deque.offer(i);

// 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
if(i >= k - 1){
res[idx++] = nums[deque.peek()];
}
}
return res;



/**
int []res = new int[nums.length - k + 1];
Queue<Integer> que = new LinkedList<>();

//将k个添加进入队列, 然后同时记录最大值
int max = Integer.MIN_VALUE;
for(int i= 0 ;i < k; i++){
que.add(nums[i]);
max = Math.max(max, nums[i]);
}
int j = 0;
res[j++] = max;
//如果到 k 个了, 那么就将最大值 写入res数组, 判断第一个是不是最大值, 如果是, 那么就需要循环找出队列的最大值, 如果不是 ,那么就直接出队列一个。 然后最大值还是沿用之前的
for(int i = k ; i < nums.length; i++){
//判断出队列的是不是第一个

int pop = que.poll();
if(pop == max){
if(que.isEmpty()){
max = Integer.MIN_VALUE;
}else{
max = Integer.MIN_VALUE;
for(Integer x : que){
max = Math.max(max, x);
}
}
}
//如果不是, 那么就直接添加新的元素 ,然后再判断一次最大值
que.add (nums[i]);
max = Math.max(nums[i], max);
res[j++] = max;
}
return res;
*/
}
}

347 前k个高频元素

题目:

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

示例 1:

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

示例 2:

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

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

思路

通过Java中的map 实现对每个字符 以及字符出现的次数进行累加。 然后利用stream流的形式再对统计的次数进行排序 (key: 出现的数 value:每个数出现的次数统计

最后通过迭代器遍历map, 将前k个依次添加到数组中返回。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public int[] topKFrequent(int[] nums, int k) {
int res[] = new int[k];
Map<Integer, Integer> getAll = new LinkedHashMap<>();

//直接将元素 和 次数全部存储到map中
for(int i= 0;i < nums.length;i++){
if(getAll.containsKey(nums[i])){
int count = getAll.get(nums[i]);
count++;
getAll.put(nums[i], count);
}else{
getAll.put(nums[i], 1 ); // i 表示哪个数 count[i]表示这个数出现的次数
}
}
//通过stream流的形式 实现对value的降序
LinkedHashMap<Integer, Integer> map = getAll.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(oldV, newV) -> oldV, LinkedHashMap::new));

Iterator<Integer> iter = map.keySet().iterator();
int i = 0;
while( i < k && iter.hasNext()){
int key=iter.next();
int value = map.get(key);
res[i++] = key;
System.out.println(key+" "+value);
}
return res;
}
}