【LeetCode热题100】打卡第35天:最小栈&相交链表
⛅前言
大家好,我是知识汲取者,欢迎来到我的LeetCode热题100刷题专栏!
精选 100 道力扣(LeetCode)上最热门的题目,适合初识算法与数据结构的新手和想要在短时间内高效提升的人,熟练掌握这 100 道题,你就已经具备了在代码世界通行的基本能力。在此专栏中,我们将会涵盖各种类型的算法题目,包括但不限于数组、链表、树、字典树、图、排序、搜索、动态规划等等,并会提供详细的解题思路以及Java代码实现。如果你也想刷题,不断提升自己,就请加入我们吧!QQ群号:827302436。我们共同监督打卡,一起学习,一起进步。
博客主页💖:知识汲取者的博客
LeetCode热题100专栏🚀:LeetCode热题100
Gitee地址📁:知识汲取者 (aghp) - Gitee.com
题目来源📢:LeetCode 热题 100 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台
PS:作者水平有限,如有错误或描述不当的地方,恳请及时告诉作者,作者将不胜感激
最小栈
🔒题目
原题链接:155.最小栈
🔑题解
-
解法一:使用两个栈
这个方式是真的巧妙,这次算是开眼界了。现在就让我来讲解一下这个思路的具体实现方式吧(●ˇ∀ˇ●)
首先我们要创建两个栈,一个 stack,一个minStack,stack是存储真实数据的栈,对外界是透明的,minStack是存储当前最小值的,对外界是不透明的。每次存储数据,stack是直接存,但是minStack是存储当前最小值,也就是要将要存储的值与minStack的栈顶元素进行比较,选较小值存,这样我们调用getMin时,直接返回minStack的栈顶元素即可
/** * @author ghp * @title * @description */ class MinStack { Deque<Integer> stack; Deque<Integer> minStack; public MinStack() { this.stack = new LinkedList<>(); minStack = new LinkedList<>(); // 初始化,防止出现NPE minStack.push(Integer.MAX_VALUE); } public void push(int val) { stack.push(val); minStack.push(Math.min(minStack.peek(), val)); } public void pop() { stack.pop(); minStack.pop(); } public int top() { return stack.peek(); } public int getMin() { return minStack.peek(); } }
复杂度分析:
- 时间复杂度: O ( 1 ) O(1) O(1)
- 空间复杂度: O ( n ) O(n) O(n)
其中 n n n 为栈中元素的个数
拓展:为什么一般栈使用Deque而不是Stack?
这就需要考虑到两者的底层实现了,算法中一般使用
Deque
(双端队列)而不是Stack
(栈)的主要原因如下:
Stack
是Java集合框架中提供的一种特殊数据结构,仅限于在栈顶进行元素的插入和删除操作,而Deque
具有更加丰富的操作方法,可以在队首和队尾进行元素的插入、删除和检索。Stack
类在Java中继承自Vector
类,并且Stack
类的方法都是同步的。因此,在并发环境下使用Stack
可能会带来性能上的开销。而Deque
的实现类LinkedList
不是同步的,可以更好地满足多线程环境下的需求。- 在Java 6之后,
Deque
被引入到Java集合框架中,将Stack
类推荐用Deque
接口的实现类来代替,以便提高代码的可读性和一致性。总结起来,使用
Deque
而不是Stack
有助于代码的可读性和一致性,同时避免了可能的性能开销和线程安全问题。因此,在大多数情况下,建议使用Deque
来实现栈数据结构。
-
解法二:使用 一个栈 + 一个变量
不得不感慨这种方法更加巧妙,栈这个数据结构简直可以玩出花了🤣
class MinStack { private Deque<Integer> stack; private int min; public MinStack() { this.stack = new LinkedList<>(); this.min = Integer.MAX_VALUE; } public void push(int val) { if (val <= min) { // 当前值是更小,更新最小值,并且将之前的最小值入栈 // 当前值等于最小值时,也需要进行入栈,否则后面会出现NPE stack.push(min); min = val; } stack.push(val); } public void pop() { if (stack.pop() == min){ // 栈顶元素等于最小值,则更新最小值 min = stack.pop(); } } public int top() { return stack.peek(); } public int getMin() { return min; } }
复杂度分析:
- 时间复杂度: O ( 1 ) O(1) O(1)
- 空间复杂度: O ( n ) O(n) O(n)
其中 n n n 为栈中元素的个数
相交链表
🔒题目
原题链接:160.相交链表
🔑题解
-
解法一:哈希表
public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { Set<ListNode> set = new HashSet<>(); // 将链表A的所有节点存入Set集合中 while (headA != null){ set.add(headA); headA = headA.next; } // 找出链表B和链表A的共有节点 while (headB != null){ if (set.contains(headB)){ // 链表B的节点在链表A中也出现了,说明两者相交了 return headB; } headB = headB.next; } return null; } }
复杂度分析:
- 时间复杂度: O ( n + m ) O(n+m) O(n+m)
- 空间复杂度: O ( n ) O(n) O(n)
其中 n n n 为链表A的节点个数, m m m为链表B的节点个数
-
解法二:双指针
双指针解法,是使用两个指针,遍历两遍链表,第一遍遍历让两个指针处于同一水平位置,第二遍遍历就可以直接找到交点了
备注:红色代表第一次遍历,蓝色代表第二次遍历
如果你一上来就来看这个方法,你可能会感觉优点不可思议,但事实如此。
现在就让我们来证明一下:
假设A链表的头节点a距离交点的距离为 p,B链表的头节点b距离交点 q,公共的长度为 t,则有一下推导
-
如果A和B不相交:
①A和B一样长,p=p.next, q=q.next,最终都为null
②A和B不一样长,A长B短,首先B走到终点,A和B走了q,然后B放到A的起点,A和B走了p,然后将A放到B的起点,循环往复,最后A走了 p+q、B走了 q+p ,A来到B的终点,B来到A的终点,最终都是null
-
如果A和B相交:
①A和B一样长,有 p=q,所以两者在 p(q)处相遇
②A和B不一样长,A长B短,首先B走到终点,A和B走了 q+t,B放到A的起点置0,此后再走p,此时B来到交点处,而A走了 q+t+p,而 t+p 表示A走到终点,要放到B链表的起点,但此时还剩 q 步,A从B的起点走 q 步,不正是交点处吗,所以两者走
p+q+t 处时,两者在交点相遇
当然我只是比较简单的推导了一下,如果想要看更加严格的数学证明,可以参考 Krahets 题解,链接附文末
PS:这个推导十分类似于【LeetCode热题100】打卡第33天的题目环形链表II,这题我同样也是参考了 Krahets 的题解
public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode p = headA, q = headB; while (p != q) { p = p != null ? p.next : headB; q = q != null ? q.next : headA; } return p; } }
复杂度分析:
- 时间复杂度: O ( n + m ) O(n+m) O(n+m)
- 空间复杂度: O ( 1 ) O(1) O(1)
其中 n n n 为链表A的节点个数, m m m为链表B的节点个数
从这题我们也可以明白一个道理人生哲理:如果你和我有交点,走过你来时的路,我们终会相遇,如果你和我没有交点,走遍整个人生我们都不会相遇而是走向各自人生的终点null
-
参考题解:
在此致谢LeetCode哪些无私共享题解的Coder,也欢迎大家给他们点赞
最后,如果觉得本文对你有帮助,欢迎三连(点赞👍+评论✍+收藏⭐)