题外话:感觉脑子没长到栈这块…最近刷栈的题都好难啊…哭哭…坚持坚持!多刷几遍就好了!!
1. LC394.字符串解码
题目链接
先说数据结构。
维护两个栈:一个栈存之前的字符串,另一个栈存之后的字符串的重复次数(数字)
维护两个变量:res表示目前遍历到的字符组成的字符串,k表示遍历到的数字组成的数字
工作原理:
最开始的字符串为""。
遇到字符时:存到res里。
遇到数字时:存到k里。
遇到左括号:把左括号前面的字符串,以及左括号内要循环的次数(数字),分别入栈,即res和k入栈。然后res和k重置。
遇到右括号:说明此时的res为最小单位,将数字栈里的栈顶元素出栈,这个数字将是res循环的次数。然后将字符串栈里的栈顶元素出栈,这个元素是括号前的字符串,后面需要拼接res。拼接后,形成新的res。不入栈。等遇到左括号再入栈。
例:
3[a2[c]] 看成 ""3[a2[c]]
图片版:
文字版:
代码
注意当字符是数字字符的处理逻辑。
- 用
c >= '0' && c <= '9'
来确定当前字符是不是数字字符 c-'0'
得到当前字符代表的数字,将字符转为数字k = k * 10 + (c-'0');
这个也很妙。如果k已经有数字了,例如k已经等于12了,这时又遍历到3,那么就是k*10再加上当前的数字,123。
class Solution {public String decodeString(String s) {int k = 0;StringBuilder res = new StringBuilder();Stack<StringBuilder> stack_Res = new Stack<>();Stack<Integer> stack_K = new Stack<>();for (char c : s.toCharArray()){if (c >= '0' && c <= '9'){k = k * 10 + (c-'0');}else if (c == '['){stack_Res.push(res);stack_K.push(k);res = new StringBuilder();k = 0;}else if (c == ']'){int count = stack_K.pop();StringBuilder temp = new StringBuilder();for (int i=0; i<count; i++){temp.append(res);}res = stack_Res.pop().append(temp);}else{res.append(c);}}return res.toString();}
}
2. LC347、前K个高频元素
题目链接
解法一:
- 遍历一遍,建立哈希表存储数字与出现频次的映射。
- 维护一个元素数目为k的小顶堆。(堆中存放的是元素,但是priority queue可以按照自定义顺序排序,自定义顺序为:元素的频次)。
- 当有新元素来的时候,与堆顶元素作比较,若频次比堆顶元素大,则该元素应放到前k个高频元素中,所以堆顶元素出队列,该元素加队列。堆会自动按定义顺序排序。
- 最终堆中的元素就是前K个高频元素。
时间复杂度:
建立哈希表:n;小顶堆本身维护:logk;遍历哈希,维护小顶堆:nlogk;遍历堆存进数组:k
所以整体时间复杂度为O(nlogk)
空间复杂度:
哈希表:n,小顶堆:k。
所以整体空间复杂度为O(n)
class Solution {public int[] topKFrequent(int[] nums, int k) {//建立哈希表维护 数值与频次 的键值对Map<Integer, Integer> map = new HashMap<>();for (int i : nums){if (map.containsKey(i)){map.put(i, map.get(i)+1);}else{map.put(i, 1);}}//定义PriorityQueue存放前k个高频元素PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>(){@Overridepublic int compare(Integer a, Integer b){return (map.get(a) - map.get(b));}});//遍历map,更新堆for (Integer key: map.keySet()){if (queue.size() < k){queue.offer(key);}else{if (map.get(key) > map.get(queue.peek())){queue.poll();queue.offer(key);}}}//输出堆元素int[] array = new int[k];for (int i=0; i<k; i++){array[i] = queue.poll();}return array;}
}
解法二:
- 遍历一遍,建立哈希表存储数字与出现频次的映射。
- 将Map.entrySet<>存放进list,list排序,按照自定义规则排序(按照频次,即value值从大到小排序)
- 获取list中前k个元素的key值
时间复杂度:
建立哈希表:n;放进list:n;sort方法排序:nlogn;遍历堆存进数组:k
所以整体时间复杂度为O(nlogn)
空间复杂度:
O(n)
class Solution {public int[] topKFrequent(int[] nums, int k) {//建立哈希表维护 数值与频次 的键值对Map<Integer, Integer> map = new HashMap<>();for (int i : nums){if (map.containsKey(i)){map.put(i, map.get(i)+1);}else{map.put(i, 1);}}//将entryset放进list,对list进行排序List<Map.Entry<Integer, Integer>> list = new ArrayList<>();for (Map.Entry<Integer, Integer> entry: map.entrySet()){list.add(entry);}Collections.sort(list, (list1, list2) -> list2.getValue() - list1.getValue());//获取前k个值int[] array = new int[k];for (int i=0; i<k; i++){array[i] = list.get(i).getKey();}return array;}
}
3. LC215.数组中的第k个最大元素
题目链接
用优先级队列方法做。
PriorityQueue,peek()方法返回的是堆顶元素,poll()方法移除的也是堆顶元素,offer()方法把元素插入队列末尾,队列会根据自定义实现排序。
所以我们考虑使用大顶堆还是小顶堆呢?
因为poll和peak针对的是堆顶元素,所以我们每次新来一个元素若想加入堆,都必须要移除堆顶的元素。如果是大顶堆的话,堆顶是最大的元素,但显然最大的元素不能被移除。所以我们要用小顶堆。若新来的元素比堆顶的大,则移除堆顶元素,把新元素加入进来。这样可以实现最后得到的大顶堆是前k个最大元素。堆顶就是第k个最大元素
代码
注意:
- 优先级队列的定义,还是要熟悉,尤其是里面的comparator怎么写
- 只有当新元素大于堆顶,而非大于等于时,才将新元素加入进来。比如考虑655,5是第二大元素,无论哪个5都是第二大元素,但如果两个5都加到堆里来的话,那堆里就有三个元素了,就成前3大元素了。所以加入堆的条件不能是等于。
- 基本类型转包装类,包装类转基本类型,必须熟记。
class Solution {public int findKthLargest(int[] nums, int k) {PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>(){public int compare(Integer a, Integer b){return a.intValue() - b.intValue();}});int index = 0;for (int i: nums){if (index < k){queue.offer(Integer.valueOf(i));}else{if (i > queue.peek()){queue.poll();queue.offer(Integer.valueOf(i));}}index++;}return queue.peek();}
}