算法学习笔记(七):常用数据结构、堆、栈、队列

embedded/2024/11/27 2:19:08/

一:常用技巧:枚举右,维护左

1.双变量问题

        对于双变量问题,例如两数之和 ai + aj = t,可以枚举右边的aj,转换成单变量问题,也就是

        在aj左边查找是否有 ai = t - aj,这就可以用哈希表来维护

        也叫做:枚举右,维护左

        例子:两数之和

        给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值target

        的那两个整数,并返回他们的数组下标

        你可以假设每种输入只会对应一个答案,并且不能使用重复元素

        你可以按任意顺序返回答案

class Solution {public int[] twoSum(int[] nums, int target) {int[] arr = new int[2];Map<Integer, Integer> map = new HashMap<>();for (int i = 0; i < nums.length; i++) {int temp = target - nums[i];if (map.containsKey(temp)) {arr[0] = map.get(temp);arr[1] = i;break;} else {//将当前值存进mapmap.put(nums[i], i);}}return arr;}
}

2.好数对的数目

        给你一个整数数组nums,如果一组数字(i,j)满足 nums[i] == nums[j] 且 i < j,

        这就可以认为是一组好数对,请返回好数对的数目

class Solution {public int numIdenticalPairs(int[] nums) {/**还是用哈希表维护*/Map<Integer, Integer> map = new HashMap<>();int count = 0;for (int num : nums) {int ans = map.getOrDefault(num, 0);count += ans;//因为要求是i < j 所以自己不能重复map.put(num, ans + 1);} return count;}
}

二.栈-基础

1.用栈操作构建数组

        给你一个数组 target 和一个整数 n,每次迭代,需要从 list = {1, 2, 3, ......, n}中依次读取

        一个数字,请你用下述操作来构建目标数组 target:

        - "Push":从 list 中读取一个新的元素,并将其推入数组中

        - "Pop":删除数组中的最后一个元素

        - 如果目标数组构建完成,就停止读取梗多元素

        题目数据保证目标数组严格递增,并且只包含 1 到 n 之间的数字

        请返回构建目标数组所用的操作序列,如果存在多个可行方案,返回任意即可。

class Solution {public List<String> buildArray(int[] target, int n) {//大概明白这题的意思了//每次操作都是递增的//所以target每两两相连的两个数之间相差几 比如 index0=1 index1=3//那么0和1之间肯定是先push1 push2 pop2 push3 做了这四个动作才会形成1,3这个结构//那么就是说两个相邻的数之间相差了几 就做了几组 push pop 然后最后会再push当前List<String> list = new ArrayList<>();for (int i = 1, j = 0; i <= n && j < target.length; i++) {list.add("Push");//如果push进来的数和当前数组中同位置的数不相等 则证明被pop了if (target[j] != i) {list.add("Pop");} else {j++;}}return list;}
}

2.比较含退格的字符串

        给定 s 和 t 两个字符串,当他们分别被输入到空白的文本编辑器后,如果两者相等,

        返回true。

        注意:如果对空文本输入退格字符,文本继续为空。

        

class Solution {public boolean backspaceCompare(String s, String t) {/**那么就是双循环 大循环就是 比较两个串的当前字符是否相等 不相等直接false相等就是 指针都--然后各自内部再循环 有两种情况当前字符是# 那么记录一下#的数量 继续往前循环当前字符不是# 然后#的数量>0 那么就意味着可以消除 #-- 指针--然后当前字符不是# 而且#的数量=0了 推出循环同理 另外一个串也是这样循环的逻辑*/int sIndex = s.length() - 1, tIndex = t.length() - 1;int sCount = 0, tCount = 0;while (sIndex >= 0 || tIndex >= 0) {while (sIndex >= 0) {if (s.charAt(sIndex) == '#') {sCount++;sIndex--;} else if (sCount > 0) {sCount--;sIndex--;} else {//退出循环break;}}while (tIndex >= 0) {if (t.charAt(tIndex) == '#') {tCount++;tIndex--;} else if (tCount > 0) {tCount--;tIndex--;} else {//退出循环break;}}//边界问题if (sIndex >= 0 && tIndex >= 0) {if (s.charAt(sIndex) != t.charAt(tIndex)) return false;} else {//其中有串已经循环结束了 但是另外一个可能还没结束//那么这个时候除非都结束了 不然只要有一个每结束就是不等于if (sIndex >= 0 || tIndex >= 0) return false;}sIndex--;tIndex--;} return true;}
}

三.栈-进阶

1.最小栈

