链表栈队列
- 链表问题的最优解往往是在空间复杂度上下功夫,就是怎样用最小的空间复杂度
- 快指针 慢指针
剑指Offer链表题目(9个):
- 6 从尾到头打印链表
- 18 在O(1)时间内删除链表结点
- 22 链表中倒数第k个结点
- 23 链表中环的入口结点
- 24 反转链表
- 25 合并两个排序的链表
- 35 复杂链表的复制
- 36 二叉搜索树与双向链表
- 52 两个链表的第一个公共结点
- 68
6 从尾到头打印链表(☆)
6.1 反转链表法(会改变链表结构,询问是否允许修改输入数据)
6.2 借助栈
6.3 递归(不时候链表很长的时候)
easy,过。
18 在O(1)时间内删除链表结点(☆☆)
18.1 顺序法,找到要删除的结点,如下图(b)。时间:O(n)
18.2 O(1)时间复杂度的值覆盖法:
假设要删除结点i,把j的值赋值给结点i,删除j(i指向j的next) 。这种时间复杂度O(1) 。
前提待删除结点在链表中。还有对链表只有1个结点 和 要删除的结点是尾结点做分别处理
22 链表中倒数第k个结点(☆☆)
双指针法:p1,p2指向投结点,快指针p1先走k步,后面一起走
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode p1 = head,p2 = head;
while(k > 0){
p1 = p1.next;
k--;
}
while(p1 != null){
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
快指针p1先走k步,后面一起走.
![]()
23 链表中环的入口结点(☆☆☆)
双指针法:快指针遍历到null时,则没有环。有环前提下2指针相遇后,快指针回到起点,循环同时往后面走一步,相遇时的结点即为环的入口结点。
记住这个法则就可以,过。
24 链表反转(☆☆☆☆☆字节)
迭代法
采用头插法,定义3个指针:旧链表cur,新链表head, 临时指针temp指向cur的next
递归法
- 因为最终结果是最后一个结点是反转后的根,我们就要返回根,递归直到最后一个结点,返回head,也就是递归最后1次返回的是最后一个结点
- 递归到倒第二个结点,让他的后一个结点指向他自己head.next.next = head
- 因为要求第一个结点的Next为null, 所以再加一句head.nxet = null;
public ListNode reverseList
public ListNode reverseList(ListNode head) { if (head == null || head.next == null){ return head; } ListNode p = reverseList(head.next); head.next.next = head; head.next = null; return p; }
25 合并2个有序链表(☆☆☆☆☆字节)
递归去做。
https://leetcode-cn.com/explore/featured/card/top-interview-questions-easy/6/linked-list/44/
递归退出条件,l1空返回l2,l2空返回l1
1.比较l1,l2,值下的就是head
2.递归调用mergeTwoList函数返回给定参数l1,l2合并后链表的头结点。
3.head.next 指向上面返回的
35 复杂链表的复制(☆☆☆)
法一:时间复杂度O(n^2^),空间复杂度O(1)
①复制原始链表上的每个结点,并用next链接起来
②设置每个结点的sibling属性,需要遍历链表,每个结点需要O(n)
法二:时间复杂度O(n),空间复杂度O(n)
针对第二步优化,空间换时间,要想每个结点sibling的查找 达到O(1),自然想到哈希表
假设原始链表结点N的sibling指向结点S,那么复制链表N指向S
,哈希表保存N,N`
法三:时间复杂度O(n),空间复杂度O(1)
步骤①复制原始链表上的每个结点,把N`接在N的后面
②设置复制出来的节点的sibling属性
③拆分成2个链表:把奇数位置的结点用next连接起来就是原始链表, 把偶数位置的结点用next连接起来就是复制链表。

注 : 三种方法背住
36 二叉搜索树与双向链表(☆☆☆)
任然采用递归(也写得出来)
将二叉搜索树的左孩子指向前驱,右孩子指向后继,采用中序递归遍历来操作.
把二叉树看成三部分,根,左子树,右子树
把左右子树转换成双链表后和根节点接起来,根节点的前驱指向左子树中序遍历的最后一个结点;
根节点的后继指向右子树的最小结点,转换右子树前先存起来它的最小结点
注:解决的关键在于把1个大问题分解成几个小问题,然后递归地处理.
private TreeNode convertprivate TreeNode convert(TreeNode pRootOfTree) {
if (pRootOfTree == null){
return null;
}
//1.转换左子树,返回中序遍历的最后一个结点
TreeNode lastNode = convert(pRootOfTree.left);
//根节点的左孩子指向左子树中序遍历的最后一个结点(转换双链表)
if (lastNode != null){
lastNode.right = pRootOfTree;
pRootOfTree.left = lastNode;
}
//2-pre把先右子树的最小结点存下来
TreeNode successorNode = findSuccessorNode(pRootOfTree.right);
//2.转换右子树
TreeNode lastNode2 = convert(pRootOfTree.right);
//根结点的右孩子指向右子树的最小结点(转换双链表)
pRootOfTree.right = successorNode;
if (successorNode != null){
successorNode.left = pRootOfTree;
}
//右子树为空,根节点就是中序遍历的最后1个结点,返回它
if (lastNode2 == null){
return pRootOfTree;
}
//返回以pRootOfTree为根结点的中序遍历的最后一个结点
return lastNode2;
}
注: 有点难
52 两个链表的第一个公共结点(☆☆)
多种解法,用O(m+n)的,leetcode有做过2次.
特点:相交后是一条链表
https://leetcode-cn.com/problems/intersection-of-two-linked-lists/description/
方法一:长链表先走几步
方法二:哈希表法(易理解),时间复杂度O(m+n),空间复杂度O(m)或O(n)
基于相交以及后面的部分是一样的,是一条链表,比较引用/地址是否在哈希表中
方法三:双指针法(不易理解,参考题解有图示),时间复杂度O(m+n),空间复杂度O(1)
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
这个题跟结点的值val不太有关系,比较的是引用,也就是当指向的是同一个对象时,即为交点
2.回文链表
法一:将值复制到数组中后用双指针法判断是否为回文 时间复杂度:O(n) 空间复杂度:O(n)
法三:时间复杂度:O(n) 空间复杂度:O(1) 快指针,慢指针 额外空间 需修改原结构
- 找到前半部分链表的尾节点。
- 反转后半部分链表。
- 判断是否为回文。
- 恢复链表。
- 返回结果。
(这个题最优解需要改结构,工程上不会让你这么做,面试上会)
3.打印有序链表的公共部分?(过)
自己练
链表问题的最优解往往是在空间复杂度上下功夫,就是怎样用最小的空间复杂度
笔试过程中:尽快过掉,该用辅助空间就用辅助空间;
面试过程中:尽量时间复杂度O(n),空间复杂度O(1)
4.【题目14 单链表最难】
1)如何判断单链表有环?
- 用哈希表;
- 不用哈希表(很玄学,这里有个结论)分别怎么做 (双指针法)
- 满指针走一步,快指针走2步(初始都从第一个结点开始走),循环直到快指针=慢指针
- 如果快指针为空,也退出循环,返回null
- 否则,快指针指向起点,循环快慢指针各走一步,直到2个指针指向同一个结点,那就是第一个入环结点
2)如何判断2个无环单链表相交,并返回交点;
- 用哈希表;
- 不用哈希表,记录长度;
- 不用哈希表,也不用记录长度;
3)如何判断2个有环单链表相交3种情况
4)一个有环和一个无环单链表是否相交:结论–>不相交
队列
特点:先进先出,一端(队尾)插入,一端(队头)删除
应用:
队列的实现:Queue
一般实现 | JDK 接口 Queue |
|
---|---|---|
入队 | boolean enqueue(E) | boolean add(E e); |
boolean offer(E e); | ||
出队 | E dequeue() | E remove(); |
E poll(); | ||
取队头 | E getFront() | E element(); |
E peek(); | ||
获取队列大小 | int getSize() | int size();//继承自Collection |
队列是否为空 | boolean isEmpty(); | boolean isEmpty(); |
add和offer有什么区别?
在没有容量限制的队列中,两者一样,LinkedList 中2者实现就是一样的,且永远返回true。
在有容量限制的队列中,建议使用offer,前者抛出IllegalStateException,后者返回false。
数组实现队列:
就是内部用动态数组(可以自己实现) 实现上面几个方法,非常简单。
入队:调用动态数组的addLast方法.
出队:调用动态数组的removeFirst方法
插入O(1),删除O(n).
数组实现循环队列:
front:指向队首元素
end:指向队尾元素的下一个位置
初始队列中没有元素时,front指向0,end指向0
定义:队列空,front 等于 end
队列满:(tail + 1) % 队列容量c == front ,要留1个空的位置。
插入平摊O(1),删除平摊O(1).
//1.定义Queue接口,定义5个操作
//2.定义LoopQueue类,1个队头front,1个队尾end,1个数据数组E[] mData,一个队列当前元素个数size
//3.提供无参构造函数和带参(容量)的构造函数,因为要留1个空位表示队列满,因此new 的容量为 capactity + 1
//4.入队时,队列满则扩容,出队时,队列大小为容量的1/4时,缩容到原来的一半
public class LoopQueue<E> implements Queue<E> {
private E[] mData;
private int size;
private int front;
private int end;
public LoopQueue() {
this(10);
}
public LoopQueue(int capacity) {
mData = (E[])new Object[capacity + 1];
front = 0;
end = 0;
size = 0;
}
@Override
public E poll() {
if (isEmpty()){
throw new IllegalArgumentException("the queue is empty");
}
E ret = mData[front];
mData[front] = null;
front = (front + 1) % mData.length;
size--;
if (size == getCapacity() / 4 && getCapacity() / 2 != 0){
resize(getCapacity() / 2);
}
return ret;
}
@Override
public boolean offer(E e) {
//队列满
if(front == (end + 1) % mData.length){
//扩容
resize(getCapacity() * 2);
}
mData[end] = e;
end = (end + 1) % mData.length;
size++;
return true;
}
private void resize(int capactity) {
E[] newObjects = (E[])new Object[capactity + 1];
for (int i = 0; i < size; i++) {
newObjects[i] = mData[(front + i) % mData.length];
}
mData = newObjects;
front = 0;
end = size;
}
public int getCapacity(){
return mData.length - 1;
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return front == end;
}
@Override
public E peek() {
return mData[front];
}
@Override
public String toString() {
return "LoopQueue{" +
"mData=" + Arrays.toString(mData) +
", size=" + size +
", front=" + front +
", end=" + end +
'}';
}
}
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!