计划对LeetCode刷题,顺便总结一下,四个阶段:
- 找出典型中等、困难题目,走读题目根据自己还记得的算法尝试回忆一遍
- 对照答案,识别自己的算法弱项,巩固算法后练习一遍典型习题
- 尝试找出同类型题目,巩固练习一遍
- 找出算法短板,重复1/2/3
算法总结
题目 | 我的算法 | 典型算法 |
---|---|---|
建筑物的最短距离 | 暴力算法,挨个点计算距离和,然后得出最小的距离和,过程中使用BFS同时记录访问过点 | 解法来看都是BFS,不过有两种思路: 1、从每个建筑物出发BFS,计算每个空地的权值和,最后找到权值和最小的空地点 2、从每个空地出发BFS,计算每个空地到所有建筑物的权值和,最后找到权值和最小的空地点; 感觉从建筑物出发的方式好一些。 |
寻找峰值 | 暴力破解,遍历一次数组,从index=1开始,满足条件则返回;不满足则从index+2的位置进行判断 | 我的解法是官方解法的一种,但是时间复杂度是On,不满足Onlog2n的要求,采取二分查找的方式找到这个峰值点,这样就能Onlog2n,一般使用二分的前提是有序,而本题中药将有序替换为局部有序,可以按照如下方式理解,当我在其中一个山峰,其实有三种可能 1. 左边的山峰比我现在在的山峰还高(注意确保左边还有山峰),因为只要找一种可能,我就去左边找比我高的 2. 右边的山峰更高(注意确保右边还有山峰),我就去右边找 3. 如果左右两边都没有我高,那我就是其中一个顶峰。 |
只出现一次的数字 | 暴力破解,和之前做过的一个单身狗的题一样,那个题的是情人节找出广场上的单身狗 | 使用集合或者hashset解决问题,时间复杂度Onlog2n,空间复杂度On,通过异或运算,时间复杂度On,空间复杂度O1;由于题目中只有一个不一样的,那么在一次遍历中,两两相同的数字通过异或,就会变成0,而我们要找的target = target ⊕ 0,恰好能得出target |
课程表 | 正经思路应该是并查集,先mark一下 | 想多了,不是并查集,而是拓扑排序,拓扑排序意为对一个有向图来说,是否存在一个点的排序序列,使得任意一个有向边(u,v),u在排序序列中一定在v前面,其实就是判断图是否是一个DAG,有向无环图,有两种解法可以获得拓扑排序: 1、BFS; 2、DFS; 具体的代码暂时没看,先mark一下。 |
矩阵中的最长递增路径 | 递归,遍历每个点,递归计算最深路径 | 官方解法 解法一:记忆化深度优先搜索,其实和我想的保存最优解的递归一样,这个思路不做赘述,只是写法不同; 解法二:拓扑排序,具体的代码暂时没看,先mark一下。 |
被围绕的区域 | 暴力破解,只要不爱这边的O,都变成X | 审题错误,和边相连的无效点,可能会和内部其他点联通,从而整体形成不被包围的多个点,类似围棋的‘气’,因此应该用DFS/BFS等搜索算法对所有点进行标记识别; 1、DFS,分别计算出有气的1点,即矩阵四周的1点,从1点DFS矩阵的1的联通点,进行标记;完成所有1点的DFS,进行遍历就算更新标记过的点不变,未标记过的全部变成X; 2、BFS,同时对所有有气的1点进行遍历标记,最终也是遍历矩阵并对标记状态进行计算; |
情侣牵手 | 没有解决办法 | 审题错误,交换过程可以不相邻。 解法一:贪心,从0开始遍历是否满足配对,不满足则找到对方在哪儿,然后做一次交换并记录;找到对方可以通过遍历,也可以通过一次数据预处理将对方位置放在一个map里,方便快速找到对方下标没搞明白这个贪心算法在哪儿? 解题过程如下: ![]() 待补充 |
寻找重复子树 | 暴力破解,求出所有子树,去重算出结果 | 解法一:暴力破解,不过暴力过程中,保存子树的方式是用二叉树的序列化方法表示![]() 使用BFS或DFS遍历二叉树的每个节点,算出每个子树的序列化表示,通过map判断重复得到结果。 解法二:唯一标识子树,认为每个子树都可以用一个唯一id表示,id = node.val + getId(leftNode) + getId(rightNode),将所有的子树id都放入map中,判断重复可得到结果; 第一种方法需要基于每个节点计算子树,因此n个节点,要进行n次子树的计算,时间复杂度On2;解法二只用遍历一次二叉树即可获得所有子树的 id,其本质是自底向上的遍历计算,和第一种自顶向下的区别就在这里,因此时间复杂度是On |
整体看题目难度,比较困难的是:寻找峰值、情侣牵手、课程表、只出现一次的数字,首先算法上情侣牵手要向并查集建模,而课程表要做拓扑排序,如果时间/空间复杂度有要求的情况下,解法相对唯一,另外包括只出现一次的数据,解法优化的唯一解也是异或。寻找峰值这个题在特殊场景下需要对二分查找进行优化。
涉及到算法:拓扑排序、异或、并查集、DFS、BFS、二分查找优化、二叉树序列化
贪心算法和拓扑排序,包括二分查找相同,泛指一类算法,本次练习过程中,发现二分查找本质上是运用规律进行折半查找,而有序只是在大多数场景下用到的一种折半原则,本次练习的寻找峰值就可以用趋势来替代有序,类似贪心算法、拓扑排序一样,实现方法有很多,总体思路是可以进行归类的。
建筑物的最短距离
链接:https://leetcode-cn.com/problems/shortest-distance-from-all-buildings
你是个房地产开发商,想要选择一片空地 建一栋大楼。你想把这栋大楼够造在一个距离周边设施都比较方便的地方,通过调研,你希望从它出发能在 最短的距离和 内抵达周边全部的建筑物。请你计算出这个最佳的选址到周边全部建筑物的 最短距离和。
提示:
你只能通过向上、下、左、右四个方向上移动。
给你一个由 0、1 和 2 组成的二维网格,其中:
0 代表你可以自由通过和选择建造的空地
1 代表你无法通行的建筑物
2 代表你无法通行的障碍物
示例:
输入:[[1,0,2,0,1],[0,0,0,0,0],[0,0,1,0,0]]
1 - 0 - 2 - 0 - 1
| | | | |
0 - 0 - 0 - 0 - 0
| | | | |
0 - 0 - 1 - 0 - 0
输出:7
解析:
给定三个建筑物 (0,0)、(0,4) 和 (2,2) 以及一个位于 (0,2) 的障碍物。
由于总距离之和 3+3+1=7 最优,所以位置 (1,2) 是符合要求的最优地点,故返回7。
题目数据保证至少存在一栋建筑物,如果无法按照上述规则返回建房地点,则请你返回 -1。
思路:计算图的中心点,但考虑到障碍物,不能直接算几何中心,根据提示,可能会有无法到达的建筑,目前只能想到暴力算法,挨个点计算距离和,然后得出最小的距离和
我们认为每个0点有一个属性distance[],记录到所有1点的最短距离,该0点的最短距离就是SUM distance[]
1、遍历图中的每一个0的位置,以每一个0点作为出发点,BFS算出到其他1点的距离
2、如果有1点无法到达,返回-1
3、一次BFS的过程中,可以计算出每个途径点到所有1点的最短距离
4、比对每个0点的最短距离和
按照从建筑物出发BFS/DFS,分别尝试了一下,没有AC,都卡在同一个场景下了。
题有点难,先mark,回头再做。
寻找峰值
链接:https://leetcode-cn.com/problems/find-peak-element
峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞。
示例 1:
输入: nums = [1,2,3,1]
输出: 2
解释: 3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
输入: nums = [1,2,1,3,5,6,4]
输出: 1 或 5
解释: 你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。
说明:
你的解法应该是 O(logN) 时间复杂度的。
思路:暴力破解,遍历一次数组,从index=1开始,满足条件则返回;不满足则从index+2的位置进行判断
1、从index=1的位置判断是否满足条件
2、满足则返回,不满足则index=index+2,
3、重复1的逻辑
感觉暴力法就很快(第一次解答错误是因为返回的是数值而不是索引)
/**
* 1、暴力解法
* 直接遍历,看看是否满足升序,如果出现满足,则找到峰值,最大On
* 对于数据有倾斜的情况不太有友好,雷思思用例3
*/
public static int findPeakElement(int[] nums) {
for (int i = 0; i < nums.length - 1; i++) {
if (nums.length == 1) {
return 0;
}
if (nums[i + 1] > nums[i]) {
continue;
} else {
return i;
}
}
return nums.length - 1;
}
用二分查找试了一下。(发现写的很费劲,好多边界情况需要考虑)
/**
* 1、二分查找
* 根据题意,我们可以基于连续三个序列的升序/降序来判断峰值的左右情况
* 如果升序,说明在右
* 如果降序,说明在左
*/
public static int findPeakElement(int[] nums) {
if (nums.length == 1) {
return 0;
}
// 从中间开始找
int left = 0, right = nums.length;
int index = (left + right) / 2;
while (left <= right) {
if (index == 0) {
return nums[0] > nums[1] ? 0 : 1;
}
if (index == nums.length - 1) {
return nums[nums.length - 2] > nums[nums.length - 1] ? nums.length - 2 : nums.length - 1;
}
if (nums[index - 1] < nums[index] && nums[index] < nums[index + 1]) {
left = index + 1;
index = (left + right) / 2;
} else if (nums[index - 1] > nums[index] && nums[index] > nums[index + 1]) {
right = index - 1;
index = (left + right) / 2;
} else {
int max = Math.max(Math.max(nums[index - 1], nums[index + 1]), nums[index]);
if (max == nums[index + 1]) return index + 1;
if (max == nums[index - 1]) return index - 1;
if (max == nums[index]) return index;
}
}
return index;
}
只出现一次的数字
链接:https://leetcode-cn.com/problems/single-number
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
思路:暴力破解,和之前做过的一个单身狗的题一样,那个题的是情人节找出广场上的单身狗
1、遍历数组元素,判断是否在set/treeSet里
2、如果存在,则删除set/treeSet元素
3、如果不存在,add到set/treeSet
4、set/treeSet最后只剩下一个元素
暴力法就不写了。
public static int singleNumber(int[] nums) {
int result = nums[0];
if (nums.length == 1) {
return nums[0];
}
for (int i = 1; i < nums.length; i++) {
result = result ^ nums[i];
}
return result;
}
课程表
链接:https://leetcode-cn.com/problems/course-schedule
你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]
给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?
示例 1:
输入: 2, [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
示例 2:
输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。
思路:正经思路应该是并查集,先mark一下
矩阵中的最长递增路径
链接:https://leetcode-cn.com/problems/longest-increasing-path-in-a-matrix
给定一个整数矩阵,找出最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。
示例 1:
输入: nums =
[
[9,9,4],
[6,6,8],
[2,1,1]
]
输出: 4
解释: 最长递增路径为 [1, 2, 6, 9]。
示例 2:
输入: nums =
[
[3,4,5],
[3,2,6],
[2,2,1]
]
输出: 4
解释: 最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。
思路:递归,遍历每个点,递归计算最深路径
1、全局变量记录maxDeep
2、遍历每个元素,计算每个元素的mostDeep
3、mostDeep = mostDeep(下一个节点) + 1
4、mostDeep的逻辑是
a. 当前点已经无路可走,返回1
b. 当前点已经在existMaxDeep中,返回缓存existMaxDeep已经计算的maxDeep
c. 标记当前点已经访问,返回 mostDeep(周围未访问点) + 1,增加到缓存existMaxDeep
5、计算完一个元素后,清空标记点的缓存
被围绕的区域
链接:https://leetcode-cn.com/problems/surrounded-regions
给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。
找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
思路:暴力破解,只要不爱这边的O,都变成X
1、遍历矩阵元素,如果是X,continue
2、如果是O,且不满足以下条件时,将元素变为X
遍历索引 i != 0 && j != 0 && i!=metrc.length - 1 && j != metrc.length - 1
3、完成遍历,输出矩阵
情侣牵手
链接:https://leetcode-cn.com/problems/couples-holding-hands
N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。
人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。
这些情侣的初始座位 row[i] 是由最初始坐在第 i 个座位上的人决定的。
示例 1:
输入: row = [0, 2, 1, 3]
输出: 1
解释: 我们只需要交换row[1]和row[2]的位置即可。
示例 2:
输入: row = [3, 2, 0, 1]
输出: 0
解释: 无需交换座位,所有的情侣都已经可以手牵手了。
说明:
len(row) 是偶数且数值在 [4, 60]范围内。
可以保证row 是序列 0…len(row)-1 的一个全排列。
思路:没有解决办法
寻找重复子树
链接:https://leetcode-cn.com/problems/find-duplicate-subtrees
给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。
两棵树重复是指它们具有相同的结构以及相同的结点值。
示例 1:
1
/ \
2 3
/ / \
4 2 4
/
4
下面是两个重复的子树:
2
/
4
和
4
因此,你需要以列表的形式返回上述重复子树的根结点。
思路:暴力破解,求出所有子树,去重算出结果
1、定义getSubTree,获取子树集合
2、根节点触发,totalTree = getSubTree(currentNode,left).addall(getSubTree(currentNode,right))
3、定义getSubTree:
a.如果left == right == null,则返回trees,trees.add(currentNode), trees.add(upNode+currentNode)
b.如果任意子节点不为空,则返回getSubTree(left/right).
4、最后计算totalTree中重复的字符串