        设计一个支持 push、pop、top 操作,并能在常数时间内检索到最小元素的栈

        实现 MinStack类:

        - MinStack()初始化堆栈对象

        - void push(int val) 将元素val推入堆栈

        - void pop() 删除堆栈顶部元素

        - int top() 获取堆栈顶部的元素

        - int getMin() 获取堆栈中的最小元素

class MinStack {private LinkedList<Integer> queue;private LinkedList<Integer> min;public MinStack() {this.queue = new LinkedList<>();this.min = new LinkedList<>();}public void push(int val) {//两个栈 一个栈正常存数据 一个栈存最小值//但是有个问题 怎么维持这俩栈的同步大小 //每次push的时候min也存一份数据:当前的最小值//栈顶和当前push比较 这样queue里面比如存了 1, 2, -1//那么当push1的时候min里面没有元素 最小值就是1//push2 最小值还是1 又往min里面push1//push-1的时候 最小值是-1 那么min也push-1//这样 queue里面就是 -1 2 1 //     min就是       -1 1 1//这样两边数据就对的上 而且只要每次存最小值 就能保证push queue的时候不管是push什么 min里面的数据都是对的//就是相当于存一个queue里面每个元素对应的最小值 和min里面的元素位置相对应queue.push(val);if (min.isEmpty()) {min.push(val);return;}int minVal = min.peek();min.push(Math.min(minVal, val));}public void pop() {queue.pop();min.pop();}public int top() {return queue.peek();}public int getMin() {return min.peek();}
}

2.设计一个支持增量操作的栈

        请你设计一个支持对其元素进行增量操作的栈

        实现自定义栈类 CustomStack

        - CustomStack(int maxSize):用maxSize初始化对象,maxSize是栈最多能容纳的元素数量

        - void push(int x):如果栈还未增长到maxSize,就将x压入栈顶

        - int pop():弹出栈顶元素,并返回栈顶的值,或栈顶为空时返回-1

        - void inc(int k, int val):栈底的 k 个元素的值都增加val,如果栈中元素总数小于k,则栈中

        所有的元素都增加val

class CustomStack {private LinkedList<Integer> queue;private int maxSize;public CustomStack(int maxSize) {this.queue = new LinkedList<>();this.maxSize = maxSize;}public void push(int x) {if (queue.size() < maxSize) queue.push(x);}public int pop() {if (queue.isEmpty()) return -1;return queue.peek();}public void increment(int k, int val) {int num = 1;LinkedList<Integer> temp = new LinkedList<>();int size = queue.size();while (num <= size && num <= k) {Integer v = queue.pollLast();v += val;temp.push(v);num++;}while (!temp.isEmpty()) {queue.addLast(temp.pop());}}
}

3.有效的括号

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

        有效满足:

        - 左括号必须用相同类型的右括号闭合

        - 左括号必须以正确的顺序闭合

        - 每个右括号都有一个对应的相同类型的左括号

class Solution {public boolean isValid(String s) {//这题可以不用栈//以为不是顺序的 噢 特尔的傻逼题目 还是要用顺序的LinkedList<Character> queue = new LinkedList<>();char[] arr = s.toCharArray();for (char c : arr) {if (c == '(' || c == '{' || c == '[') {queue.push(c);continue;}if (c == ')' && !queue.isEmpty() && queue.peek() == '(' ) {queue.pop();continue;}if (c == '}' && !queue.isEmpty() && queue.peek() == '{' ) {queue.pop();continue;}if (c == ']' && !queue.isEmpty() && queue.peek() == '[') {queue.pop();continue;}queue.push(c);}return queue.isEmpty();}
}

四.队列

1.最近的请求次数

        写一个 RecentCounter 类来计算特定时间范围内最近的请求

        请你实现 RecentCounter类:

