算法学习03:栈和队列
栈和队列
用数组结构实现大小固定的队列和栈
- 栈结构很好实现,准备变量index,指向栈顶
- 当压入一个数,index++
- 当弹出一个数,index- -
- 越界条件: index超了
- 队列结构的实现: 循环队列
- 一般的循环队列有两个指针: start和end
- 加以改进的循环队列: 再加一个变量
size = end-start
. 用size来解耦start和end,让end与start之间不再发生关系.
最小栈leetcode 155
最小栈的pop()
,push()
,getMin()
操作的时间复杂度都是O(1)
解法:
- 存储两个栈,
data
栈和min
栈,data
栈存储压入的数据,min
栈存储当前栈中最小值 - 当向
data
压入一个数时,与min
栈栈顶比较,若被压入的数比min
栈顶小,则向min
栈压入新数,否则向min
栈重复压入min
栈顶.
栈和队列的相互实现leetcode 232,leetcode 225
用队列实现栈: 前边所有数都入队列,留着最后一个数给用户返回.
用栈实现队列: 创建两个栈:push
栈和pop
栈,进队列时进push
栈,出队列时从pop
栈往外拿. 中间可能要把数据从一个栈倒入另一个栈,每次倒数据的时候要直接倒完.
宏观划分问题
对于一些问题,其问题需要不断的考虑边界问题,比较难想,这样我们就遵循先宏观再微观的思路.
先提取出宏观思路来把问题划分成微观步骤,然后通过函数考虑局部的具体实现.
转圈遍历矩阵leetcode 54
将遍历一圈提取成printCircle()
函数,然后从左上角和右下角开始,每打印一圈两个角向里侧移动一位,直到两个角相遇.
printCircle()
要考虑边界情况: 左上角和右下角位于同一行或同一列.
旋转正方形矩阵leetcode 48
将遍历一圈提取成rotateCircle()
函数,然后从左上角和右下角开始,每打印一圈两个角向里侧移动一位,直到两个角相遇.
rotateCircle()
要考虑边界情况: 左上角和右下角相遇,实际只有一个元素,不用旋转.
“之”字形打印矩阵
将打印一条斜线提取成writeLine()
函数,接收斜线的两个顶点和打印方向. 初始斜线的顶点应该为矩阵左下角,每打印一行,顶点向右下移动一位,且方向转变一次.
最优解来自于数据状况或问法
在行列都排好序的矩阵中找数(leetcode 74)
- 这个数据状况比较特殊,可以帮助我们找到解法:
- 每一个数都与其同一行和同一列的数有明确的大小关系,而与其斜对角线上的数没有大小关系.因此每次比较大小后应该向其同一行或同一列转移,下面考虑从哪里开始比较.
- 若从左上(最小值)或者右下(最大值)比较后,有两个可转移的方向,因此我们从右上角或左下角开始比较.
- 从右上角开始找,令指针指向右上角的节点,
- 若一个数比目标大,则该数以下必不是目标,因此指针向左移动一位;
- 若一个数比目标小,则该数以左必不是目标,因此指针向下移动一位.
- 注意指针不越界边界条件:
((curY < rowNum) && (curX >=0))
,一边是等于,一边是不等于.另外对二维数组进行索引时先索引y,后索引x,matrix[y][x]
推广: 双指针问题leetcode 15
- 问题:leetcode中有一系列
k-sum
问题,判断数组中能否找出k
个数之和为目标aim
? - 思路:使用双指针扫描方法可以将问题降维: 先将数组排序,然后两个指针分别指向数组首尾向中间逼近,不断判断两指针之和是否为目标
aim
,这样3sum
可以化成2sum
,4sum
可以化成3sum
. - 实现:
2sum
的执行过程如下:
- 现将数组排序,设定两个指针
p1
和p2
,分别指向数组的第一个元素和最后一个元素. - 计算两个元素的和
p1+p2
- 若其和大于目标
aim
,则p2
向前移动一位. - 若其和小于目标
aim
,则p1
向后移动一位. - 若其和等于目标
aim
,则找到解,返回两个指针.
- 若其和大于目标
- 若
p1
和p2
相遇,则返回未找到
,否则继续步骤2.
- 证明: 数组中任意元素两两相加构成一张表,其横坐标为
第一个加数
,纵坐标为第二个加数
,在这个表中查找目标值aim
.这个问题就转化为上边的在行列有序的表中查找值
的问题了.
初始状态下,p1
指向数组首,p2
指向数组末尾,相当于这个二维表左下角的点.p1
向后移动对应二维表中向右移动.p2
向前移动对应二维表中向上移动.
假 设 横 纵 坐 标 分 别 为 1 − 4 [ 1 2 3 4 2 3 4 5 3 4 5 6 4 5 6 7 ] 假设横纵坐标分别为1-4\\ \begin{bmatrix} 1 & 2 & 3 & 4 \\ 2 & 3 & 4 & 5 \\ 3 & 4 & 5 & 6 \\ 4 & 5 & 6 & 7 \\ \end{bmatrix} 假设横纵坐标分别为1−4⎣⎢⎢⎡1234234534564567⎦⎥⎥⎤
链表问题:笔试与面试要求不同
链表的题目时间复杂度难以优化,关键优化的点在于空间复杂度.
因此笔试链表,优先考虑如何过掉这道题.
面试链表,要注意考虑用更少空间复杂度.
判断一个链表是否为回文结构
快慢指针:
快指针指向末尾,慢指针走向中点.然后把右半部分逆序.
之后从两头开始向中间走,比较是否相等
最后在把右半部分逆序回来.
注意:
- 链表长度为偶数或者奇数会对两个指针产生影响.
- 反转链表函数中要注意最后把链表头结点的next置空.
将单向链表按某值划分成左边小、中间相等、右边大的形式(荷兰国旗问题)
准备成数组.然后再穿起来.
但是荷兰国旗问题不保证稳定性,且要O(N)空间
设置三个指针节点:equal,more,less
遍历一遍数组,把相应的节点挂在三个指针后边.
注意扣边界: 有个区域没节点.
复制含有随机指针节点的链表leetcode 138
遍历两次链表,第一趟复制表的next结构,第二趟复制表的random指针.
具体实现有两种方式:
- 使用map,较简单
- 在原链表每个节点后面复制出新节点.
注意:
1. 复制之后再遍历链表的话需要指针每次转移跳两格,要注意是否产生空指针错误,要将cur.next = cur.next.next;
改为cur.next = (cur.next != null) ? cur.next.next : null;
2. 注意有一些节点的random指针指向空,这种复制的时候要格外小心,别发生空指针错误.cur.next.random = (cur.random != null) ? cur.random.next : null;
两个单链表相交的一系列问题
问题: 判断两个链表是否相交,若相交,返回首个交点. 其中两个链表都可能有环或者无环.
转化: 该问题可以转化为三个小问题:
- 判断链表是否有环,若有环返回入环节点,(快慢指针方法:
快指针
每次走两步,慢指针
每次走一步,直到相交
. 然后新建两个指针
,分别指向交点
与指向头结点
,两个指针同步后移
直到他们相遇,相遇点为入环节点
)leetcode 142 - 判断两个无环链表的首个交点(先遍历两个链表,得出长度差值,根据差值对齐链表,然后两个头指针同步后移直到相遇)leetcode 160
- 判断两个可能有环链表的首个交点
解法- 先找到两个列表是否有环,并返回入环点.
- 若两列表都无环,则转化为无环链表求交点问题.
- 若一个链表有环,一个链表无环,则它们不可能相交.(反证:若相交,则无环链表最终将进环)
- 若两链表都有环,则先比较两入环点是否相同
- 若两入环点相同,则说明入环以前两链表已经相交,所以对于入环前节点可以简化为无环链表求交点问题.
- 若两入环点不同,则比较两入环点是否相连:
1. 若两入环点不相连,说明两链表无交点(一个链表不可能有两个环)
2. 若两入环点相连,则他们都是最早交点(这种情况下无非是哪个离两链表较近的问题了,无所谓首个交点)
- 先找到两个列表是否有环,并返回入环点.