栈
栈相关题目一般是模拟过程,本身结构不复杂,难点在于你要想的到用栈。
用栈进行模拟直接可以解决,很容易想到题目:
. - 力扣(LeetCode)-删除字符串中所有的相邻重复项:利用栈做一下模拟即可。可以使用现成的栈结构,也可以直接在字符串上通过指针模拟整个入栈出栈判断删除的操作:
//使用String直接模拟一个栈,结果直接就是返回,不用进行转化
StringBuffer dest = new StringBuffer();
int i = 0;//字符串s的遍历指针
int j = -1;//dest的标记指针
while(i < s.length()) {
if(j < 0 || dest.charAt(j) != s.charAt(i)){
dest.append(s.charAt(i));
j ++;
}else{
dest.deleteCharAt(j --);
}
i ++;
}
return dest.toString();
}
. - 力扣(LeetCode)同理。直接用栈模拟解决;或者用指针在字符串上模拟栈解决
. - 力扣(LeetCode)出栈序列问题,很好联想到到用栈解决。
有效括号序列_牛客题霸_牛客网 经典括号匹配问题
需要绕一点弯子不好直接想到:
*****. - 力扣(LeetCode 自己模拟出一个计算器
思路:维护一个字符变量cur表示当前运算符(加减乘除),初始值是+。遍历字符串,遇到数字进行判断:cur是 + 直接将数字入栈;cur 是 - 将数字相反数入栈;cur 是乘法将栈顶元素出栈然后相乘结果入栈;cur是除法将栈顶元素出栈相除结果入栈。 遇到运算符就更新字符变量cur;
此时遍历完字符串后已经将乘法除法进行计算结果入栈,减法转化成加法。所以把栈里的元素相加即可得到结果
//字符串转数字
while(i < len && s.charAt(i) <= '9' && s.charAt(i) >= '0') {
num = num * 10 + (s.charAt(i) - '0');
i ++;
}
. - 力扣(LeetCode)字符串解码 : 利用栈执行模拟过程。但是这个模拟过程比较绕,
核心就是什么时候字符串直接入栈,什么时候需要拼接到栈顶,要满足嵌套关系。当字符为'['时, [ 跟着的字符串需要单独重复,所以直接入栈;不在 [ 之后的字符串拼接到栈顶(所以在初始化站的时候要初始一个空数据防止报错)。遇到 ] 则弹出重复之后拼接到栈顶。
大概流程如下:
{
Stack<StringBuffer> stack = new Stack<>();//字符串栈
Stack<Integer> nums = new Stack<>();//重复次数栈
while(i < s.length()) {
if(s.charAt(i) == '[') { // [ 将后面的字符串直接入栈
}else if(s.charAt(i) == ']'){//将栈顶元素弹出并重复之后拼接到栈顶
} else if(s.charAt(i) >= '0' && s.charAt(i) <= '9') {//提取数字加入到数字栈
}else{//如果字符串之前没有跟着[,直接拼接到栈顶
}
}
return stack.pop().toString();
}
直接说明使用栈进行操作:
Stack<Integer> stack1 = new Stack<Integer>();//push栈
Stack<Integer> stack2 = new Stack<Integer>();//pop栈
public void push(int node) {
stack1.add(node);
}
public int pop() {
if(!stack2.isEmpty()) {//pop栈不为空直接出
return stack2.pop();
}else if(! stack1.isEmpty()){ //pop栈为空,push栈不为空将push栈移到pop
while(!stack1.isEmpty()) {
stack2.add(stack1.pop());
}
return stack2.pop();
}else {
return -1;
}
}
包含min函数的栈_牛客题霸_牛客网 实现一个栈,包含一个返回最小值的min函数,要求为O1时间复杂度。解法:针对最小值维护一个栈:入栈的数小于等于最小值栈顶元素,入栈;出栈的元素等于最小值栈顶元素,最小值栈也出栈。 注意这里要与equals比较,常识但很容易被忽略。
堆
最大(最小)k个数,第k大(第k小)的数 这类题目
最小的k个数/第k小 建大根堆,最大的k个数/第k大 建小根堆
最小的K个数_牛客题霸_牛客网 :建大小为K的大根堆,遍历数组,堆不满k直接放,满了跟堆顶比较,比堆顶小弹出堆顶放此元素;比堆顶大不做操作。
寻找第K大_牛客题霸_牛客网:建大小为K的小根堆,遍历数组,堆不满k直接放,满了根堆顶比较,比堆顶大堆顶弹出放这个元素;比堆顶小不做操作
. - 力扣(LeetCode)前k个高频单词:1:hashMap统计出现频率 2:建堆比较:这里涉及到俩种堆比较逻辑:需要判断使用比较器:
PriorityQueue<Pair<String,Integer>> queue = new PriorityQueue<>((a,b) -> {
if(a.getValue().equals(b.getValue())) {
//调用String的compareTo方法进行字典序比较,注意这里是字典序降序
return b.getKey().compareTo(a.getKey());
}else {
//出现次数比较
//注意这里大根堆和小根堆对应的写法
return a.getValue() - b.getValue();
}
});
小技巧:这里比较内容是个键值对,可以使用Pair使代码更简洁
//确定前k个高频单词
for(Map.Entry<String,Integer> entry : hash.entrySet()) {
queue.offer(new Pair<>(entry.getKey(),entry.getValue()));
if(queue.size() > k) queue.poll();
}
手写堆
//规定根节点的下标是0,则叶子节点与父节点的关系为:父节点下标 = (叶子节点下标 - 1) / 2
private static int[] array;
private int size = 0;
public PriorityQueue_shousi(int[] nums) {
if(nums == null) array = new int[10];
else {
array = nums;
size = nums.length;
}
}
//建堆过程
//在建堆阶段,一定是从下往上逐步建立规则的过程,所以从下往上对每个节点进行向下调整
private void buildPriorityQueue() {
int start = size - 1;//最后一个节点下标
while(start >= 0) {
shiftDown(start);
start --;
}
}
//添加元素,向上调整
public void add(int val) {
if(size == array.length) expansion();
size ++;
array[size - 1] = val;
shiftup(size - 1);
}
//出堆顶元素:与最后一个元素调换位置,size -- ,堆顶元素向下调整
public int poll() {
int dest = array[0];
swap(0 , size - 1);
size --;
shiftDown(0);
return dest;
}
//向下调整
private void shiftDown(int index/*要进行调整的节点的下标*/) {
int child = index * 2 + 1;//左孩子下标
if(child > size - 1) return;
while(child <= size - 1) {
if(child + 2 <= size && array[child] < array[child + 1]) child += 1;
if(array[child] > array[index]) {
swap(index, child);
index = child;
child = index * 2 + 1;
}else break;
}
}
//向上调整
private void shiftup(int index) {
int parent = (index - 1) / 2;
while(parent >= 0) {
if(array[parent] < array[index]) {
swap(parent , index);
index = parent;
if(index - 1 < 0) break;//防止死循环
parent = (index - 1) / 2;
}else break;
}
}
private void swap(int left , int right) {
int cur = array[left];
array[left] = array[right];
array[right] = cur;
}
//扩容,暂定1.5倍扩容
private void expansion() {
int[] newArray = new int[(int) (1.5 * array.length)];
for(int i = 0;i < array.length;i ++) {
newArray[i] = array[i];
}
array = newArray;
}
队列+宽搜
队列 + 宽搜问题框架:
List<List<Node>> lists = new LinkedList<>();
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while(! queue.isEmpty()) {
int size = queue.size();
List<Node> list = new LinkedList<>();
for(int i = 0;i < size;i ++) {
Node cur = queue.poll();
list.add(cur);
List<Node> nodes = cur.children;
for(int j = 0;j < nodes.size();j ++) {
queue.add(nodes.get(j));
}
}
lists.add(list);
}
//如果题目没有要求返回List<List<>>格式的数据,那么循环里size和第一个for循环的逻辑就可以去掉了
最基础 :层序遍历:. - 力扣(LeetCode)
二叉树每层最大值:加一个比较逻辑即可
锯齿形输出二叉树:
二叉树最大宽度:. - 力扣(LeetCode)
俩个解法:
1:还是套用层序遍历的模板,但是碰见子节点是null要将null存储进队列,
如何统计最大宽度?设置一个局部变量,在将子节点入队列时,每入一个子节点给变量加一,但是只有当子节点碰到数字时才将局部变量的值给全局统计变量,这样就能避免树的最后一个节点是null导致统计数量比实际高的问题
问题:如果题目中给出一个层数很高的树,比如单链表1500层,这个时候如果要去加null的话那么这个解法就会超时和超空间。
2:
前置知识:如果规定根节点编号为0,那么对于这整个二叉树来说:左子树编号: 2 * 根节点编号 + 1 右子树编号:2 * 根节点编号 + 2
解法:多给节点维护一个编号信息,这样我们就不用往队列加null节点了,直接正常入队列然后通过下标计算最大宽度即可:
public int widthOfBinaryTree(TreeNode root) {
if(root == null) return 0;
List<Pair<TreeNode , Integer>> list = new LinkedList<>();
list.add(new Pair<>(root , 0));
int width = 1; //宽度
while(list.size() != 0) {
List<Pair<TreeNode , Integer>> newList = new LinkedList<>();
for(int i = 0;i < list.size();i ++) {//将下一层节点如队列
Pair<TreeNode , Integer> pair = list.get(i);
TreeNode cur = pair.getKey();
if(cur.left != null) newList.add(new Pair<>(cur.left , 2 * pair.getValue() + 1));
if(cur.right != null) newList.add(new Pair<>(cur.right , 2 * pair.getValue() + 2));
}
if(newList.size() != 0)//通过下标计算宽度
width = Math.max(width , newList.get(newList.size() - 1).getValue() - newList.get(0).getValue() + 1);
list = newList;//直接进行覆盖,相当于将上一层元素出队列
}
return width;
}