        - RecentCounter() 初始化计数器,请求书为0

        - int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回

        过去 3000 毫秒内发生的所有请求数(包括新请求),确切的说,返回 [t - 3000, t] 内发生

        的请求数

        保证每次队 ping的调用都使用比之前更大的 t 值。

class RecentCounter {private LinkedList<Integer> queue;public RecentCounter() {this.queue = new LinkedList<>();}public int ping(int t) {queue.add(t);//由于每次收到的请求的时间都比之前的大,因此从队首到队尾的时间值是单调递增的。//当在时间 t 收到请求时,为了求出 [t−3000,t] 内发生的请求数,//我们可以不断从队首弹出早于 t−3000 的时间。循环结束后队列的//长度就是 [t−3000,t] 内发生的请求数。while (queue.peek() < t - 3000) {queue.poll();}return queue.size();}
}

2.用队列实现栈

        请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、

        top、pop和empty)。

        实现 MyStack类

        - void push(int x) 入栈

        - int pop() 栈顶出栈

        - int top() 返回栈顶元素

        - boolean empty() 判断栈是否为空

class MyStack {private LinkedList<Integer> stack; private LinkedList<Integer> temp; public MyStack() {this.stack = new LinkedList<>();this.temp = new LinkedList<>();}public void push(int x) {/**思路:每次push的时候先进temp 然后从stack里面一个一个出队列并oush到temp最后temp和stack交换这样就能保证队列变为栈 先进后出比如:第一次push x = 1,这个时候stack是空的 temp也是空的进temp,temp变为: 1 然后temp和stack交换 temp为空 stack = 1然后第二次push x = 2temp = 2, temp.offer(stack.poll)---> temp= 1->2 然后交换第三次push x= 3最后temp就变为:1-2->3 这样队列就变为了一个后进先出的栈          */temp.offer(x);while (!stack.isEmpty()) {temp.offer(stack.poll());}LinkedList<Integer> t = stack;stack = temp;temp = t;}public int pop() {if (stack.isEmpty()) return - 1;return stack.poll();}public int top() {if (stack.isEmpty()) return - 1;return stack.peek();}public boolean empty() {return stack.isEmpty();}
}

3.用栈实现队列

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

class MyQueue {private LinkedList<Integer> queue;private LinkedList<Integer> temp;public MyQueue() {this.queue = new LinkedList<>();this.temp = new LinkedList<>();}public void push(int x) {/**思路和用2个队列实现栈是一样的先从queue正常出栈到temp然后temp正常出栈到queue这样就能用栈实现队列的先进先出*/while (!queue.isEmpty()) {temp.push(queue.poll());}temp.push(x);while (!temp.isEmpty()) {queue.push(temp.poll());}}public int pop() {return queue.pop();}public int peek() {return queue.peek();}public boolean empty() {return queue.isEmpty();}
}

4.设计循环队列

        设计你的循环队列实现,循环队列是一种线性数据结构,其操作表限基于 FIFO(先进先出)

        原则并且队尾连接在队首之后以形成一个循环,它也被称为 环形缓冲器

        循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦

        一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列

        我们能使用这些空间去存储新的值,

        你的实现应该支持如下操作

        - MyCircularQueue(k):构造器,设置队长度为k

        - Front:从队首获取元素,为空返回-1

        - Rear:获取队尾元素,为空返回-1

        - enQueue(value):向循环队列插入一个元素,如果成功返回true

        - deQueue():从循环队列删除一个元素,成功返回true

        - isEmpty():是否为空

        - isFull():是否已满

class MyCircularQueue {private int[] queue;private int maxSize, head, tail;public MyCircularQueue(int k) {this.queue = new int[k];this.maxSize = k;this.head = 0;this.tail = 0;}public boolean enQueue(int value) {if (isFull()) return false;//意味着从队尾插入 因为队列都是从尾部插入数据//用模运算操作 这样就能实现循环 从而保证不会越界queue[tail % maxSize] = value;tail++; //尾部插入了数据之后向后移动一位 标示着这个位置是尾部return tail >= 0;}public boolean deQueue() {if (isEmpty()) return false;//从头删除 那就是直接将头往后移动一位head++;return head >= 0;}public int Front() {if (isEmpty()) return -1;return queue[head % maxSize];}public int Rear() {if (isEmpty()) return -1;//因为tail指针都是指向下一位的 所以真正队尾的数据是tail-1位return queue[(tail - 1) % maxSize];}public boolean isEmpty() {return head == tail;}public boolean isFull() {return tail - head == maxSize;}
}

五.堆(优先队列)

1.最后一块石头的重量

