theme: juejin
动态规划
309.最佳买卖股票时机含冷冻期(中等)
给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:动态规划,当与一般的动态规划题不同的是这题带有状态。
状态转移方程:
hold[i] : 第 i 天,手中持有股票,这时的最大收益。 有两种可能: 昨天就持有股票,今天休息。 前天卖了股票,今天买了股票。
hold[i] = Math.max(hold[i - 1], unhold[i - 2] - prices[i])
unhold[i] : 第 i 天,手中没有股票,此时的最大收益。 有两种可能:今天休息、或卖了股票 昨天也没有持有,今天休息。 昨天持有股票,今天卖了股票。
unhold[i] = Math.max(unhold[i -1], hold[i - 1] + prices[i])
目标是求 unhold[n-1] ( n:0 1 2 3 ... )
357. 统计各位数字都不同的数字个数
给你一个整数 n ,统计并返回各位数字都不同的数字 x 的个数,其中 0 <= x < 10n 。
输入:n = 2
输出:91
解释:答案应为除去 11、22、33、44、55、66、77、88、99 外,在 0 ≤ x < 100 范围内的所有数字。
状态转移方程:dp[i] = (dp[i - 1] - dp[i - 2]) * (10 - i + 1) + dp[i - 1],当n大于等于3时,数字首位可以取除了0之外的其他9个数,第二位可以取除了第一位数字的其他9个数,第三位可以取8个,依次递减。
双指针
16.最接近的三数之和
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在恰好一个解。
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
思路:指定一个数,对另外两个数用双指针判断最接近的距离。
栈
946.验证栈序列(中等)
给定 pushed 和 popped 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false 。
输入: pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出: true
思路:利用一个栈存放遍历pushed的数组元素,当popped[index]的元素和栈顶元素相等时,将栈顶元素弹出。
队列
406. 根据身高重建队列
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
示例:
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
思路:先排序,后插入,若身高不等时,按由大到小排序,如果身高一样则将前面高于自己人数小的人放在前面.因为people已按照身高排序,所以某个人被插入到ans里时,所有比他高的都已经在ans里了,而身高比他矮的人怎样插入到ans里都不影响前面高于他的人数.
数组
36. 有效的数独
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
思路:一次遍历,通过三个map记录遍历的字符。
39. 组合总和
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。 示例:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
40. 组合总和 II
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。 示例 :
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出: [[1,1,6],[1,2,5],[1,7],[2,6]]
思路:两道题目的思路都是回溯,但细节处有所不同,第一题是元素可以重复利用,组合不能重复;第二题是元素不能重复利用,组合不能重复。回溯的要点:怎么选择元素。
套路做法(可硬记)
用 for 循环去枚举出所有的选择
做出一个选择
基于这个选择,继续往下选择(递归)
上面的递归结束了,撤销这个选择,进入 for 循环的下一次迭代
46. 全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
47. 全排列 II
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
输入:nums = [1,1,2]
输出:[[1,1,2],[1,2,1],[2,1,1]]
思路:因为第一题数组中是不重复的数字,所有只要保证回溯时,不要遍历到已经在数组的元素即可。但第二题数组中包含了重复的数字,这里需要注意。需要用一个used[i]数组记录该索引处的元素是否已经使用。另外需要用if(i - 1 >= 0 && nums[i - 1] == nums[i] && !used[i - 1])判断该元素是否在这一层中已经被使用过了。
(该图出自leetcode笨猪爆破组,侵删)
57. 插入区间
给你一个 无重叠的 ,按照区间起始端点排序的区间列表。 在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。
示例:
输入:intervals = [[1,3],[6,9]], newInterval = [2,5]
输出:[[1,5],[6,9]]
思路:一开始自己想的太复杂了,这题主要分别三个阶段:1.新区间左侧的区间 2.与新区间重叠的区间 3.新区间右侧的区间。
2. while(index < len && intervals[index][0] <= newInterval[1]) {
newInterval[0] = Math.min(intervals[index][0], newInterval[0])
newInterval[1] = Math.min(intervals[index][1], newInterval[1])
}
954. 二倍数对数组
给定一个长度为偶数的整数数组 arr,只有对 arr 进行重组后可以满足 “对于每个 0 <= i < len(arr) / 2,都有 arr[2 * i + 1] = 2 * arr[2 * i]” 时,返回 true;否则,返回 false。
示例:
输入:arr = [3,1,3,6]
输出:false
思路:用哈希表存储每一个值出现的次数,再用map.keys()方法取得map中无重复的key值用一个数组存储,在根据绝对值从小到大的顺序排序,这样做的好处是对于x,只需要考虑 2 * x 的情况。
字符串
420. 强密码检验器
如果一个密码满足下述所有条件,则认为这个密码是强密码: 由至少 6 个,至多 20 个字符组成。 至少包含 一个小写 字母,一个大写 字母,和 一个数字 。 同一字符 不能 连续出现三次 (比如 "...aaa..." 是不允许的, 但是 "...aa...a..." 如果满足其他条件也可以算是强密码)。 给你一个字符串 password ,返回 将 password 修改到满足强密码条件需要的最少修改步数。如果 password 已经是强密码,则返回 0 。
在一步修改操作中,你可以:
插入一个字符到 password ,
从 password 中删除一个字符,或
用另一个字符来替换 password 中的某个字符。
思路:用一个数值num记录小写字母、大写字母、数字的满足个数
当字符串长度小于6时,Math.max(6 - len, 3 - num),当字符串长度在6-20之间时,要考虑连续字母的情况,连续字母的情况是Math.floor(count / 3),当长度大于20时,有点麻烦的,遇到这种情况听天由命了~
796. 旋转字符串
给定两个字符串, s 和 goal。如果在若干次旋转操作之后,s 能变成 goal ,那么返回 true 。
s 的 旋转操作 就是将 s 最左边的字符移动到最右边。
例如, 若 s = 'abcde',在旋转一次之后结果就是'bcdea' 。
输入: s = "abcde", goal = "cdeab"
输出: true
思路:利用两次循环,外层是偏移量0 —— length-1,内层是字符串的index,s[(index + i) % length] 和 goal[index]比较,若全部相同则返回true。
二叉树
99. 恢复二叉搜索树
给你二叉搜索树的根节点 root ,该树中的 恰好 两个节点的值被错误地交换。请在不改变其结构的情况下,恢复这棵树 。
思路:考虑两种节点被错误交换的情况,一种两个节点分别在左右子树上,另一种是两个节点在同一颗子树上。
DFS
463. 岛屿的周长
给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。
网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
输入: grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
输出: 16
解释: 它的周长是上面图片中的 16 个黄色的边
思路:深度遍历,当遍历到i < 0 || i >= row || j < 0 || j >= col || grid[i][j] == 0说明这个方向上存在边,返回1,当遍历到grid[i][j] == 2时,意味这个点已经访问过了,返回0。
1254. 统计封闭岛屿的数目
二维矩阵 grid 由 0 (土地)和 1 (水)组成。岛是由最大的4个方向连通的 0 组成的群,封闭岛是一个 完全 由1包围(左、上、右、下)的岛。
请返回 封闭岛屿 的数目。
示例 1:
输入:grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]]
输出:2
解释:
灰色区域的岛屿是封闭岛屿,因为这座岛屿完全被水域包围(即被 1 区域包围)。
思路:深度遍历,遍历到岛屿时将其转化为水域。
链表
86. 分隔链表
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
输入: head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
思路:<目标值放链表a,>=放链表b,拼接链表a和b
头部节点不确定时,用虚拟链头指向头部。拼接和返回虚拟链头.next即可
61. 旋转链表
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
输入: head = [1,2,3,4,5], k = 2
输出: [4,5,1,2,3]
思路:要留意当k值大于链表长度的情况,后面通过遍历每个节点,一边遍历计算链表长度一边找到尾节点,并将尾节点指向头节点,即可完成尾首的连接。
143. 重排链表
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
输入: head = [1,2,3,4]
输出: [1,4,2,3]
输入: head = [1,2,3,4,5]
输出: [1,5,2,4,3]
思路1.利用Map存储节点,然后再进行插入。
思路2.双指针,找到中间节点,再对后半部分翻转链表,然后再依次插入前半段链表中。
图
221.最大正方形(中等)
在一个由 '0' 和 '1' 组成的二维矩阵内,找到只包含 '1' 的最大正方形,并返回其面积。
思路:使用动态规划,dp[i][j] 表示以 matrix[i][j] 为右下角顶点的最大正方形的边长。
状态转移方程:
dp[i][j] = Math.min(d[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
207.课程表(中等)
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。 请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
思路:拓扑排序,记录每个节点的入度,再用map = {}邻接表存储先后关系,随后用queue队列存放入度为0的节点,并根据队列中入度为0的节点访问其邻接节点,每次访问邻接节点的入度-1,直到访问节点的入度为0,将其存入队列。
310. 最小高度树
树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
给你一棵包含 n 个节点的树,标记为 0 到 n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条无向边。
可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。
请你找到所有的 最小高度树 并按 任意顺序 返回它们的根节点标签列表。
示例 :
输入: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]
输出: [3,4]
思路:拓扑排序 + BFS,从叶节点出发
逆向思维,由结果推初始状态
780. 到达终点
给定四个整数 sx , sy ,tx 和 ty,如果通过一系列的转换可以从起点 (sx, sy) 到达终点 (tx, ty),则返回 true,否则返回 false。例如从点 (x, y) 可以转换到 (x, x+y) 或者 (x+y, y)。
示例 :
输入: sx = 1, sy = 1, tx = 3, ty = 5
输出: true
解释:
可以通过以下一系列转换从起点转换到终点:
(1, 1) -> (1, 2)
(1, 2) -> (3, 2)
(3, 2) -> (3, 5)
思路:反向思维,当tx和ty都比sx和sy大时,通过tx = tx % ty,即可让tx达到前一个状态。但是当到达(1,1)->(1,2)状态时,循环会停止,此时可以看到 ty 其实是通过sy + sx得到的,需要通过 ty > sy && (ty - sy) % sx == 0。
来源:力扣(LeetCode) 链接:leetcode-cn.com 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。