移动零
问题的两个要求是:
- 将所有 0 移动到数组末尾。
- 所有非零元素必须保持其原始顺序。
这里很好地认识到这两个需求是相互排斥的,也就是说,你可以解决单独的子问题,然后将它们组合在一起以得到最终的解决方案。
方法:
- 一个指针
i
从前到后遍历数组,每当遇到非0数时,令nums[cur] = nums[i]; cur++;
,cur
是另一个指针(从0开始),记录当前位置。 - 当
i
遍历完后,此时cur
指向的位置到数组的末尾全赋为0即可。
1 | class Solution { |
复杂度
- 时间复杂度:O(n)。
- 空间复杂度:O(1)。
最小覆盖子串
方法:滑动窗口
算法
初始,
left
指针和right
指针都指向s
的第一个元素.将
right
指针右移,扩张窗口,直到得到一个可行窗口,亦即包含t
的全部字母的窗口。得到可行的窗口后,将
left
指针逐个右移,若得到的窗口依然可行,则更新最小窗口大小。若窗口不再可行,则跳转至 2。
代码
1 | from collections import defaultdict |
复杂度
滑动窗口最大值
方法一:暴力法
遍历每个滑动窗口,找到每个窗口的最大值。一共有 N - k + 1 个滑动窗口,每个有 k 个元素,于是算法的时间复杂度为 O(Nk),表现较差。
代码
1 | class Solution: |
复杂度
- 时间复杂度:O(Nk)。其中
N
为数组中元素个数。 - 空间复杂度:O(N−k+1),用于输出数组。
方法二:递归
详细讲解见https://leetcode-cn.com/problems/sliding-window-maximum/solution/hua-dong-chuang-kou-zui-da-zhi-by-leetcode-3/
代码
1 | class Solution: |
复杂度
时间复杂度:O(N),我们对长度为 N 的数组处理了 3次。
空间复杂度:O(N),用于存储长度为 N 的 left 和 right 数组,以及长度为 N - k + 1的输出数组。
方法三:双端队列
思路:维护窗口,向右移动时左侧超出窗口的值弹出,因为需要的是窗口内的最大值,所以只要保证窗口内的值是递减的即可,小于新加入的值全部弹出。最左端即为窗口最大值
代码
1 | class Solution: |
复杂度
时间复杂度:O(N),每个元素被处理两次- 其索引被添加到双向队列中和被双向队列删除。
空间复杂度:O(N),输出数组使用了O(N−k+1) 空间,双向队列使用了 O(k)。
二叉树的中序遍历
中序遍历:左-中-右
方法一:递归
定义一个辅助函数来实现递归。
代码
1 | class Solution { |
复杂度
时间复杂度:O(n)。递归函数 T(n) = 2 T(n/2)+1。 空间复杂度:最坏情况下需要空间O(n),平均情况为O(logn)。
方法二:迭代
颜色标记法:
作者:hzhu212 链接:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/yan-se-biao-ji-fa-yi-chong-tong-yong-qie-jian-ming/ 来源:力扣(LeetCode)
其核心思想如下:
- 使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。
- 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。
- 如果遇到的节点为灰色,则将节点的值输出。
代码
1 | # Definition for a binary tree node. |
如要实现前序、后序遍历,只需要调整左右子节点的入栈顺序即可。
另一种写法:
1 | # Definition for a binary tree node. |
复杂度
时间复杂度:O(n)。
空间复杂度:O(n)。
算法 | 执行用时 | 内存消耗 | 语言 |
---|---|---|---|
方法二:迭代(另一种写法) | 28 ms | 13 MB | Python3 |
方法二:迭代(颜色标价法) | 36 ms | 12.7 MB | Python3 |
方法一:递归 | 0 ms | 34.6 MB | Java |
二叉搜索树中第k小的元素
此题与二叉树的中序遍历(难度:中等)类似,只需稍加变化。
方法一:递归
通过构造 BST 的中序遍历序列,则第 k-1
个元素就是第
k
小的元素。
代码
1 | /** |
复杂度
时间复杂度:O(N),遍历了整个树。
空间复杂度:O(N),用了一个数组存储中序序列。
方法二:迭代
在栈的帮助下,可以将方法一的递归转换为迭代,这样可以加快速度,因为这样可以不用遍历整个树,可以在找到答案后停止。
代码
1 | # Definition for a binary tree node. |
复杂度
时间复杂度:O(H+k),其中 H指的是树的高度,由于我们开始遍历之前,要先向下达到叶,当树是一个平衡树时:复杂度为O(logN+k)。当树是一个不平衡树时:复杂度为 O(N+k),此时所有的节点都在左子树。
空间复杂度:O(H+k)。当树是一个平衡树时:O(logN+k)。当树是一个非平衡树时:O(N+k)。
最长连续序列
方法:哈希表(HashSet)
因为一个序列可能在 nums
数组的任意一个数字开始,我们可以枚举每个数字作为序列的第一个数字,搜索所有的可能性
(暴力法)。优化:将数组中的数字用HashSet保存,实现O(1)的时间查询,同时,我们只对
当前数字 - 1
不在哈希表里的数字,作为连续序列的第一个数字去找对应的最长序列,这是因为其他数字一定已经出现在了某个序列里。
1 | class Solution { |
复杂度
时间复杂度:O(n)。尽管在 for 循环中嵌套了一个 while 循环,时间复杂度看起来像是二次方级别的。但其实它是线性的算法。因为只有当 curNum 遇到了一个序列的开始, while 循环才会被执行(也就是 curNum-1 不在数组 nums 里), while 循环在整个运行过程中只会被迭代 n 次。这意味着尽管看起来时间复杂度为 O(n⋅n) ,实际这个嵌套循环只会运行O(n+n)=O(n) 次。所有的计算都是线性时间的,所以总的时间复杂度是 O(n)的。
空间复杂度:O(n)。为了实现 O(1)的查询,我们对哈希表分配线性空间,以保存 nums 数组中的 O(n)个数字。