        有一堆石头,每块石头的重量都是正整数

        每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎,假设石头的重量分别为 x 和 y

        且 x <= y,那么粉碎的可能结果如下:

        - 如果 x == y,那么两块石头都会被完全粉碎

        - 如果 x != y,那么重量为 x 的石头完全粉碎,而为 y 的石头新重量为 y - x

        最后,最多只会剩下一快石头,返回此石头的重量,如果没有石头剩下,返回 0

class Solution {public int lastStoneWeight(int[] stones) {//用java自带的优先队列 默认降序排序PriorityQueue<Integer> heap = new PriorityQueue<>((a, b) -> b - a);//将数据放入堆中for (int x : stones) {heap.offer(x);}//每次取出两个最上面的石头//保证里面最少有2个元素while (heap.size() > 1) {int y = heap.poll();int x = heap.poll();if (x != y) {heap.offer(y - x);}}return heap.isEmpty() ? 0 : heap.poll();}
}

2. K 次乘运算后的最终数组 I

        给你一个整数数组 nums,一个整数 k 和一个整数 multiplier

        你需要堆 mus 执行 k 次操作,每次操作中:

        - 找到 nums 中 最小的值 x,如果存在多个最小值,选择最前面的一个

        - 将 x 替换为 x * multiplier

        请你返回执行完 k 次乘运算只会,最终 nums 数组

class Solution {public int[] getFinalState(int[] nums, int k, int multiplier) {//优先队列存数组,0存元素 1存元素索引PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> a[0] - b[0] == 0 ? a[1] - b[1] : a[0] - b[0]);for (int i = 0; i < nums.length; i++) {int[] arr = new int[2];arr[0] = nums[i];arr[1] = i;heap.offer(arr);}for (int i = 0; i < k; i++) {int[] min = heap.poll();nums[min[1]] = min[0] * multiplier;heap.offer(new int[]{nums[min[1]], min[1]});}return nums;}
}

3.从数量最多的堆取走礼物

        给你一个整数数组 gifts,表示各堆礼物的数量,每一秒,你需要执行以下操作

        - 选择礼物数量最多的那一堆

        - 如果不止一堆都符合礼物数量最多,任选其一

        - 选中的那一堆留下平方根数量的礼物(向下取整),取走其他的礼物

        返回在 k 秒后剩下的礼物数量

class Solution {public long pickGifts(int[] gifts, int k) {PriorityQueue<Integer> heap = new PriorityQueue<>((a, b) -> b - a);for (int num : gifts) {heap.offer(num);}while (k-- > 0) {int max = heap.poll();int sqrt = (int)Math.sqrt(max);heap.offer(sqrt);}long sum = 0;while (!heap.isEmpty()) {sum += heap.poll();}return sum;}
}

4.无线集中的最小数字

        现有一个包含所有正整数的集合 [1, 2, 3, ,4 ,5, ......]

        实现 SmallesInfiniteSet类:

        - SmallesInfiniteSet() 初始化对象以包含所有 正整数

        - int popSmallest() 移除并返回该无线几种的最小整数

        - void addBack(int num) 如果正整数 num 不存在无限集中,则将一个num添加进来

