剑指offer刷题(栈、堆、 队列、 图)
Stack & Queue
005-用两个栈实现队列
题目描述
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 push 和 pop ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
题解
用两个栈来实现队列(先进先出)的功能
-
stack1:实现
push
操作
- 直接添加到stack1中 -
stack2:实现
pop
操作
1. stack1和Stack2 均而空,返回 -1;
2.若 stack2 为空(有上面条件知stack1不为空)
- 遍历stack1,将stack1中的元素添加到stack2中
3.只要stack2不为空,弹出stack2栈顶的元素;
代码
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
//push() 是从队列的头部插入元素
public void push(int node) {
stack1.add(node);
}
// pop() 是从队列的尾部取出元素
public int pop() {
if(stack1.isEmpty() && stack2.isEmpty()){
return -1;
}
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
复杂度
由于问题特殊,以下分析仅满足添加 N 个元素并删除 N 个元素,即栈初始和结束状态下都为空的情况。
-
时间复杂度: push函数为 O(1);pop()函数在N次队首元素删除操作中总共需完成N个元素的倒序。
-
空间复杂度O(N):最差情况下,栈1和2共保存N个元素。
020-包含min函数的栈
题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
题解
知识点:
- dd & push
共同点:
add
,push
都可以向stack中添加元素。不同点:
`add`是继承自Vector的方法,且返回值类型是boolean. `push`是Stack自身的方法,返回值类型是参数类类型。
- peek & pop
共同点:peek,pop都是返回栈顶元素。
不同点:
peek()函数返回栈顶的元素,但不弹出该栈顶元素。 pop()函数返回栈顶的元素,并且将该栈顶元素出栈。
函数设计:
- push(x) 函数: 重点为保持栈 B 的元素是 非严格降序 的。
-
将 x 压入栈 A(即 A.add(x) );
-
若 ① 栈 B 为空 或 ② x 小于等于 栈 B 的栈顶元素,则将 x 压入栈 B (即 B.add(x) )。
- pop() 函数: 重点为保持栈 A,B 的 元素一致性 。
- 执行栈 A 出栈(即 A.pop() ),将出栈元素记为 y ;
- 若 y等于栈 B 的栈顶元素,则执行栈 B 出栈(即 B.pop() )。
-
top() 函数: 直接返回栈 A 的栈顶元素即可,即返回 A.peek() 。
-
min() 函数: 直接返回栈 B 的栈顶元素即可,即返回 B.peek() 。
代码
class MinStack {
Stack<Integer> stack;//c存储数据
Stack<Integer> stackMin;//存储最小值
/** initialize your data structure here. */
public MinStack() {
stack = new Stack<>();
stackMin = new Stack<>();
}
public void push(int x) {
stack.push(x);//将x压入存储栈stack中;
//当stackMin为空或者当前x小于栈顶元素(x为最小值);将x压入栈stackMIn中;
if(stackMin.isEmpty() ||x <= stackMin.peek()) stackMin.push(x);
}
public void pop() {
//stack.pop()实现了出栈操作;
//stackMin的栈顶元素 == stack的栈顶元素,说明最小值是最后进栈的,也需要出栈;
if(stack.pop().equals(stackMin.peek())) stackMin.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return stackMin.peek();
}
}
复杂度
- 时间复杂度 O(1): push(), pop(), top(), min() 四个函数的时间复杂度均为常数级别。
- 空间复杂度 O(N) : 当共有 N 个待入栈元素时,辅助栈 B 最差情况下存储 N个元素,使用 O(N) 额外空间。
021-栈的压入、弹出序列
题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5}
是某栈的压栈序列,序列 {4,5,3,2,1}
是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2}
就不可能是该压栈序列的弹出序列。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
提示:
- 0 <= pushed.length == popped.length <= 1000
- 0 <= pushed[i], popped[i] < 1000
- pushed 是 popped 的排列。
题解
辅助栈 stack ,模拟 压入 / 弹出操作的排列。根据是否模拟成功,即可得到结果。
- 入栈操作: 按照压栈序列的顺序执行。
- 出栈操作: 每次入栈后,循环判断 “栈顶元素 == 弹出序列的当前元素” 是否成立,将符合弹出序列顺序的栈顶元素全部弹出。
代码
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
Stack<Integer> stack = new Stack<Integer>();
//用于标识弹出序列的位置
int popIndex = 0;
for(int i = 0; i< pushA.length;i++){
stack.push(pushA[i]);
//如果栈不为空,且栈顶元素等于弹出序列
while(!stack.empty() && stack.peek() == popA[popIndex]){
//出栈
stack.pop();
//弹出序列向后一位
popIndex++;
}
}
return stack.empty();
}
}
复杂度
- 时间复杂度 O(N) : 其中 N 为列表 pushed 的长度;每个元素最多入栈与出栈一次,即最多共 2N 次出入栈操作。
- 空间复杂度 O(N) : 辅助栈 stack 最多同时存储 N 个元素。
044-翻转单词顺序列(栈)
题目描述
loser a am I
翻转后为 I am a loser
题解
双指针
substring(start, stop)
:方法用于提取字符串中介于两个指定下标之间的字符,方法返回的子串包括 start 处的字符,但不包括 stop 处的字符。
append()
:将指定的字符串追加到此字符序列
trim()
:删除收尾的空格
单引号
引的数据 是char类型的——> 单引号只能引一个字符(表示单个字符)
双引号
引的数据 是String类型的——> 而双引号可以引0个及其以上(引用字符串)
代码
class Solution{
public String ReverseSentence(String str) {
StringBuffer res = new StringBuffer();
str = str.trim();
int i = str.length -1, j = i;
while(i > 0){
// 搜索首个空格,charAt(i) 返回指定位置(i)的字符;
while(i > = 0 && str.charAt(i) != ' ') i--;
res.append(str.subString(i + 1, j + 1) + " ");
//// 跳过单词间空格
while(i >= 0 && str.charAt(i) == ' ') i--;
j = i;
}
// 转化为字符串并返回
return res.toString().trim;
}
}
复杂度
- 时间复杂度:O(N)
- 空间复杂度:O(N)
括号序列
给出一个仅包含字符'(',')','{','}','['和']',的字符串,判断给出的字符串是否是合法的括号序列
括号必须以正确的顺序关闭,"()"和"()[]{}"都是合法的括号序列,但"(]"和"([)]"不合法。
示例1
输入
题解:
利用栈的性质,
- 遍历到"(" "{" "[" 时:
- push "(" "{" "["进栈
- 遍历到 "(" "{" "[" 时:
- 与 栈中 pop() 出的元素比较
- 不相等返回 false;
public class Solution {
public boolean solution(String s){
Stack<Character> stack = new Stack<Character>();
for(char c : s.toCharArray()) {
if(c == '('){
stack.push(')');
}else if(c == '{'){
stack.push('}');
}else if(c == '['){
stack.push(']');
}else {
if(stack.isEmpty() || stack.pop() != c){
return false;
}
}
return true;
}
}
064-滑动窗口的最大值(双端队列)
题目描述
题解
代码
复杂度
Heap
029-最小的K个数
题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
示例1
输入
[4,5,1,6,2,7,3,8],4
返回值
[1,2,3,4]
题解
法一:大根堆(前 K 小) / 小根堆(前 K 大),Java中有现成的 PriorityQueue.
- 默认是小根堆,实现大根堆需要重写一下比较器。
PriorityQueue<Integer> queue = new PriorityQueue<>((o1,o2) -> o2-o1);
代码
import java.util.ArrayList;
import java.util.PriorityQueue;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
if(input.length < k || k < 1) return new ArrayList<>();
//默认是小根堆,实现大根堆需要重写一下比较器
PriorityQueue<Integer> queue = new PriorityQueue<>((o1,o2) -> o2-o1);
//遍历数组
for(int num : input){
//近小顶堆填满
if(queue.size() < k){
queue.add(num);
}
//小顶堆的堆顶为最大值,若遍历的数小于堆顶,将堆顶元素替换
if(queue.size() >= k && queue.peek() > num){
queue.poll();
queue.add(num);
}
}
return new ArrayList<>(queue);
}
}
复杂度
- 时间复杂度: O(NlogK)
- 空间复杂度: O(K)
Hash Table
034-第一个只出现一次的字符
题目描述
题解
代码
复杂度
图
065-矩阵中的路径(BFS)
题目描述
题解
代码
复杂度
066-机器人的运动范围(DFS)
题目描述
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入:m = 2, n = 3, k = 1
输出:3
示例 2:
输入:m = 3, n = 1, k = 0
输出:1
提示:
- 1 <= n,m <= 100
- 0 <= k <= 20
题解
1. DFS方法
这里统计的是能走多少个格子,所以统计肯定是不能有重复的,题中说了,机器人是可以沿着上下左右四个方向走的。但你想一下,任何一个格子你从任何一个方向进来(比如从上面进来),那么他只能往其他3个方向走,因为如果在往回走就重复了。但实际上我们只要沿着两个方向走就可以了,一个是右边,一个是下边,也就是上面图中红色的箭头。我们来看下代码
2. BFS方法
DFS是沿着一个方向一直往下走,有一种不撞南墙不回头的感觉,直到不满足条件才会回头。而BFS就显得有点博爱了,他不是一条道走下去,他会把离他最近的都访问一遍,访问完之后才开始访问第二近的……,一直这样下去,所以最好的一种数据结构就是使用队列,因为队列是先进先出,离他最近的访问完之后加入到队列中,最先入队的也是最先出队的。如下图,DFS就是沿着一条道走下去,然后再走其他的道……。BFS就是图中先访问圈内的部分,然后再把圈放大继续访问……。
代码
DFS
public int movingCount(int m, int n, int k) {
//临时变量visited记录格子是否被访问过
boolean[][] visited = new boolean[m][n];
return dfs(0, 0, m, n, k, visited);
}
public int dfs(int i, int j, int m, int n, int k, boolean[][] visited) {
//i >= m || j >= n是边界条件的判断,k < sum(i, j)判断当前格子坐标是否
// 满足条件,visited[i][j]判断这个格子是否被访问过
if (i >= m || j >= n || k < sum(i, j) || visited[i][j])
return 0;
//标注这个格子被访问过
visited[i][j] = true;
//沿着当前格子的右边和下边继续访问
return 1 + dfs(i + 1, j, m, n, k, visited) + dfs(i, j + 1, m, n, k, visited);
}
//计算两个坐标数字的和
private int sum(int i, int j) {
int sum = 0;
while (i != 0) {
sum += i % 10;
i /= 10;
}
while (j != 0) {
sum += j % 10;
j /= 10;
}
return sum;
}
BFS
public int movingCount(int m, int n, int k) {
//临时变量visited记录格子是否被访问过
boolean[][] visited = new boolean[m][n];
int res = 0;
//创建一个队列,保存的是访问到的格子坐标,是个二维数组
Queue<int[]> queue = new LinkedList<>();
//从左上角坐标[0,0]点开始访问,add方法表示把坐标
// 点加入到队列的队尾
queue.add(new int[]{0, 0});
while (queue.size() > 0) {
//这里的poll()函数表示的是移除队列头部元素,因为队列
// 是先进先出,从尾部添加,从头部移除
int[] x = queue.poll();
int i = x[0], j = x[1];
//i >= m || j >= n是边界条件的判断,k < sum(i, j)判断当前格子坐标是否
// 满足条件,visited[i][j]判断这个格子是否被访问过
if (i >= m || j >= n || k < sum(i, j) || visited[i][j])
continue;
//标注这个格子被访问过
visited[i][j] = true;
res++;
//把当前格子右边格子的坐标加入到队列中
queue.add(new int[]{i + 1, j});
//把当前格子下边格子的坐标加入到队列中
queue.add(new int[]{i, j + 1});
}
return res;
}
//计算两个坐标数字的和
private int sum(int i, int j) {
int sum = 0;
while (i != 0) {
sum += i % 10;
i /= 10;
}
while (j != 0) {
sum += j % 10;
j /= 10;
}
return sum;
}
复杂度
- 时间复杂度:O(MN)最差情况
- 空间复杂苏:O(MN)最差情况
本文作者:今天学了吗
本文链接:https://www.cnblogs.com/hugoyu/p/13979535.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了