- 博客(36)
- 收藏
- 关注
原创 代码随想录day40 动态规划(5)
为了避免dp元素始终为0,令dp[0]=1,其余=0。* amount > 0时,空集不算一种组合,所以不能将dp所有元素初始化为1。当coins[i]不大于当前上限j,进入第二层循环,想象coins[0]==j的情况,dp[j] = 0+1 = 1,这个组合数是合理的。由于第0个物品和后面物品的转移方程没有区别,可以不额外初始化dp数组,直接用元素全0的dp从第0个物品开始遍历。求排列数,需要先遍历target再遍历物品。先遍历背包上限再遍历物品=>排列数。先遍历物品再遍历背包上限=>组合数。
2024-07-05 11:06:27
302
原创 代码随想录day38 动态规划(4)
若它不大于j,可以选择不让nums[i]加入,对应dp[j]种组合数,也可以选择让nums[i]加入,对应dp[j-nums[i]]种组合数,注意遍历strs时,对每个字符串i和j的范围不可以是range(m, 0, -1),因为这是二维的背包,仅凭i==0或j==0不能判定只有空串满足条件,也可以是只有1或只有0的字符串,所以dp[0][x]或dp[x][0]不一定为0;只有dp[0][0]一定为0.相当于将nums分为两部分,第一部分的数取正,第二部分数取负,使得加上正负号后的和为target。
2024-07-04 17:42:20
902
原创 代码随想录day37 动态规划(3)
考虑dp[i][j]的计算:若当前数nums[i] > j,说明i不可加入,于是dp[i][j] = dp[i-1][j]。否则,i可能加入,获得总和 = dp[i-1][j-nums[i]] + nums[i](将加入了i数的背包想象成两部分,一部分是i数本身,价值和重量都为i,另一部分。2)dp[0][x]表示只考虑nums第0个数,则当x小于nums[0],初始为0,当x不小于nums[0],都初始化为nums[0]。i也可以选择不加入,获得总和 = dp[i-1][j]。
2024-07-04 15:34:24
463
原创 代码随想录day36 动态规划(2)
dp是一个m行n列的二维数组,其中dp[i][j]表示从起点[0, 0]到[i, j]有多少条路可达。状态转移方程为dp[i][j] = dp[i-1][j] + dp[i][j-1],为了保证计算dp[i][j]时它的左和上格子已经计算好,将起点初始化为0,将左上两条边的边缘格子初始化为1,随后逐行遍历dp数组直到右下角。2)当m、n大于1,dp[0][0]的值没有影响;可以将二维dp压缩为一维节省空间:注意到计算dp每行时仅与上一行相关,可以只维护长度为n的一维数组:时间O(n*m),空间O(n)
2024-07-04 10:02:45
303
原创 代码随想录day35 动态规划(1)
base情况是F(0) = 0和F(1) = 1。由于F(n)只与n之前的两个数的F()值有关,所有只需维护p1和p2两个数,不用维护整个dp数组,使得空间复杂度为O(1)。从2开始遍历,遍历到i时,res=F(i),因此for循环执行到n,此时res是F(n),返回res。dp[i]可以由它往下一级/两级台阶转移而来(最后一步上一级/两级),dp[i]是前两个方法数之和。cost[i]:台阶i再往上1或2需要花的体力。由于初始可以从0或1开始,dp[0] = dp[1] = 0。
2024-07-03 16:57:15
252
原创 代码随想录day34 贪心(5)
关键在于从后往前遍历,一旦遇到反单调递增的数就用flag记下其位置,同时前一个数减去1。退出循环后,从flag开始(包括flag)都改为9,flag以前的数按digit数组。写法二:修改res末尾区间的右边界直到它不与当前区间重合,将这个不重合的新区间推入res。写法一:维护right和left两个数作为当前区间的左右边界。
2024-07-03 16:22:01
253
原创 代码随想录day33 贪心(4)
解法一:与上面两题思路类似,首先耗费O(1)的空间统计字符串中各字母所在区间:[首次出现位置,最后出现位置],接着按左边界从小到大排序,用left和right维护当前区间的左右边界,若当前字母区间在上一个区间之外,即right<该字母开始位置,就将上一个区间长度推入res,更新left和right为当前字母的左右边界;解法二:用字典保存字母最后出现位置,遍历s,用当前字母最后位置更新区间右界right,若当前下标等于区间右界,说明得到了一个区间,将长度推入res。否则,箭的数量增加1。
2024-07-03 14:30:32
543
原创 代码随想录day31 贪心(3)
先按身高从大到小排序:向sort函数传入key参数 lambda x : (-x[0], x[1]),表示优先按people中的元素x的x[0],即身高从大到小排序,若身高一样则按k值从小到大排序。接着从左往右遍历排好序的people,将当前元素插入队列(用list)下标为p[1]位置。由于当前元素前面的元素都不小于它,这种插入方式能保证满足当前元素的k而且不破坏前面已遍历过元素的k。从右到左遍历一次:处理左边比右边高的情况。解法一,暴力搜索所有可能的起点,超时,通过测试样例34/40。
2024-07-03 10:39:27
261
原创 代码随想录day30
当len==1,不需要跳动,已在末尾。位置i初始在0,当i不超过cover的限制,遍历数组,加上i<=cover的限制,根据当前元素更新cover,并且判断新的cover是否能够抵达数组末尾。之间的所有值(python for循环的特点),遍历结束后新的cover是以这一区间的元素为起点能跳到的最远位置,步数cnt加1,进入下一个循环while。用动态规划, dp[i][1]表示到第i天为止,在持有股票状态下的最大利润;dp[i][0]表示到第i天为止,在不持股票状态下的最大利润。
2024-07-01 23:22:13
314
原创 代码随想录day29 贪心算法(1)
跳过开头的平台后,不需要再考虑后面的平台,通过delmono函数跳过后续的不严格单调区间(可以含有平台,并不影响结果)即可。否则,delmono的返回值是首个打破数组单调趋势的元素下标,相当于cur*pre<0的情况,cnt增1。i的遍历在选择不同的子序列右端点,而cur = max(nums[i], cur + nums[i])在选择子序列的左端点,表示在。解法二:动态规划,dp[i][0]、dp[i][1]分别表示从第0~第i个数,以i为山谷、山峰的最大摆动序列长度。首先对孩子的胃口和饼干大小排序。
2024-07-01 17:22:17
782
原创 代码随想录day28 回溯(4)
一开始看错题了,先将数组排序,但其实题目要求取原数组的递增子序列,所以此题需要处理一个可能是乱序的数组。(1)为保证子序列递增,加入当前元素前确保它不比path最后一个元素小;(2)每得到一个新path,只要它长度超过1,加入res;(3)由于数组乱序,不能通过当前元素的上一个元素去重,所以在每一层递归里新建一个集合,保存该层已访问过的数值,只有当元素未访问才能加入path。path的每个位置可以取除了前面已使用过的数字之外的全部数,由于数组元素不重复,可以用visit数组存放已访问过的数;
2024-06-19 11:24:46
191
原创 代码随想录day27 回溯(3)
数组不含重复元素,path每变动一次,就将其推入res以记录所有子集。start的含义是,当前子集开头元素为nums[start]或path当前位置为nums[start]。对于有重复元素的数组,在上题的基础上要考虑去重。只需要对path同一个位置的元素去重即可。由于for i in range(start, len(nums))就是在遍历path。易错2:空集也是一个子集,所以self.res应该初始化为[[]] ,容易漏掉此子集。元素的可能取值,将去重放在此处。,修改一个元素会影响所有行。
2024-06-18 11:41:09
356
原创 代码随想录day25 回溯(2)
对当前c,将c个cur放入path,递归调用backtrace并传入cur的下一个元素和target-cur*c,每次调用backtrace都回溯path(target的回溯隐含在参数传递中)。在搜索答案的时候,首先确定path的第一个元素(对应树的第一层),再次调用traceback,进入树的下一层,也就是path下一个位置的元素...(以上操作沿着树垂直向下)直到达到递归中止条件,path回退末尾元素(垂直向上),平行向右移动搜索path当前位置的其他可能值,重复。
2024-06-17 21:42:55
434
原创 代码随想录day24 回溯(1)
self.path和n是对应的,n为target减去path中所有元素;而start是path和n的下一层,表示紧接着path末尾元素的下一个元素。
2024-06-17 14:23:46
268
原创 代码随想录day23
其实不需要root.left = self.convertBST(root.left),因为节点之间的关系没有改变,只是修改了节点值,可以直接调用self.convertBST()去修改当前节点左右子树各节点值,最后返回root。由于是二叉搜索树,若当前节点超出范围,则它和它的左/右子树都超出范围,应该返回另一棵子树。解法二:维护一个全局变量self.next,表示当前节点在数值上最近邻的较大节点,直接在中序遍历二叉树的过程中修改节点值,不需要额外空间。由于数组已排序,构造出来的一定是平衡二叉搜索树。
2024-06-14 12:14:01
273
原创 代码随想录day22
全局变量self.pre保存左子树当前访问节点的父节点,self.isfirst指示是否首次调用self.func(),self.left和self.right分别是待删除节点原来的左右孩子。若目标==当前值,找到待删除节点,设置全局变量,将待删除节点的左子树传入self.func()。若首次调用self.func()就访问到没有右孩子的节点node,就将以node为根的子树整体向上移动,node替换掉删除的节点,self.right成为node的右孩子,返回node。当节点值大于p、q,则往左子树搜索。
2024-06-14 10:49:02
536
原创 代码随想录day21
若p、q都在其中一棵子树上,返回该子树。2)由于二叉搜索树的中序遍历是有序的,通过维护maxcount(int)、pre(Node)、res(list)三个全局变量可以仅遍历一次树得到结果(但是不好理解,比较绕、容易出错。递归的退出条件设为self.res长度为2(找到了两个路径)这样的退出条件可能导致回溯时出现问题,因此在pop()之前再加上self.path不为空的判断。获得两个路径list后,遍历时,容易漏掉公共祖先是较短列表的最后一个元素的情况,需要在最后加上return path1[n-1]。
2024-06-13 20:51:25
224
原创 代码随想录day20
2)迭代:由于是二叉搜索树,左子树的所有节点的值都小于根节点,右子树则都大于根节点,所以不需要队列或栈也能实现迭代。还可以用中序遍历将所有节点值保存在数组中,遍历数组检查是否严格递增。易错:不可以只判断当前节点是否比左孩子大、比右孩子小,因为二叉搜索树要求。1)将二叉搜索树通过中序遍历得到数组,遍历一次数组得到最小差距。2)迭代,用deque。
2024-06-13 16:55:58
215
原创 代码随想录day18 寻找树左下角 路经总和 构造二叉树
1)递归+回溯+栈:这里右侧叶子先入栈,出栈时就是从左叶子开始。若后出栈的(靠右的)叶子具有更大的深度,就用它更新当前结果。也可以不用栈而使用两个全局变量,保存当前结果,保存当前结果对应的深度。注意此时需要先进入左树。2)层序遍历:用一个嵌套列表保存每层的节点,遍历结束后取最后一层的第一个(最左)节点即可。
2024-06-07 21:46:00
365
原创 代码随想录day17 平衡二叉树 所有路径 左叶子之和
isBalanced()的逻辑是:判断平衡树应该要比较当前节点的左右子树最大深度,若相差不超过1,再接着判断左孩子的两个子树、右孩子的两个子树,退出递归的条件是节点为空。每次弹出栈中的节点和从root到该节点的路径(字符串),若为叶子节点,对应路径推入结果列表,若不是叶子,再将非空孩子节点与对应的路径推入两个栈。易错1:path.append(str(node.val)),否则paths.append("->".join(path))时执行出错;易错3:回溯,即path.pop()的位置、次数。
2024-06-07 14:02:38
371
原创 代码随想录day16 二叉树的最大/最小深度 完全二叉树的节点数
错误原因:若当前节点的左右子树一个为空,另一个不为空,会将0+1作为最小深度赋给当前节点。因此需要加如下的判断:若其中一棵子树为空(深度为0)则将另一棵的深度+1赋给当前节点;只有当两棵子树都非空,才能将两子树深度较小者+1赋给当前节点。求最小深度,需要在遇到叶子节点时及时退出,因此每次从队列头部取出节点就判断一下是否为叶子。关键是:若当前节点为空,返回0;否则返回其左子树深度、右子树深度中更大者,再。容易仿照maxDepth写成下面这样,但是。求最大深度,需要遍历至最后一层。2)深度优先,递归法。
2024-06-06 16:59:09
322
原创 代码随想录day15 层序遍历 翻转二叉树 对称二叉树
代码同第一题,只是最后一行return res[::-1]代码同第一题,只是最后一行return [r[-1] for r in res]代码同第一题,只是最后一行return [sum(r) / len(r) for r in res]代码同第一题,只是最后一行return [max(r) for r in res]注意返回的是root,即修改了各节点next之后的新树的根节点。
2024-06-06 15:11:10
400
原创 代码随想录day14 二叉树遍历
1.1 二叉树前序遍历:144. 二叉树的前序遍历 - 力扣(LeetCode)python3C++1.2 中序遍历:94. 二叉树的中序遍历 - 力扣(LeetCode)python3C++ 1.3 后序遍历:145. 二叉树的后序遍历 - 力扣(LeetCode)python3C++2. 迭代法2.1 前序遍历:python3C++ 2.2 中序遍历:94. 二叉树的中序遍历 - 力扣(LeetCode)若当前节点cur非空,cur入栈,访问cur左
2024-06-04 11:33:22
449
原创 代码随想录day13 构造单调队列求滑动窗口最值 & 大小堆求top k值
构造单调队列sortque,使得对当前窗口,sortque中的元素单调递减,首项为窗口最大值;当窗口向右滑动,检查sortque左侧元素是否需要弹出,推入右侧元素时保证新元素左侧的所有元素大于它。空间:维护sortque所用的O(k);解法一:map+小顶堆,当大小超过k,移除堆顶,最后剩下的就是前k大的元素。python 中的heapq是小顶堆;若需要大顶堆,可以(-1)*元素再推入堆,返回结果时再*(-1)。解法二:map+列表排序,时间O(nlogn),空间O(n)时间O(nlogk),空间O(n)
2024-06-03 16:54:38
229
原创 代码随想录day11 栈和队列(2)
栈que仅保存左括号,一旦遇到匹配的右括号,最后入栈的左括号弹出。栈中有剩余的左括号但已没有右括号。时间:每次调用getNext都是O(n),每次调用结果是将当前字符串相邻重复字母消除,如"abba"变为"aa",而"aa"的消除要等到下一次调用。python3中的"//"是向下整除,对于异号的除法,结果为负数,向下取整就不是题目要求的向零取整。需要先做小数除法"/",再转为int,此时取结果的整数部分,相当于向零取整。解法一:用栈,时间O(n),空间O(1)(事实上空间为res的大小。
2024-06-01 12:42:35
416
原创 代码随想录day10 栈实现队列 队列实现栈
弹出队头元素pop(返回并删除)时,由于栈是后进先出的,需要将stack_in的元素挪到stack_out,使得元素倒序,这样从stack_out末尾取出的元素就是队列头部的元素。而peek只返回队头元素但不删除:需要调用一次self.pop(),再将弹出元素放回队列头部,即stack_out栈的尾部,即stack_out.append(res)。pop:要求弹出最后一个元素,将最后一个元素之前的所有元素依次删除并重新入队,此时队列的首个元素即原来的最后一个元素。Python:用一个队列模拟栈。
2024-05-31 22:45:36
420
原创 代码随想录day9 字符串(2)KMP
next[0] = -1, next[i]表示子字符串template从第0个字符到第i个字符[0, i]的(最大重复前缀长度-1)。以字符串s = "abcabcabc"为例,我的错误思路是把next[-1]+1当作"abc"的长度,然后在s的前后掐掉"abc"、取中间部分,判断中间部分能否与"abc"匹配。但事实上next[-1]+1是"abcabc"的长度,即前后缀都是"abcabc",这是因为next不阻止前后缀的重叠,它只限制首字符(末字符)不出现在前缀(后缀)里。
2024-05-31 20:35:42
252
原创 代码随想录day8 字符串(1)
使用快慢指针,fast遍历原字符串,slow指向新字符串的后一个位置,即新字符串是[0, slow),其长度==slow。2.2)可以用自己定义的reverse函数,也可以#include <algorithm>然后使用reverse(s.begin(), s.end())。2)由于python的字符串不可变,不能在空间O(1)下解决此题。C++在空间O(1)限制下的解法:思路类似上题,先反转整个字符串,然后将两部分各自反转回来。1)直接的解法是用python的split()和strip()方法。
2024-05-29 15:57:32
442
原创 小结:n数和的解法
2)双指针:先对数组排序nums.sort(),一层for循环套一层双指针。2)双指针:先对数组排序nums.sort(),两层for循环套一层双指针。3)哈希表:对数组遍历一次,用dict保存已访问的元素及其下标,一旦当前数能够与dict中的数构成target,返回结果。1)暴力:两层for循环遍历nums,时间 O(n^2),空间O(1)。1)暴力(不考虑去重):三层for循环遍历nums,时间 O(n^3)。1)暴力(不考虑去重):四层for循环遍历nums,时间 O(n^4)。
2024-05-29 10:19:24
404
原创 代码随想录day7 哈希(2)
返回时遍历set中的tuple,用list()方法将其转为list,再在外层套一个list。接着,两层for循环遍历n3和n4,每当com = -(n3+n4)在map12中时,就将对应的频次加到cnt上,时间O(n^2),空间O(1)。然而该方法不能跑完所有样例,可能是因为在遍历n3的for循环中存在对hashmap[tar]的遍历,极端情况下,比如nums所有元素相同,则两数和hashmap只有一个键值对,值是长度为n!暴力遍历,若不考虑去重,使用三层for循环,时间O(n^3),空间O(1)。
2024-05-28 21:10:59
1205
原创 代码随想录day5 哈希表
用哈希表,dict或defaultdict实现,记录已访问过的元素,令key为元素值、value为元素下标。时间和空间都为O(n)。python dict的get方法:dict_name.get(key, default_val_if_key_not_exist) -> dict_name[key]。使用三个集合set1、set2、res,遍历一次集合set1,时间O(n+m)空间O(n),其中m是集合和数组转换的耗时。时间O(n)空间O(1)。暴力解法,两层for循环,时间O(n^2)、空间O(1)。
2024-05-27 17:03:46
460
原创 代码随想录day4 交换链表节点 删除链表倒数节点 求链表相交节点 求链表入环节点
相遇后,将它们在环中相遇节点赋给fast,将head赋给slow,再同步移动两指针,它们将在入环节点相遇(证明见下方链接)。若当前节点cur后只剩下<=1节点(cur.next为空或cur.next.next为空)则不需再操作,返回。易错2:涉及链表长度时,注意区间开闭,比如循环的条件是while cur还是while cur.next、是while window_size < n还是while window_size <= n、起始时指针指向dummy_head还是head,等等。
2024-05-26 16:30:13
392
原创 代码随想录day3 移除链表元素 设计链表 反转链表
时间O(n),空间O(1)。题目的头节点head是链表的第一个节点,创建虚拟头节点virtual_head,指向链表的第一个节点。从virtual_head开始,检查当前节点node的下一个节点的值,若为val则删去node的下一个节点,即令node指向node.next.next;注意不要直接返回虚拟头节点。
2024-05-24 11:38:24
269
原创 代码随想录day2 977有序数组的平方 & 209长度最小的子数组 & 59螺旋矩阵II
易错点1:若写成 while r < n,可能会在 cur_sum 刚加上 nums[-1] 时结束循环,错过对这个新的子数组的左边界更改(错过第二个while循环)。易错点3:x,y虽然是直觉上的横纵坐标(在写move函数时),但x作为列idx,是mat的第1维;由于数组有序,可以从两头开始(绝对值大)用双指针向中间(绝对值小)逼近,只遍历一次数组,时间和空间复杂度均为O(n),p指示当前元素在新数组中的下标。滑动窗口法,时间O(n)、空间O(1),l和r指针各自遍历一次数组即可。但边界条件判断易错。
2024-05-23 20:29:04
444
1
原创 代码随想录day1 二分查找 & 移除元素
解法一,双向指针:l和r分别从左右两端逼近中间;解法二:快慢指针,快指针遍历数组,慢指针指向不等于val的数组的后一个元素。解法二更简洁清晰,解法一的条件判断容易出错。1.2 开闭区间与循环不变量:一开始没有仔细思考开闭区间,直接将middle赋值给left或right,导致超时,通过添加r7~r12解决。1.1 为了防止数值溢出,将r14改为mid = l + (r - l) // 2。注意python的整除是//。空间复杂度O(n),时间复杂度O(n),事实上不符合题目“原地更改nums数组”的要求。
2024-05-21 22:56:04
287
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人