        提示:1 <= num <= 1000

        

class SmallestInfiniteSet {private PriorityQueue<Integer> heap;    //没有去重功能private boolean[] bool; //用来判断去重private int min;        //用来标识无限集的最左边界public SmallestInfiniteSet() {this.heap = new PriorityQueue<>();this.min = 1;   //题目说了num>=1this.bool = new boolean[1001];}public int popSmallest() {//移除最小元素,如果不在最小堆中,那么就移除无限集的左边界 if (!heap.isEmpty()) {int poll = heap.poll();bool[poll] = false;return poll;}//因为每次都是移除最小整数,所以min每次++往右移动一位就行//前提是不在堆中//比如第一次调用 肯定是返回1//这个时候无限集的左边界已经是从2到正无穷//这个时候如果add了一个1,那么是不是说明不在无限集的范围内,//那么就会把这个1存进heap中//heap里面保存的数据都是小于无限集的左边界的数据 //也就是说都是已经移除了的数据return min++;}public void addBack(int num) {//>min 或者bool里面是true就表示存在if (num >= min || bool[num]) return;//否则不存在无限集中 证明曾经某次调用移除了 //这个时候就从heap中返回即可heap.offer(num);//并且标识已经添加过了bool[num] = true;}
}


http://www.ppmy.cn/embedded/140791.html

相关文章

MySQL 索引详解

在数据库的世界中&#xff0c;索引就像是一本巨大书籍的目录&#xff0c;它能够极大地提高数据检索的效率。在 MySQL 中&#xff0c;索引的合理使用对于数据库的性能至关重要。本文将深入探讨 MySQL 索引的各个方面。 一、索引的概念与作用 1. 什么是索引&#xff1f; 索引是一…

Apple Vision Pro开发003-PolySpatial2.0新建项目

unity6.0下载链接:Unity 实时开发平台 | 3D、2D、VR 和 AR 引擎 一、新建项目 二、导入开发包 com.unity.polyspatial.visionos 输入版本号 2.0.4 com.unity.polyspatial&#xff08;单独导入&#xff09;&#xff0c;或者直接安装 三、对应设置 其他的操作与之前的版本相同…

在 Ubuntu 系统上安装 npm 环境以及 nvm(Node Version Manager)

在 Ubuntu 系统上安装 npm 环境以及 nvm&#xff08;Node Version Manager&#xff09; 步骤 1: 更新系统包步骤 2: 安装 nvm步骤 3: 安装 Node.js 和 npm步骤 4: 设置默认 Node.js 版本&#xff08;可选&#xff09;总结 在 Ubuntu 系统上安装 npm 环境以及 nvm&#xff08;No…

动态调试对安全研究有什么帮助?

动态调试在安全研究中提供了多方面的帮助&#xff0c;以下是其主要作用&#xff1a; 深入了解恶意软件行为&#xff1a;动态调试允许安全研究人员实时监视程序的执行过程&#xff0c;包括指令的执行情况、内存的读写情况、寄存器的状态等&#xff0c;从而帮助分析人员理清程序的…

PyQt的安装和再PyCharm中的配置

安装 pip install pyqt5 -i https://pypi.tuna.tsinghua.edu.cn/simple pip install pyqt5-tools -i https://pypi.tuna.tsinghua.edu.cn/simple配置 QtDesigner Name&#xff1a;自己取 Program&#xff1a;上面的路径 Working directory&#xff1a;$FileDir$PyUic Name&am…

亚信安全与飞书达成深度合作

近日&#xff0c;亚信安全联合飞书举办的“走近先进”系列活动正式走进亚信。活动以“安全护航信息化 共筑数字未来路”为主题&#xff0c;吸引了众多数字化转型前沿企业的近百位领导参会。作为“走近先进”系列的第二场活动&#xff0c;本场活动更加深入挖掘了数字化转型的基础…

数据结构(Java版)第二期:包装类和泛型

目录 一、包装类 1.1. 基本类型和对应的包装类 1.2. 装箱和拆箱 1.3. 自动装箱和自动拆箱 二、泛型的概念 三、引出泛型 3.1. 语法规则 3.2. 泛型的优点 四、类型擦除 4.1. 擦除的机制 五、泛型的上界 5.1. 泛型的上界的定义 5.2. 语法规则 六、泛型方法 6.1…

Python设计模式详解之13 —— 模板方法模式

Template Method 设计模式 是一种行为型设计模式&#xff0c;用于定义一个操作的骨架&#xff0c;将某些步骤延迟到子类中实现&#xff0c;从而允许子类在不改变整体算法结构的情况下重新定义某些步骤。 在 Python 中&#xff0c;Template Method 模式通常使用基类的方法来定义…