- 博客(46)
- 收藏
- 关注
原创 labuladong刷题之 股票问题大集合
第一天不买 → 利润 0第一天买入 → 利润 = -prices[0])今天利润 = max(昨天利润, 昨天持股 + 今天卖出)但如果“昨天”是第 0 天之前(即第 -1 天),那你根本没进入市场。所以你不能直接“用昨天的值”,而必须手动告诉程序:“第 0 天开始前,我的利润是 0,我没有股票。所以第 0 天的 base case不是修改原有逻辑而是对「dp[-1][...] 的理论定义」进行实际展开计算的结果。层面形式作用理论让状态转移公式统一、简洁、通用实现特判i == 0,显式赋值。
2025-10-30 09:00:57
1018
原创 labuladong 刷题之环检测及拓扑排序算法
(1)visited 在整个图的维度上判断一个节点是否已经被遍历过了,visited[i]==true表示该节点已经访问过了,而如果一个节点已经被访问过了,由于遍历一个点时一定会遍历完其所有的相邻节点(也即一定会遍历完以该节点开头的所有递归路径),所以当visited[i]==true时,则没有必要重复遍历该节点了,故可以减少很多的重复遍历。(2)而 onPath数组代表的是当前递归遍历路径中已经被访问过的节点,如果 onPath[i]==true,则说明在当前路径中已经有该节点,说明成环。
2025-10-18 06:47:32
491
原创 labuladong刷题之二叉搜索树心法(构造篇)
那么,如果给一个进阶题目,不止让你计算有几个不同的 BST,而是要你构建出所有有效的 BST,如何实现这个算法呢?对于每棵树,构造它至少需要 O(n) 时间(因为需要创建 n 个节点)95 → 返回所有树 → 用双重循环拼接所有可能的左右子树。如何有效穷举 几个数字规定下的所有二叉搜索树的可能。:大约 O(n × 4ⁿ / n^{3/2})。所以总时间复杂度为:O(n2⋅n)=O(n3)因此空间复杂度:O(n2+n)≈O(n2)(因为 lo,hi 各有 n 个可能)。每棵树占 O(n) 空间(n 个节点)
2025-10-04 09:00:58
798
原创 labuladong 刷题之贪心算法
贪心算法的关键在于问题是否具备贪心选择性质,所以只能具体问题具体分析,没办法抽象出一套固定的算法模板或者思维模式,判断一道题是否是贪心算法。我的经验是,没必要刻意地识别一道题是否具备贪心选择性质。你只需时刻记住,算法的本质是穷举,遇到任何题目都要先想暴力穷举思路,穷举的过程中如果存在冗余计算,就用备忘录优化掉。如果提交结果还是超时,那就说明不需要穷举所有的解空间就能求出最优解,这种情况下肯定需要用到贪心算法。
2025-10-02 11:44:39
796
原创 labuladong刷题之动态规划:完全背包问题
完全背包:物品可以用无限次 → 用 dp[i][j-coins[i-1]](继续使用第 i 种物品)01 背包:每种物品只能用一次 → 用 dp[i-1][j-coins[i-1]]完全背包和 0-1背包最大的不同:允许用重复的硬币 所以状态转移方程。代表需要凑出的目标金额为 0,那么什么都不做就是唯一的一种凑法。直觉:你用过一次第 i 个物品后,还能继续用它去凑剩下的金额。,而剩下的金额仍然可以继续选它 →。leetcode 518 零钱兑换。→ 用一个它,再凑剩下的金额。,所以需要减少对应的金额。
2025-09-26 08:51:04
361
原创 labuladong刷题之动态规划:背包问题练习
先分析问题:能否转化为“从 n 个物品中选一些,使得背包容量/价值达到目标”定义 DP 数组:dp[i][w] → 前 i 个物品,容量 w 的状态写状态转移:选或不选第 i 个物品初始化 base case:容量为 0,或者没有物品遍历顺序:二维 DP 或一维优化(倒序)返回答案:通常是 dp[n][W] 或 dp[target]这个套路几乎可以套到所有 0-1 背包变体题:最大价值、能否凑出和、最小/最大代价。
2025-09-22 08:55:31
836
原创 labuladong 刷题之动态规划练习:子序列问题套路
leetcode 1143 最长公共子序列看到题目的第一思路:返回最长公共子序列 无需连续 保持顺序即可。
2025-09-13 08:41:10
586
原创 labuladong刷题之动态规划练习习题(比较难 多练习)
如果 dp[i-1] + nums[i] 更大,就延续前面的子数组,把 nums[i] 接上。注意:需要初始化dp[i][0]和dp[0][j],自己想一下概念,在纸上画一画。如果前面的 dp[i-1] 和 nums[i] 相加反而更小,就不要接上。画数组或矩阵,帮助理清 dp[i] / dp[i][j] 的依赖关系。:dp[i] = nums[0..i] 的最大子数组和(没法转移):dp[i] = 以 nums[i] 结尾的最大子数组和(能转移)dp[0]、dp[0][j]、dp[i][0] 都是关键。
2025-09-10 05:18:13
502
原创 labuladong刷题之动态规划的核心框架如何确定
leetcode 991 下降路径最小和看到题目的第一思路:题目要求 要求你从第一行的某个位置出发,每一步只能往下走一格,或者往左下/右下走一格,最后走到最后一行,路径上数字的和尽可能小代码随想录之后的想法和总结:1递归函数(i, j)。2为什么只看?因为题目规定:从(i, j)只能是从它正上方、左上方、右上方走下来的。3状态转移方程如何推导:只要知道到达这三个位置的最小路径和,加上的值,就能够计算出来到达位置(i, j)的最小路径和:到(i, j)的最小路径和 =。当i == 0。
2025-09-04 08:51:02
448
原创 总结之LIS(最长递增子序列)问题如何利用DP的核心思想解决
先明确状态dp[i] = 以 nums[i] 结尾的最长递增子序列长度这就是“子问题”,因为每个位置i都有自己的 LIS 长度问题。
2025-09-04 06:52:49
455
原创 labuladong 刷题之动态规划设计:最长递增子序列
1、明确dp数组的定义。这一步对于任何动态规划问题都很重要,如果不得当或者不够清晰,会阻碍之后的步骤。2、根据dp数组的定义,运用数学归纳法的思想,假设都已知,想办法求出dp[i],一旦这一步完成,整个题目基本就解决了。但如果无法完成这一步,很可能就是dp数组的定义不够恰当,需要重新定义dp数组的含义;或者可能是dp数组存储的信息还不够,不足以推出下一步的答案,需要把dp数组扩大成二维数组甚至三维数组本题还可以使用二分查找将时间复杂度降低为O(n log n)。我们定义一个dp数组,其中dp[k]
2025-09-03 08:23:24
773
原创 labuladong 刷题之动态规划解题框架
啥叫「自底向上」?就是反过来嘛。我们直接从最底下、最简单、问题规模最小、已知结果的f(0)和f(1)(base case)开始往上推出最后推出我们想要的f(5),这就是「自底向上」。其实「自底向上」和「自顶向下」本质是一样的,只是视角不同而已。实际上,带备忘录的递归解法中的那个「备忘录」memo数组,最终完成后就是这个解法中的dp数组,所以说自顶向下、自底向上两种解法本质其实是差不多的,大部分情况下,效率也基本相同。什么是状态转移方程?其实状态转移方程直接代表着暴力解,就是描述问题结构的数学形式。
2025-08-27 07:06:47
840
原创 labuladong刷题之二叉搜索树心法(基操篇)
leetcode 98 验证二叉树看到题目的第一思路:Q:因为二叉搜索树是左小右大,是不是可以直接比较每个点的左边节点比它小,右边节点比它大即可?❌A:不是的,因为这样不能保证左子树所有的节点都小于根节点,右子树所有的节点都大于根节点代码随想录之后的想法和总结:1如何改进- 对于每个以root为根节点的子树:满足 max> root.val> min2这份代码的核心思想是:所以这是一个用。
2025-08-21 11:29:41
815
原创 labuladong刷题之二叉搜索树心法(心法)
2 其他解法:想找到第k小的元素,首先找到每个节点的排名,而每个节点的排名,根据二叉树的规律规则就可以计算出来,通过bst左小右大的性质,对于每个节点node可以通过局部信息node.left.size推导出node的全局排名(当我知道node的 全局排名比如m,就可以用m和k的大小做比较,然后如果k<m,说明在当前节点的左子树,以此类推。空间复杂度:O(h)(递归栈深度,h 是树的高度,最坏 O(n),平衡树 O(log n))无论这个节点是整棵树的根,还是某个子树的内部节点,这个规律都成立。
2025-08-20 08:10:54
456
原创 labuladong刷题之层序遍历练习题part1
用 BFS 遍历每一层;用pre变量保存前一个节点;每访问一个cur节点时,若pre;每层的最后一个节点,其.next自然就是null(不需要显式设置);(你只需要设置每一层的前 n-1 个节点的.next = 下一个节点,最后那个节点自然就保持,因为它从一开始就是 null,不需要你多此一举地写。这样每一层的节点都会正确地用.next串联起来。时间复杂度以及空间复杂度:时间:O(n) 对每个节点做3次操作 每个操作都是O(1)一共n个节点每个节点最多被访问一次,也就是被q.poll()
2025-08-06 08:19:33
769
原创 labuladong刷题之二叉树分解练习题
和之前构建最大二叉树思路类似,往最大二叉树中加入一个数,其实只会加在右子树,因为只是原数组最右边加了个尾巴,不会影响左子树的结构,唯一要顾及的就是 如果加入的数值甚至比根节点(最大值)还大,那么只能把原来的二叉树作为左子树。这是判断是否新根节点的核心条件:(利用上一层父节点是否删除传进来的验证作为参数结合自己是否要被删除的双重验证判断当前节点是否是新根节点)题意思考:删除二叉树中的几个点,要求你返回剩下的所有独立二叉树的根节点,以及由他们组成的所有独立二叉树的完整结构。,找合适的位置插入。
2025-07-29 06:10:31
699
原创 labuladong刷题之二叉树的遍历练习题
leetcode 988 从叶节点开始的最小字符串看到题目的第一思路:和之前题目类似的思路:DFS遍历+回溯,只是回溯为什么在后序位置还是有点卡壳代码随想录之后的想法和总结:1 对于问题的答案:回溯要放在后序位置,是因为你需要等当前节点的所有可能路径都探索完,才能撤销“当前节点在路径中的存在”。🌿 每个节点都当作**当前“根”**来看;👣 “遍历左右子树”是指当前节点的两个子节点;🔙 回溯要等你走完它的左右两条路径(因为路径里要保留这个节点);
2025-07-22 08:44:59
563
原创 labuladong刷题之 二叉树拓展之最近公共祖先
BST 的“有序结构”让我们不需要像普通二叉树那样左右子树配对递归,而是可以通过比较节点值,像查字典一样一路剪枝,快速锁定最近公共祖先。每次递归/迭代只往一边走(左或右),不会同时递归两边(不像普通二叉树找 LCA 要左右递归)。:因为从最底层开始配对,一旦在某一层配成对,就直接返回,不会被更上层覆盖。如果只在某一边找到,就继续把这个结果往上传,等待另一边的“配对”结果。,在最早能配对成功的那个节点停下,这个节点就是最近公共祖先。,在最早能配对成功的那个节点停下,这个节点就是最近公共祖先。
2025-07-15 07:26:53
814
原创 labuladong刷题之二叉树心法(构造篇)
leetcode 654 通过数组构造最大二叉树看到题目的第一思路:最大二叉树的构造规则是:当前数组的。。。重复这个过程 —— 这就天然是!代码随想录之后的想法和总结:12 如何利用索引确定数组中的区间?将 nums[lo..hi] 构造成符合条件的树,返回根节点3是什么意思?我们要找最大值,一开始需要一个。是 Java 中int类型所能表示的最小值:4 递归的终止条件?basecase: 如果lo > hi,说明这个数组区间不成立,是一个空区间,没有任何节点 无法构造树O(n)
2025-07-04 06:18:31
613
原创 labuladong刷题之二叉树心法
A:“因为子节点之间的连接依赖于父节点之间已经建立的连接,所以要在递归处理子节点之前(即前序位置)先完成当前节点的连接。因为这样中间两个节点的指针无法连接,所以必须把这个二叉树想像成一个三叉树,这样才能把二叉树空隙之间的指针也连接。就是在找出当前链表的末尾节点,然后把原来的右子树接上去,完成树“拉平”的步骤之一。”的特性(如树、回溯、分治、记忆化搜索等),都可以/推荐写这种结构。处理树的问题时,我们经常“对当前节点做处理 + 递归处理左右子树”树的每个节点结构就是天然递归的(每个节点下面还有左右子树)
2025-07-01 06:46:43
502
原创 labuladong 刷题之 二叉树系列算法
如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。因为我们需要知道一个节点的左右子树的深度才可以知道它的直径长度,所以我们放在后序位置。A:因为前面说了,前序位置是进入一个节点的时候,后序位置是离开一个节点的时候,中序位置的代码在一个二叉树节点左子树都遍历完,即将开始遍历右子树的时候执行。空间复杂度:O(n) 因为每个节点最多遍历一次,,每个节点在整棵树的结构中。如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值。
2025-06-29 06:01:57
654
原创 labuladong刷题之 二叉树和递归思想总结
3 如果用「分解问题」的思维模式,那么一定要写清楚这个递归函数的定义是什么,然后利用这个定义来分解问题,利用子问题的答案推导原问题的答案;在斐波那契数列当中,一个节点要等待左节点计算完成,右节点计算完成,再把两个节点相加才得到节点的值,把这个思路抽象出来,和计算二叉树的节点值的思想完全一致,二叉树也是需要遍历左边,遍历右边,最后才得到结果。比如斐波那契数列问题,要求f(5) 就要得到f(4)和f(3)的答案,那么这种过程就是把一个规模较大的问题分解成规模较小的问题,然后通过子问题得到原问题的解。
2025-06-26 07:20:28
305
原创 labuladong刷题之 单调队列
239leetcode看到题目的第一思路:滑动窗口+单调队列代码随想录之后的想法和总结:1 运用单调队列这种双端队列的数据结构来解决- 按照队列先进先出的时间顺序- 可以实时更新保存数组的动态最值的问题2「pop 1(不在队列,无操作)」是因为在之前所以等到滑动窗口向右移动、要移除旧元素 1 时,那么这个时候就时间复杂度以及空间复杂度:时间:整个算法做的事情就是把nums中的每个元素加入和移出window,不可能把同一个元素多次移入移出window。
2025-06-24 08:11:02
797
原创 labuladong刷题之单调栈模版的几种变种
倒序遍历每个元素i栈中维护从右往左的候选折扣价格每次从栈顶尝试找 ≤ 当前值 的折扣找到就减去,没找到就不变当前值入栈 ➜ 等待成为别人折扣时间复杂度O(n)每个元素最多入栈/出栈一次空间复杂度O(n)最坏情况下栈里可能会放所有元素Q4题目链接leetcode 901 股价跨度看到题目的第一思路:继续沿用单调栈套路,但是更改来保存跨度因为当你遇到一个价格price时,你想往回找那些小于等于它的价格,计算它们贡献的连续天数。而栈维护的是历史中没有被“吃掉”的”价格波峰“
2025-06-19 08:35:56
1056
原创 labuladong刷题之 单调栈算法模板
单调栈是用栈结构维护一个单调递增/递减序列,在 O(n) 时间内找出下一个/上一个更大(或更小)元素,非常适合处理“数组中对比相邻关系”的问题。
2025-06-18 05:33:51
637
原创 Labuladong刷题之队列的经典习题
请求是按时间递增调用的(题目保证 ping 的参数 t 是递增的),所以队列中的请求时间戳是按照时间顺序排好的。队头就是最早的请求,如果它都没超过 3000 毫秒,那么后面的都没问题;如果队头的时间戳已经小于t - 3000,说明它太久远了,不能再算在 3000 毫秒之内,就应该删除。
2025-06-17 05:43:51
695
原创 labuladong刷题之队列和栈
看到题目的第一思路:如何用栈实现队列的功能-队列是先进先出,栈是先进后出代码随想录之后的想法和总结:把两个栈反着拼接在一起即可实现:把s1中的所有元素依次弹出并压入s2。于是最早入队的元素(原本在s1栈底)被移动到了s2的栈顶,方便我们通过pop或peek正确地获取“队头”元素。这种做法就是两个栈模拟队列的。peek操作,调用它时可能触发while循环,这样的话时间复杂度是 O(N),但是大部分情况下while循环不会被触发,时间复杂度是 O(1)。由于pop操作调用了peek。
2025-06-06 06:21:41
1025
原创 labuladong刷题之前缀和与差分数组技巧(空间换时间)
看到题目的第一思路:代码随想录之后的想法和总结:如果你想查询:可以用公式:唯一要注意的是 要在前缀和数组前面加一个索引,也就是比数组多一个索引时间复杂度以及空间复杂度:🧮 初始化阶段:时间复杂度:O(n) 是输入数组 的长度。因为你只循环了一遍数组,计算前缀和,所以是 线性时间。空间复杂度:O(n) 数组的长度是 ,所以占用了额外的线性空间。🔍 查询阶段:时间复杂度:O(1)你只做了一次减法:所以无论查询区间多大,时间始终是常数级。空间复杂度:O(1)不需要分配额外空间,仅使用已有数组“这种方式用空间
2025-06-02 08:48:47
993
原创 labuladong刷题之-二分搜索算法
看到题目的第一思路:二分法的变种:寻找左右边界的二分法代码随想录之后的想法和总结:Q:找左边界的二分法和普通二分法有什么区别?A:找到 target 时不要立即返回,而是缩小「搜索区间」的上界right,在区间中继续搜索,即不断向左收缩,达到锁定左侧边界的目的。时间复杂度以及空间复杂度:left_bound,right_bound,都是一个二分查找函数,他们各自的时间复杂度是O(logn), 所以时间复杂度是两个O(logn) 依然为O(logn)
2025-05-25 07:31:58
601
原创 labuladong刷题-二维数组技巧(滑动窗口)
leetcode 48 顺指针/逆时针旋转矩阵看到题目的第一思路:这里有技巧需要记住,以后见到顺指针和逆时针旋转矩阵都可以牢记使用这个技巧代码随想录之后的想法和总结:首先以对角线为线镜像对折,然后对每一行进行反转,就可以得到旋转90度后的矩阵。
2025-05-19 07:25:44
904
原创 labuladong刷题day3-数组使用双指针技巧
注意:这道题和前面两题思路不一样之处:首先更新数值,更新数组,然后再移动慢指针到下一位,因为为了保存有效元素,及时更新数组,防止移动指针造成错误指向。和上题类似操作,只不过一个在数组里面去重,一个在链表里面,所以用同样快慢指针的方法,只不过把数组赋值操作变成操作指针而已。中心扩展法:对于每个可能的回文中心,包括奇数和偶数回文中心,用双指针左l,右r从中心向两边扩展,直到字符串不匹配越界,所以在首先利用前一题的方法,去除数组里面所有0,返回一个不含0的数组,然后把剩下的数组长度里面的元素 全部赋值为0。
2025-05-05 07:28:41
693
原创 labuladong刷题day1
看到题目的第一思路:两个链表比较大小然后合并 但是具体怎么比较和移动指针 没有明确思路代码随想录之后的想法和总结:1 何时使用虚拟头结点经常有读者问我,什么时候需要用虚拟头结点?。比如说,让你把两条有序链表合并成一条新的有序链表,是不是要创造一条新链表?再比你想把一条链表分解成两条链表,是不是也在创造新链表?这些情况都可以使用虚拟头结点简化边界情况的处理。
2025-04-03 12:24:30
738
原创 算法的时间复杂度与空间复杂度进一步理解
为了把复杂度控制在 O(NlogN)O(NlogN) 或者 O(N)O(N),我们的选择范围就缩小了,可能符合条件的做法是:对数组进行排序处理、前缀和、双指针、一维 dp 等等,从这些思路切入就比较靠谱。这个量级,那么我们肯定可以知道这道题的时间复杂度大概要小于 O(N2)O(N2),得优化成 O(NlogN)O(NlogN) 或者 O(N)O(N) 才行。所以总的时间复杂度依然保持在 O(N)O(N),均摊到每一次操作上,其平均时间复杂度依然是 O(1)O(1)。,这样就不会产生额外的复制开销。
2025-03-25 00:47:37
878
原创 算法训练营day11-二叉树day1
二叉树的最基础遍历方法(递归遍历的前序,中序和后序)看到题目的第一思路:可以套模版但是要学会看到底如何安排三个节点的遍历顺序,还有根节点的位置很重要代码随想录之后的想法和总结:递归遍历 三步骤1 确定递归函数的参数和返回值2 确定终止条件3 确定单层递归的逻辑,只要大家记住 前中后序指的就是中间节点的位置就可以了。前序:左中右中序:左中右后序:左右中遇到的困难:三种遍历方法,都有一个特点,无论是先序根 ➜ 左 ➜ 右,中序左 ➜ 根 ➜ 右,后序左 ➜ 右 ➜ 根,所谓的。
2025-01-14 09:43:16
861
原创 算法训练营day11 打卡-栈和队列part2
leetcode 150 medium 逆波兰表达式求值看到题目的第一思路:不太理解什么是逆波兰表达式,其实就是把符号放在数字后面的表达式代码随想录之后的想法和总结:1逆波兰表达式 可以看作是 二叉树的后序遍历,计算机也是同样的思考方式(当看到数字和运算符号时)2可以利用之前做过的 消除相邻字符串的题目思路- 用栈的思路来解决此题。
2024-08-15 07:36:04
1058
1
原创 算法训练营day 10- 栈与队列part1
leetcode232 用栈实现队列的操作看到题目的第一思路:暂无代码随想录之后的想法和总结:- 初始化两个栈in和out,出队直接就往in压栈,出队先检查out里面有没有元素,有的话out的栈顶就是队首,否则就先把in里面的元素弹出压入到out中。这样就能保证out的栈顶始终都是队首,in的栈顶始终都是队尾。- 理解这个解法的关键在于,这样就维持了输出栈顶是队列开头的定义。stack_intostack_outstack_instack_out遇到的困难:1 peek函数-peek。
2024-07-30 08:56:13
811
原创 算法训练营打day8-字符串part1
这道题目和反转链表的思路接近,唯一区别是字符串(数组)当中内存连续分布,所以不用考虑指针指向这些细节问题,只要从头尾两个位置开始交换指针,然后下一个头尾继续交换,直到没有交换的空间就可以完成反转了。2 在这道题目里,先为凑足2k的情况进行反转操作,然后用continue来进入下一次循环(如果凑足的情况),然后再写上不足k的情况,如何处理。题目规则写的其实不是很清楚:可以概括为-每隔2k个反转前k个,尾数不够k个就全部反转,尾数够k个但不够2k也是反转前k个。O(1),因为在原地交换,没有创造额外空间。
2024-06-21 08:12:00
866
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人
RSS订阅