一年里刷过的题
2101. 引爆最多的炸弹
BFS
数据量较小,BFS得到答案,注意引爆条件
754. 到达终点数字
数学+分类讨论
1.target正负不影响答案(所有步骤反着来为-target的答案),取abs(target)来讨论
2.一直往右走,若最后恰好到达target,得到答案
3.最后一步超过终点,且与终点距离为偶数,则将前面的其中一步改为往左得到答案
4.如果与终点距离为奇数,继续向前走,直到相距为偶数,再改一步往左得到答案
1106. 解析布尔表达式
栈
1.遇到逗号,跳过
2.遇到右括号,进行计算(直到左括号的上一个运算符)
3.遇到其他字符,入栈
1536. 排布二进制网格的最少交换次数
贪心
1.需要后缀有n-1个0的行,n-2个0的行...0个0的行
2.从上到下确定,找到最近的满足情况的行,模拟交换
3.由于从上到下确定行,越下面的行限制条件越宽松,贪心正确
4.最后一行无需判断
1255. 得分最高的单词集合
DFS
words长度较小,可以对每个单词枚举 选/不选 最终判断该选择是否合法 最多枚举 pow(2,n) 种情况,n为words长度
2054. 两个最好的不重叠活动
排序
1.把活动的开始时间点和结束时间点提取出来进行排序,时间相同时先处理开始事件,否则可能让第一个活动的结束时间等于第二个活动的开始时间
2.遍历时间点,遇到结束时间点维护已结束活动的最大价值,遇到开始时间点维护答案=已结束活动的最大价值+当前开始时间点的价值
1326. 灌溉花园的最少水龙头数目
模拟
类似于跳跃游戏,位于i的水龙头,范围为r,预处理:i-r最大能到达i+r
即 maxPos[i-r] = max(maxPos[i-r],i+r) 当i-r<=0时,则更新maxPos[0]
接下来模拟跳跃,cur表示当前最大能跳跃的位置,当i==cur时:将cur更新为max,max为之前所有的位置中最大能到达的位置,同时ans+=1
2064. 分配给商店的最多商品的最小值
二分
最大值最小化,使用二分查找。需要把所有商品分配出去,商店可以接收0件商品,但分配后不能有多余商品
每个商店最多分到max件,计算最多能分给count个商店。当商店数n>=count时,不会让商品多余,为合法的分配,缩小右边界,答案为最后一次合法的右边界
2305. 公平分发饼干
DFS+回溯
数据量较小,枚举哪个孩子得到当前饼干
2537. 统计好子数组的数目
滑动窗口
使用哈希表统计相同元素的出现次数,从而获取满足条件的下标对的个数cnt
注意当相同元素有n个时,减少1个该元素,会减少多少个满足条件的下标对?会减少(n-1)个
1722. 执行交换操作后的最小汉明距离
并查集
allowedSwaps[i] = [ai, bi]说明ai和bi这两个下标的元素可以互通
对于互通的下标集,大小为m,尽可能让更多的下标与target对应下标的元素相同,假设有k个下标符合条件,得到最小汉明距离为m-k,累加所有互通下标集的最小汉明距离即为答案
2327. 知道秘密的人数
动态规划
dp[i]表示第i天结束时,新增的知道秘密的人数,dp[i]=dp[begin,end]的累加,其中begin为i-forget+1,end为i-delay
第n天时知道秘密的人数为最后forget天的新增人数之和
1871. 跳跃游戏 VII
动态规划+前缀和
1.定义dp[i]表示i位置是否能够到达,在区间[i-maxJump,i-minJump]中若存在j,满足dp[j]为true,则dp[i]为true
2.通过枚举区间来得到dp[i]的算法时间复杂度为o(n^2),会超出时间限制,因此使用前缀和来优化。
3.当dp[i]为true时对前缀和的贡献为0,反之贡献为1
3.当pre[right+1]-pre[left]!=(right-left+1)时说明出现了0,即出现了true
878. 第 N 个神奇数字
数学+二分
1.设f(x)表示<=x的神奇数字的个数。在<=x的数中,有x/a个数能被a整除,有x/b个数能被b整数,有x/lcm(a,b)个数能被lcm(a,b)整除,lcm为最小公倍数函数
2.可以写出f(x)的表达式f(x)=x/a+x/b-x/lcm(a,b),在题目条件下f(x)是单调递增函数
3.设f(x)==k,那么x就是第k个神奇数字,要得到第n个神奇数字,可以使用二分查找,当f(x)
2360. 图中的最长环
模拟
使用一个时钟记录全局时间,尝试从每一个点出发寻找环。若该点不是首次访问,则跳过。若该点是首次访问,记录当前时间startTime,再开始寻找环。
如果找到某个节点其访问时间visitTme满足visitTime>=startTime,说明找到了一个环,动态更新最长环的长度即可得到答案
780. 到达终点
模拟
反向计算:位于(x,y)的点可以转换为(x,y-x)或(x-y,y)
由于x,y>=1,那么能够继续转换的条件是:当前的x大于目标x,当前的y大于目标y,当前的x不等于当前的y
当前x>当前y且能够继续转换时,x不断减去y;反之y不断减去x,直到无法继续转换
如果当前x,y与目标x,y完全相同返回true,否则判断x和y是否其中一个与目标相同,若没有其中一个相同,返回false。若其中一个相同,进一步判断能否通过不断减的操作让其也与目标相同。
1878. 矩阵中最大的三个菱形和
暴力枚举
枚举所有可能的菱形和,使用TreeSet及其pollLast方法可以维护最大的三个菱形和
823. 带因子的二叉树
排序+枚举
先对arr进行排序,因为较大的元素只能由两个较小元素相乘得到,优先计算以较小元素为根的构造方案数
遍历arr数组,枚举每一个元素作为二叉树的根节点时,有多少种构造方案
设根节点为root,如果arr中存在元素x以及root/x,则:
以root为根的构造方案数 += 以x为根的方案数 * 以root/x为根的方案数
以root为根的初始构造方案数为1,即没有左子树也没有右子树
1665. 完成所有任务的最少初始能量
排序
由于门槛>=耗能,对每个任务提炼一个虚报值=门槛-耗能
对任务按照虚报值从小到大排序,虚报值越大的任务越需要前面的耗能来支撑虚报,若前面的能量还不够则补到足够为止。
遍历每个任务,累加实际耗能,若实际耗能仍不够虚报,则补到虚报后的值。
2602. 使数组元素全部相等的最少操作次数
排序+二分查找+前缀和
首先对数组进行排序,记数组长度为n。对于每个查询queries[i],二分查找最后一个<=queries[i]的下标,记为left
将数组分成[0,left]以及[left+1,n-1]两部分,要把数组中所有元素变成queries[i]:
对于[0,left]而言,相当于把所有元素增加至queries[i];
对于[left+1,n-1]而言,相当于把所有元素减少至queries[i];
使用前缀和维护区间的值,通过整体计算可以快速获得答案。
2608. 图中的最短环
BFS
首先建立无向图。由于顶点总数较小,从每个节点出发寻找环,同时记录到不同节点的距离,当重复遇到节点时说明找到了环,动态维护最短环的长度作为答案
进行BFS时,数据格式为{节点id,当前节点的父节点},不能走回头路(即不能重新回到父节点)
1124. 表现良好的最长时间段
枚举+前缀和
hours的长度不超过1e4,可以使用双重循环枚举表现良好的时间段
工作时间大于8小时,记为1,反之记为0,通过前缀和可快速判断某个区间是否为表现良好时间段
动态维护表现良好时间段的最大长度作为答案
991. 坏了的计算器
数学+模拟
反向计算:当Y大于X时,对Y进行操作向X趋近,Y为奇数时进行加一操作,为偶数时进行除2操作
最后对Y进行(X-Y)次加一操作得到X
2585. 获得分数的方法数
动态规划
分组背包模板题。定义dp[i][j]表示前i道题目获得target分的方案数
使用三重循环,枚举前i道题目,枚举第i道题目做的数量j,枚举分数k进行状态转移,最后dp[n][target]就是答案
873. 最长的斐波那契子序列的长度
动态规划
定义dp[j][k]表示最后两项为下标j和下标k的元素时最长的斐波那契子序列长度
对于每个k,枚举可能的下标i和j满足arr[i]+arr[j]==arr[k]
找到满足这样条件的下标i,j,k时,如果以下标i,j为最后两项的斐波那契子序列长度为0,那么下标i,j,k组成了新的斐波那契初始序列,令dp[j][k]=3,否则dp[j][k]=max(dp[j][k],dp[i][j]+1),同时更新ans
如果arr[i]+arr[j]
2049. 统计最高分的节点数目
DFS
0节点是根,从0出发进行dfs
对于每一个节点,计算出左子树的节点数量leftCnt以及右子树的节点数量rightCnt,那么该节点的分数为:
leftCnt * rightCnt * (sum-leftCnt-rightCnt-1),这三部分中出现0的部分取1
动态维护最高分及其个数,最高分的个数作为答案
2296. 设计一个文本编辑器
双向链表
光标不会主动移动到某个下标位置,只会相对移动(向左或向右),可以用双向链表模拟,每个节点存储一个字符
2680. 最大或值
枚举+前缀后缀优化
选择一个数并将它乘2等价于将其二进制表示左移1位
进行k次操作一定是移动同一个数字,才能得到最大的结果
枚举对每一个数字左移k次,取最大值作为答案
对于下标i,使用前缀或、后缀或数组可以快速计算出[0,i-1]中元素的按位或以及[i,n-1]中元素的按位或
1147. 段式回文
贪心+递归
当前缀与后缀相同时,立即分离出来,再对剩余字符串继续递归,直到无法分离
为什么要立即分离?因为对于更长的相同前后缀,一定可以分解为更多的子串,题目需要求出k的最大值,因此需要贪心地分离相同的前后缀。
1898. 可移除字符的最大数目
二分
移除的字符越少,越可能满足p是s的子序列,而假设移除n个下标后仍满足p是s的子序列,那么移除更少的下标例如n-1个,一定也满足该条件。
二分查找最大的k,找到满足条件的临界值作为答案
1671. 得到山形数组的最少删除次数
枚举+动态规划
题目定义山形数组的长度至少为3。记nums长度为n,枚举[1,n-2]作为山顶,当山顶为i时,问题转化为:
求[0,i]中最长上升子序列的长度len1以及[i,n-1]中最长下降子序列的长度len2
此时删除次数为n-(len1+len2-1),动态维护最少删除次数作为答案
定义upper[i]表示到下标i为止的最长上升子序列的长度
定义lower[i]表示以下标i开始的最长下降子序列的长度
注意点:
不能把山顶的左边或右边全部删掉了,左右两边至少留有一个元素,即最长上升/下降子序列都>1时,才更新答案
1373. 二叉搜索子树的最大键值和
递归
每棵子树返回:这棵子树的最小节点值、这棵子树的最大节点值、这棵子树的所有节点值之和
对于空节点,返回{max,min,0},对于不是二叉搜索树的节点,返回{min,max,0}
这是由二叉搜索树的判断决定的,当一个节点root的孩子child不是二叉搜索树,该节点也不会是二叉搜索树,非二叉搜索树的判断条件为:
root的值<=child的最大值(当child为左孩子) 或者 root的值>=child的最小值(当child为右孩子)
需要让以上式子成立,因此child不是二叉搜索树时返回{min,max,0},使其父节点也被判定为非二叉搜索树
同理,空节点返回{max,min,0}是为了让父节点无条件被判定为二叉搜索树
2147. 分隔长廊的方案数
模拟
如果座位的总数为0或者为奇数,那么不存在划分方案,返回0
模拟划分,每遇到两个座位后,统计中间间隔的植物数量count,该段的划分方案为(count+1),
对ans而言应乘上(count+1),再对mod取余
2594. 修车的最少时间
二分
给定m分钟,计算所有机械工可以修好的汽车总数cnt,当cnt>=cars时缩小右边界right,最后right作为答
案
能力值为 r 的机械工可以在 r * n * n 分钟内修好 n 辆车,那么给定m分钟,可以修好sqrt(m/r)辆车
1043. 分隔数组以得到最大和
动态规划
定义dp[i]表示前i个元素可以得到的最大和
dp[i]由dp[j]+max*(i-j)转移而来
2673. 使二叉树所有路径值相等的最小代价
递归
对于两个有着相同父节点的叶子节点来说,他们到达根节点的路径上只有本身的cost不同,要满足到达根节点的路径值相同,只能将两个节点的cost改为相同,由于只能进行增加操作,应增加较小的cost
通过递归,获取每个node经过操作后到每个叶子节点的路径值,叶子节点的路径值为本身的cost值
具体来说,先获取node的左、右孩子到其每个节点的路径值left、right,为答案增加abs(left-right)次,让左右孩子的路径值相同,返回给上一级的数据为max(left,right)+本身的cost值
2398. 预算内的最多机器人数目
二分+滑动窗口
题目要求的是可以连续运行的机器人数目,枚举连续的个数m,二分查找满足条件的边界值作为答案
对于连续个数m,具体实现是使用固定长度的滑动窗口+TreeMap维护窗口内最大值,找到满足cost<=budget的窗口则返回true,遍历完所有可行的窗口仍未找到返回false
2069. 模拟行走机器人 II
模拟
根据题目描述,机器人只会在网格图的最外圈移动。
定义方向,记录机器人的坐标以及朝向,模拟进行移动
2050. 并行课程 III
记忆化搜索
定义dfs函数返回第i门课程所需要的最少月份数:
若第i门课程没有先修课,完成该课程的最少月份数为time[i-1];
若第i门课程有先修课,完成该课程的最少月份数为max(先修课需要的最少月份数)+time[i-1];
使用记忆化搜索减少重复搜索
所有课程中最大的最少需要月份数作为答案
1130. 叶值的最小代价生成树
单调栈
问题可以转化为:给定一个数组arr,不断地合并相邻的数,合并代价为两个数的乘积,合并之后的数为两个数的最大值,直到数组只剩一个数,求最小合并代价和
在合并时,优先合并较小的两个数能得到最小代价
1552. 两球之间的磁力
排序+二分
对pos进行排序后,二分枚举答案
具体实现:枚举最小磁力为k时,是否为合法放置。
统计成功放置的个数cnt,使用pre记录上一个放置的位置,从下标1开始枚举位置,当前位置pos[i]-pre>=k时,可以在pos[i]放置,cnt+=1。最后如果cnt>=m为合法放置
2731. 移动机器人
排序+前缀和
当两个机器人相撞时,它们开始沿着原本相反的方向移动,等价于没有相撞
分别计算出每个机器人最后的位置,排序后再计算两两距离之和
为什么需要排序?对于i下标,保证pos[i,n-1]都大于pos[i],那么计算距离和时相当于pos[i,n-1]之和减去(n-1-i+1)个pos[i],如果不排序,那么计算出的距离可能为负数。
由于(i,j) 和 (j,i) 视为相同的坐标对,因此遍历到i时,只需计算i与后面下标的距离,不用计算与前面下标的距离。使用前缀和快速计算距离
1416. 恢复数组
动态规划
定义f[i]表示s的前i个字符能恢复的方案数
k<=1e9 对于第i个字符,只需枚举下标[i-10,i-1]是否能够形成有效的数字
注意前导0的判断
1745. 分割回文串 IV
动态规划+枚举
使用动态规划预处理所有区间,找出回文区间。再使用双重循环枚举分割点
具体实现:枚举区间长度len,枚举区间左端点i,计算出区间右端点j=i+len-1,若s[i]==s[j]:当len==2时,dp[i][j]为true,否则dp[i][j]=dp[i+1][j-1]
题目需要分割成非空子字符串,注意割点的范围
85. 最大矩形
预处理+枚举
对于位于(i,j)的1,预处理len[i][j],表示其左边连续1的个数(包括本身)
遍历矩阵,找到1说明找到了矩形的底边,向上枚举矩形的高度,同时底边的长度取min(len[k][j]),其中k为向上枚举的行下标,从而得到面积,维护最大面积作为答案
具体实现:当mat[i][j]==1时,记录底边长度a=len[i][j],矩形高度b=1,矩形面积s=a*b,在想上枚举高度的过程中b不断+1,a取最小的len[k][j],s取最大的a*b,最后用s来更新答案
1727. 重新排列后的最大子矩阵
预处理+排序+枚举
对于(i,j)的1,预处理len[i][j]表示其上面连续1的个数(包括自身)
对每一行的len[i][j]进行排序,对于i行,找到一个len[i][j]!=0后向左扩展矩阵的宽度,维护最大高度,同时维护最大矩阵面积作为答案,同时跳出i行的循环,因为经过排序,后面的len[i][j]只会更小
1562. 查找大小为 M 的最新分组
模拟
使用正难则反的思想。反向遍历进行操作,当出现恰好长度为m的一组连续1时,直接返回答案
通过TreeSet可快速记录最近的前面的1的位置以及最近的后面的1的位置
注意步骤与下标之间的映射,以及可以直接返回结果的特判
2654. 使数组所有元素变成 1 的最少操作次数
模拟
出现一个1就可以一直传播,直到整个数组所有元素等于1
遍历数组,统计1出现的次数cnt,如果cnt不等于0,答案为n-cnt,n为数组长度
否则,需要找到一个最短子数组,满足其元素的最大公约数为1,从而构造出1,答案为(len-1)+(n-1),其中len为子数组的长度
1802. 有界数组中指定下标处的最大值
贪心+二分
要让nums[index]最大,那么其他元素应尽可能小,即nums[index]的左边的元素大小不断递减,右边的元素大小也是不断递减,[0,index]形成递增数列,[index,n-1]形成递减数列
二分枚举nums[index]的值,计算所有元素之和是否小于等于maxSum,若满足为合法赋值,缩小左边界。最后的左边界作为答案
注意点
2.题目要求数组全部为正整数,等差数列小于1
1.用到了高中的等差数列求和公式的部分记为1
2018. 判断单词是否能放入填字游戏内
模拟
1 <= m * n <= 2 * 1e5 数据量较小,模拟是否能够将单词填入矩阵中
1705. 吃苹果的最大数目
优先队列
把苹果的过期时间放入优先队列中,模拟每一天吃苹果,直到队列中所有苹果都已过期
在一边新增苹果时就一边吃苹果,遍历完新增苹果数组后,继续模拟吃苹果
1798. 你能构造出连续值的最大数目
排序+贪心
对硬币进行排序,初始时ans=0,表示数字0必定能得到。
遍历硬币,当前正在构造的数为ans+1,若coins[i]>ans+1,而后面的硬币值只会更大,说明ans+1无法被构造,只能构造[0,ans]的数,返回ans+1作为答案。
否则可以将可以构造[0,ans+coins[i]]中的所有整数。那么如何证明?
仔细想想,之前可以构造[0,ans]区间的数,现在来了一个coins[i],我把coins[i]与前面可以构造出来的1组合,是不是得到了coins[i]+1?与之前的2再组合,得到coins[i]+2,再与3,与4...一直组合,最后可以构造出ans+coins[i]。
996. 正方形数组的数目
建图+dfs+回溯+排序
对于下标a,如果与下标b各自对应的元素之和为完全平方数,则为下标a与b之间添加一条无向边
dfs同时记录路径情况,将所有节点遍历得到顺序后,放入哈希表去重
使用排序进行剪枝,当相邻元素相同,且visit[i-1]为false时,说明i-1已经作为同层元素(想象一下搜索树长什么样子)使用过一次(经过回溯其visit又变成了false),直接跳过
最后哈希表的长度作为答案
1964. 找出到每个位置为止最长的有效障碍赛跑路线
二分
维护一个列表,列表中元素为非递减,遍历数组
当前元素大于等于列表中所有元素时,放入列表末尾,ans[i]为当前列表长度
否则,在列表中二分查找第一个大于当前元素的位置,并将其改为当前元素,ans[i]为该位置+1
这样做的原因是在保持非递减序列最长的同时,序列中每一项尽可能小。
答案是不会错的,但是我们实际匹配的序列与列表中的序列并不一定相同,
例如输入数组{1,2,3,5,2,6}
当遍历到下标4,列表中的元素{1,2,3,5}会被修改为{1,2,2,5}
接着遍历下标5,我们知道设置的障碍应该是{1,2,3,5,6},而此时将6放入列表末尾,列表为{1,2,2,5,6}
但是毫无疑问的是,对于下标5的答案是5,我们通过维护的非递减列表长度依然得到了正确的答案
827. 最大人工岛
dfs+枚举
先对所有的岛进行dfs并记录岛的编号及其大小(为了与初始矩阵中的0、1区分开,岛的编号从1001开始),并初始化答案为最大的岛屿面积
再遍历矩阵,枚举每个0,计算将其变成1后可以合并的岛屿的最大面积,注意:翻转后,当前0的位置会变成1,也算岛屿面积
1818. 绝对差值和
排序+二分
先将nums1中所有元素复制一份样本并排序,并计算每一对下标i对应的两个数组的绝对差值
遍历nums2中的元素,从nums1的排序副本中二分查找最接近的元素,维护替换后能产生最大变化的差值及其下标
最后统计绝对差值和,对于替换后能产生最大变化的下标进行替换,其余下标按原来的diff计算
为什么要选择替换后能产生最大变化的下标?
由于使用二分查找,那么进行替换后的差值一定小于等于原差值,替换后的差值相比与原差值的变化越大,说明替换后能使差值和越小
813. 最大平均值和的分组
前缀和+动态规划
结论:对于给定的k,分成k个非空子数组能得到最大分数。如何证明?
可以这样想,求平均值时相当于每个元素都要除去一个分母再相加,这个分母是划分出的子数组的长度,要让平均值更大,就要让每个元素除去的分母更小,所以划分更多的子数组,可以得到更大的分数
定义dp[i][j]表示前i个元素划分为j个子数组的最大平均值和,使用前缀和快速获取区间总和
最后答案为dp[n][k]
777. 在LR字符串中交换相邻字符
思维题
可以转换需要满足的条件:
1.字符L和R的相对顺序相同
2.每个L在end中的下标小于等于对应的L在start中的下标
3.每个R在end中的下标大于等于对应的R在start中的下标
三个条件都满足时返回true,否则返回false
1760. 袋子里最少数目的球
二分
根据操作的定义,袋子中的球可以被拆分而不能合并,且求的是最小的单个袋子最大球数,使用二分查找
具体实现:每个袋子的最大球数为max时,求出此时所需要的操作次数needOp,若needOp<=maxOp,说明当前操作可行,缩小右边界。最后一次右边界作为答案
2762. 不间断子数组
滑动窗口+TreeMap
使用TreeMap快速获取窗口内的最大值与最小值,每一次循环都先增加右边界,再计算窗口内的差值,当差值不满足条件时进行缩窗,缩窗后如果当前窗口满足条件,对答案加上当前窗口的长度
2111. 使数组 K 递增的最少操作次数
分组+二分
将arr分成k组,分别求每一组变成非递减的最少次数,最后累加得到答案
对于某一组元素,设长度为n,则将其变成非递减序列的最少次数=n-最长非严格递增子序列长度
由于数组长度达到1e5,不能使用时间复杂度为o(n^2)的动态规划算法,可以使用维护有序序列+二分查找来获取最长非严格递增子序列的长度
2227. 加密解密字符串
模拟
对于一个字符串,其解密结果可能不唯一,其加密结果唯一。可以逆向思考:
对dictionary中的每个字符串进行加密,记录加密得到的 字符串及其出现的次数
那么对于word2,想知道其解密结果,获取其在加密记录表中的出现次数即可
1482. 制作 m 束花所需的最少天数
二分
二分枚举等待的天数,判断是否能满足条件
具体实现:遍历数组,当开花时间<=等待时间,说明该朵花可以采摘,count++。每当count>=k时,count-=k,并为find+1说明完成了一束花的制作。如果遇到一朵花其开花时间>等待时间,立即重置count的数量为0。最后返回find是否>=m
1793. 好子数组的最大分数
预处理+单调栈+枚举
分数由子数组中的最小值以及子数组的长度决定,为了得到最大分数,可以枚举最小值。
当最小值固定后,尽可能让子数组长度更大
使用单调栈预处理每个下标i,其左右两边比自己更小的数字出现的下标,那么当下标i作为最小值时,就可以快速得到子数组的最大长度,再计算出分数,维护最大分数作为答案
分数计算的具体细节:设上一个更大值的下标为left,下一个更大值的下标为right,那么该子数组的范围为[left+1,right-1],长度为right-1-left-1+1=right-left-1,分数为nums[i]*(right-left-1),注意合法的子数组需要满足左右端点与k的关系
1631. 最小体力消耗路径
BFS+二分
二分枚举体力消耗值,判断是否能找到终点
对于限定最大值max,使用BFS从起点出发搜索是否能到达终点,当相邻格子未被访问且高度差绝对值小于max时放入队列,能到达终点说明当前max合法
2516. 每种字符至少取 K 个
滑动窗口
先统计出每个字符分别有多少个,如果存在某字符个数少于k,直接返回-1。获取一个最长的窗口,窗口内每个字符的个数<=cnt-k,cnt是字符对应的总数,那么n-窗口长度就是答案
2509. 查询树中环的长度
最近公共祖先
对于每个查询获取其最近的公共祖先,答案为两个节点分别到与公共祖先的距离+1
由于本题是数组表示的完全二叉树,可以通过下标快速获取最近公共祖先的位置
具体实现:对于编号a和b
如果 a>b,说明 a 的深度大于等于 b 的深度,把 a 移动到其父节点,即 a=a/2;
如果 a 如果 a=b,则找到了 LCA,退出循环
1690. 石子游戏 VII
前缀和+动态规划
定义dp[i][j]表示对于数组区间[i,j]两人得分的差值
使用前缀和快速计算石头的价值之和
具体实现:从小到大枚举区间长度,dp[i,j]可由更小的区间dp[i+1,j]或dp[i,j-1]转移而来,二者取最大
1737. 满足三条件之一需改变的最少字符数
模拟
枚举a的最大值,b的最小值的各种情况,取最少操作次数
枚举b的最大值,a的最小值的各种情况,取最少操作次数
枚举a和b每个字母都相同时的各种情况,取最少操作次数
答案取上面三个方案中的最少操作次数
2434. 使用机器人打印字典序最小的字符串
贪心+栈
遍历s,不断从s中取出ch,放入栈中。
什么时候将字符写到纸上?
当栈顶元素<=当前s中剩余字符的最小值时,将其写在纸上。
否则应继续从s取到那个更小值ch',让ch'在答案字符串的前排
1696. 跳跃游戏 VI
动态规划+优先队列
使用maxScore记录到达每个位置的最大得分
对于位置x,需要知道[x-k,x-1]的最大得分,可以使用优先队列快速获取该区间内的最大得分
1463. 摘樱桃 II
动态规划
两个机器人同时移动,任意时刻两个机器人都在同一行中
定义dp[i][j][k]表示在移动到下标为i的行时,机器人1位于(i,j),机器人2位于(i,k)可以收集的最多樱桃数目
初始化:除了dp[0][0][n-1],所有状态初始化为-1
当机器人1与机器人2位于同一列时,只能摘取一次(摘取顺序不影响答案)
枚举机器人1的上一个状态以及机器人2的上一个状态,当两个机器人的上一个状态都合法(不等于-1)时,进行状态转移,取最大的状态
枚举到达m-1行时,机器人1和机器人2分别位于不同位置时可以收集的最多樱桃数目,取最大作为答案
2564. 子字符串异或查询
预处理
需要找到一个二进制字符串str,满足:str对应的十进制值 ^ firsti == secondi
根据异或的性质,等价于str对应的十进制值 == firsti ^ secondi
由于0 <= firsti, secondi <= 1e9 那么满足条件的str字符串对应的十进制值也会在该范围中
预处理所有长度<=30的子字符串及其位置,通过哈希表快速匹配字符串
2392. 给定条件下构造矩阵
拓扑排序
矩阵的大小为 k × k ,需要填充k个数字,让每个数字独占一行,可以尽可能的使矩阵满足条件
分别对行和列的约束关系进行拓扑排序,从而确定数字的位置
如果行和列的拓扑排序后得到的序列长度
2350. 不可能得到的最短骰子序列
思维+哈希表
一开始,可以表示的最长子序列长度len为0
遍历数组,每当某个时刻1~k都出现至少一次,那么len+=1,并清空对所有数字的计数
最后无法表示的长度为len+1
使用哈希表快速判断每个数字是否都出现至少一次
1537. 最大得分
贪心+前缀和
两个数组都有序,那么不论怎么走,所有的公共点都至少经过一次
记录公共点的位置,当遇到公共点时,根据前缀和判断是否要切换路径(比较两条路径中,当前公共点与下一个公共点之间的所有数字的和,贪心地选择和更大的路径)
1.所有公共点只统计一次
2.第一个公共点之前、最后一个公共点之后的路径怎么走,需要额外讨论
3.两个数组没有任何公共点时,也需要额外讨论
1642. 可以到达的最远建筑
二分+贪心+排序
一架梯子相当于无限量的砖块,因此梯子应该用在高度差尽可能大的地方
二分枚举到达最远的下标,将其中每次需要梯子或砖块的地方的高度差统计出来,进行排序
排序后,在最大的ladders个高度差上使用梯子,其余的高度差使用bricks
如果使用后bricks>=0说明该下标可达,缩小左边界,否则缩小右边界
最后一个合法的左边界作为答案
获取高度差时,可以遍历,也可以预处理+二分(这里直接从下标0开始遍历到下标mid获取高度差)
815. 公交路线
BFS
先遍历所有的线路,若该线路中存在起始点source,则将该线路中所有点放入队列中
进行BFS,路线不需要重复访问,每访问到一条路线时将其标记为已访问
进入新的路线时将整个路线的点入队。找到终点target时返回答案
特判:source==target时,不需要乘坐任何公交车,返回0
2439. 最小化数组中的最大值
二分
枚举经过操作后,数组的最大值为x,遍历数组判断是否可行
进行操作相当于可以把后面大于x的元素全部改为x,并分配给前面小于x的元素
遍历数组:
当nums[i]
记当前剩余可分担的大小为cnt,任何时刻cnt<0说明令最大元素为x不可行
1293. 网格中的最短路径
BFS
对于每个状态,记录当前状态的行下标、列下标和已经消除的障碍物个数,记已经消除的障碍物个数是 eliminations,记当前状态的最少步数是distance
如果当前状态已经到达右下角,则返回distance。如果当前状态尚未到达右下角,则对于四个方向上的每个相邻单元格执行BFS:
1.如果相邻单元格是空白,且该相邻单元格对应的消除eliminations个障碍物的状态未访问,则将该相邻状态的最少步数更新为 distance+1,继续访问该相邻状态。
2.如果相邻单元格是障碍物,且满足eliminations
1943. 描述绘画结果
差分
注意到 1 <= start_i < end_i <= 1e5 那么只需要开一个差分数组就可以知道某下标的颜色之和
但是颜色值相同可能是由不同颜色混合而来的,如何区分?
将差分数组开成二维,分别记录加和减,某位置如果有加或有减,那么它就是不同颜色的边界
将color初始化为0,每遇到颜色边界时,如果color不为0,添加答案,更新color,并更新color的左边界pre为当前的下标i
835. 图像重叠
思维+模拟
将所有的1上下左右移动相当于img1的(0,0)位置可以贴在img2的任意坐标(可以超出img2的范围),img1自身相对位置保持不变
枚举帖在img2的任意坐标位置,计算最大重叠数
实际上枚举img1贴在img2中的x坐标(-n,n),y坐标(-n,n)即可,因为超出该范围的坐标即使贴上去两张图片也不会有重叠
1250. 检查「好数组」
裴蜀定理
对于不全为零的任意整数 a 和 b,记 g = 最大公约数(a, b),则对于任意整数 x 和 y 都满足 a×x + b×y 是 g 的倍数
特别地,存在整数 x 和 y 满足 a×x + b×y=g
裴蜀定理可以推广到任意个整数的情况,本题等价于求出nums数组中所有元素的最大公约数,若最大公约数为1返回true
1032. 字符流
字典树
1.把words中所有单词放入字典树
2.使用StringBuilder记录当前的字符流
3.对于每一次查询,枚举StringBuilder中的所有后缀,如果能找到返回true
使用字典树可以通过现有的StringBuilder进行后缀匹配,不需要截取字符串,因此省下许多时间
1488. 避免洪水泛滥
贪心+TreeSet
由于湖泊编号较大,可以使用哈希表来存储哪些湖泊是满的
每次遇到不下雨的天气,先存储抽干湖泊的机会,具体操作是将该机会的下标放入TreeSet中
当湖泊未满时,记录湖泊的满水日期
当遇到湖泊已满时,使用湖泊满水日期之后最近的抽水机会,具体使用TreeSet的higher方法查找,并更新湖泊的满水日期
1092. 最短公共超序列
动态规划
str1、str2长度最大为1000,可以根据数据量推测解题使用的算法
定义dp[i][j]表示同时以str1中[i,m-1]的字符和str2中的[j,n-1]的字符作为子序列的最短字符串的长度
初始化:其中一个字符串不选取任何字符时,dp值为另一个字符串的长度
状态转移:
ch1==ch2时,最短字符串的开头为ch1,dp[i][j] = dp[i+1][j+1] + 1
ch1!=ch2时,最短字符串的开头为ch1或ch2,dp[i][j] = min(dp[i+1][j],dp[i][j+1])+1
最后根据字符的相同情况以及dp值的转移过程构造答案字符串
907. 子数组的最小值之和
单调栈
最容易想到的是使用双循环枚举每个子数组的最小值计算答案,超出时间限制
如果枚举arr[i]作为最小值时对答案的贡献呢?那么就需要使用单调栈来维护arr[i]作为最小值时子数组的左右边界
假设左边更小值出现在下标left,右边更小值出现在right 则对答案的贡献为(i-left)*(right-i)*arr[i]
但是这样计算会导致子区间的重复计算,因此需要使用左开右闭或者左闭右开区间
1882. 使用服务器处理任务
优先队列
服务器分配规则:
1.需要处于空闲状态,优先分配给权重最小的服务器
2.权重相同时,分配给下标最小的服务器
使用两个优先队列分别存放空闲服务器、繁忙服务器
使用time来标记全局时间,遍历任务数组并处理任务,每遍历一次time+=1
遍历完任务数组后,可能还有任务没有完成,这时候继续处理,此时time应该直接“加速”到又有繁忙服务器变为空闲的时间,而不是简单+1(每个任务的完成时间可能会很长)
2151. 基于陈述统计最多好人数
二进制枚举
人数至少为2,那么可以认为好人数至少为1(可以枚举所有的4种情况来证明)
情况一:
1 认为2 是好人
2 认为1 是好人
最佳情况是两人都是好人
情况二:
1 认为2 是坏人
2 认为1 是好人
至少1可以是好人,此时2是坏人
情况三:
1 认为2 是好人
2 认为1 是坏人
至少2可以是好人,此时1是坏人
情况四:
1 认为2 是坏人
2 认为1 是坏人
可以认为1或2是好人,那么另一个人是坏人而且说假话
人数至多为15,可以通过二进制枚举[1,1<
1278. 分割回文串 III
预处理+动态规划
先预处理每个区间[left,right]构成回文子串需要的最少修改字符数
定义dp[i][j]表示[0,i]区间分割为j个回文子串时需要的最少字符数
805. 数组的均值分割
折半搜索+二进制枚举
数组长度最大为30,由于A、B不能为空,考虑可行方案下,A、B的长度:A长度为[1,29], B长度为30-A长度
此时进行pow(2,30)次搜索会超出时间限制,可以使用折半搜索达到空间换时间的效果
具体实现是先对数组的前一半进行搜索,并记录相关信息,再对数组后一半进行搜索,判断是否存在前一半的一种方案与后一半的一种方案可以组合出答案
折半搜索时,对于数组长度n,使用n/2位的二进制数来表示A、B中的元素情况
如果前半段和后半段能组合出一种数组A使得平均值为整个数组的平均值all/n,则数组B自然也满足条件
设前半段的长度为j,和为ele,那么有等式(ele+sum)/(j+cnt)==all/n,移项可得ele的表达式:
ele=all(j+cnt)/n-sum,从哈希表中判断是否存在该ele即可,由于除法可能导致整数计算精度不正确,可以使用乘法再验算一遍
1733. 需要教语言的最少人数
贪心+模拟
数据量较小,可以把所有是好友且无法沟通的关系找出来,再枚举教学的语言
940. 不同的子序列 II
枚举
字符串仅由小写英文字母组成,枚举以26个小写字母为结尾的子序列的个数,最后答案就是以26个小写字母为结尾的子序列个数之和
具体实现:每遇到一个字符ch,先统计之前以a~z为结尾的子序列的总数sum,那么现在以ch为结尾的子序列总数应更新为sum+1。
可以在每次遍历时都统计总数sum,也可以把总数sum作为全局变量来维护
928. 尽量减少恶意软件的传播 II
并查集
在924题尽量减少恶意软件的传播I中,只是让某个节点初始时不再是感染状态,并不影响联通分量的状态
本题中,让某个节点被完全删除,可能将原来的联通分量拆分成多个联通分量
对健康节点进行建图,合并,得到多个联通分量
枚举每一个初始感染节点,统计对哪些联通分量进行了感染
对于健康的连通分量,如果它的感染源只有1个,说明它是可被拯救的
再次枚举初始感染节点,计算移除当前初始感染节点后最终感染的节点数,取可以得到最少节点数的感染节点作为答案
902. 最大为 N 的数字组合
数位dp
将n转化为字符数组进行记忆化数位dp计算
当前填的位置不受限制时,才可以返回缓存中的答案,同时算出结果时继续缓存
当前填好的数字处在不合法的状态时,可以跳过当前数位
当前可以填的数字上限取决于当前位置是否受限制(n为数字上限,当上一位数字与n中对应位数字相同时,当前位置就是受限状态)
756. 金字塔转换矩阵
DFS
数据量较小,dfs搜索是否能完成金字塔的构建
把允许的三角形图案进行状态压缩存入哈希表,方便查找合法图案
dfs参数:上一层的图案pre,当前层的图案now,当前层正在构建的下标index
当前层已构造完毕,递归是否可以构造再上一层,出口为上一层的长度为1,代表金字塔顶部已构建完毕
对于当前层的index下标,枚举可以构建的图案,只要存在一种图案最终到达终点则返回true
963. 最小面积矩形 II
枚举
数据量较小,枚举三个点,判断是否存在第四个点与其组成矩阵
1625. 执行操作后字典序最小的字符串
枚举
无法改变字符串的长度,那么字典序越小就是对应的数字越小,分情况枚举答案
如果 b 是偶数,无论轮转多少次,都只能给奇数位的元素做累加操作
如果 b 是奇数,可以给奇数位和偶数位的元素都做加法,且操作次数可以不同
由于加法超过9会变回0,那么可操作次数也是极小的,很快就会恢复到原来的状态
直接枚举操作的次数,维护字典序最小的字符串作为答案
927. 三等分
枚举
先统计arr中1的个数sum,若个数为0则返回任意答案,若个数%3不等于0,说明无法做到
将arr分成三部分,每部分中1的个数partial=sum/3,根据每部分中1的个数寻找分割点
由于第一部分和第二部分首尾的0的划分不确定,但是第三部分的尾部是固定的,用第三部分的值来反推是否可以划分为三部分
1986. 完成任务的最少工作时间段
状态压缩+动态规划
数据量较小,可以使用二进制数表示任务完成的状态
定义dp[mask]表示任务完成状态为mask时需要的最小工作时间段
初始化:
1.先初始化所有dp值为最大值
2.枚举mask,若工作时长之和<=sessionTime,可以在一个工作时间段内完成,dp[mask]初始化为1
状态转移:
对于mask,枚举其子集subset,如果dp[subset]==1,则dp[mask]可由dp[mask^subset]+1转移而来,取最小的dp[mask]
返回答案:dp[(1<
2250. 统计包含每个点的矩形数目
枚举+排序+二分
横坐标最大为1e9,纵坐标最大只有100
首先对矩阵的右上角按横坐标从小到大排序
对矩阵的右上角按纵坐标分组,每个纵坐标对应一个或多个横坐标,存入哈希表中
对于查询点(x,y),纵坐标
2488. 统计中位数为 K 的子数组
前缀和
数组由1到n的不同整数组成,k只出现一次,对于其他数,只考虑与k相比的大小,具体大小不影响答案
中位数为k的子数组满足以下条件的任意一条:
1.包含k,小于k的个数 与 大于k的个数相等
2.包含k,只有一个大于k的元素,没有任何一个小于k的元素
3.只包含k,没有其他任何元素
将小于k的数记作-1,大于k的数记作1,等于k的数记作0,则有:
对于上述的条件1,等价于子数组元素和为0
对于上述的条件2,等价于子数组元素和为1
对于上述的条件3,等价于子数组元素和为0
对于当前的前缀和sum,若之前出现过一个前缀和pre,满足sum-pre=0或sum-pre=1,就可以构成满足题意的子数组,移项可得pre的表达式:pre=sum或pre=sum-1
使用哈希表记录前缀和出现的次数获取答案
注意:
1.需要在包含k时和包含k之后才可以开始记录答案(满足子数组要包含k的条件)
2.在k出现之后,不需要再向哈希表中记录前缀和
765. 情侣牵手
贪心+并查集
对于一对情侣,假设编号为a,b,那么让a,b并肩坐在一起即可,与具体顺序是a,b还是b,a无关,因此可以将同一对情侣编上相同的号,以简化代码
由于座位没有循环,最终0,1下标的座位必然是同一对情侣,而不会出现1,2下标是同一对情侣,因为这样0下标的人无法与Ta的对象牵手
将n对情侣看做图中的n个节点,对于每两个相邻的节点,如果是第 i 对与第 j 对坐在了一起,则在 i 号节点与 j 号节点之间连接一条边,代表需要交换这两对情侣的位置
最终每一个联通分量都是一个环,交换次数为这个联通分量的大小减1
统计所有联通分量的交换次数作为答案
911. 在线选举
预处理+二分查找
预处理每一个时间的最近获胜者,对于查询t,找到<=t时间的获胜者作为答案
1686. 石子游戏 VI
贪心
假设有石子a和石子b,Alice认为价值为a1,b1,Bob认为价值为a2,b2
考虑取石子的方案:
第一种方案:Alice取第一个石子,Bob取第二个石子,此时分数差diff1=a1-b2
第二种方案:Alice取第二个石子,Bob取第一个石子,此时分数差diff2=b1-a2
两种不同方案之差:
diff = diff1 - diff2 = (a1+a2) - (b1+b2)
因此有:
diff>0 第一个方案更优 此时(a1+a2) > (b1+b2)
diff=0 两个方案等价 此时(a1+a2) = (b1+b2)
diff<0 第二个方案更优 此时(a1+a2) < (b1+b2)
对于Alice而言,a石头和b石头哪一个的双方认为价值之和更大,就选择那个石子
因此对于每个人而言,贪心地选择剩余石头中双方认为价值之和更大的石子就是最优策略
将石子价值之和放入优先队列,模拟两人取石子的过程,统计两人分数得到答案
2328. 网格图中递增路径的数目
动态规划+优先队列
定义dp[i][j]表示以(i,j)为终点的路径数目
需要从较小值所在位置开始计算,每个位置都可能从四个方向上的更小值转移而来,记录每个位置的数值大小并放入优先队列,按数值从小到大顺序对每个位置进行计算
2092. 找出知晓秘密的所有专家
BFS
使用哈希表维护目前知道秘密的专家的编号
同一个时间的会议上,对知道秘密的人进行BFS,得到该时间过后有多少人知道秘密
1927. 求和游戏
数学
当问号的个数为零,可直接判断胜负,下面讨论问号个数不为零的情况
结论1:如果问号的个数为奇数,那么Alice一定获胜。
因为Alice是先手操作,那么最后一个问号是由Alice来操作的,Alice可以让前一半数字与后一半数字不相等
结论2:如果问号的个数是偶数,前一半数字和为n0,问号个数为q0,后一半数字和为n1,问号个数为q1,Bob只有在n0-n1=(q1-q0)*9/2成立时必胜
1761. 一个图中连通三元组的最小度数
枚举
数据量较小,建图后枚举可能的三元组(注意剪枝),统计度数,取最小度数作为答案
2448. 使数组相等的最小开销
排序+前缀和+枚举
枚举把数组中所有元素变成nums[i]的开销,使用前缀和+排序快速计算开销,取最少的开销作为答案
将cost[i]理解为nums[i]有cost[i]个即可
1223. 掷骰子模拟
dfs+记忆化
枚举第一次投掷的编号,第二次投掷的编号...最后一次投掷的编号
根据题目数据的大小,开三维数组cache进行记忆化,注意不合法序列的判断
1981. 最小化目标值与所选元素的差
暴力枚举
数据量较小,可以枚举到下标为i的行为止,每行选一个元素能得到的所有可能和值
最后遍历选到最后一行的所有可能和值,维护与目标值的最小绝对差
880. 索引处的解码字符串
模拟
模拟解码字符串的过程,直到解码出至少k个字符,再回头寻找第k个字符,如果回头寻找时遇到数字,将长度除以数字,并将k对长度求余
2333. 最小差值平方和
排序+二分
获取每对nums1[i]和nums2[i]的差值,放入diff数组
k1和k2都可以让diff中最大的一个元素减1,记总共的剩余操作次数为cnt,cnt=k1+k2
对diff进行排序,二分查找得到在提供cnt次操作的情况下,diff中最小的最大差值为maxDiff
得到maxDiff后,计算使最大差值为maxDiff后还有多少剩余次数
在maxDiff的情况下,说明要么剩余次数为0,要么就是其他的maxDiff可以再减1,直到次数被用完,这是由于如果可以把所有的maxDiff缩小,那么maxDiff就会是maxDiff-1了
2477. 到达首都的最少油耗
递归
对于一个节点u,计算出以u为根节点的子树(包括u节点)的节点总数为cnt
因此得到节点u向其父节点p出发需要提供的汽车数量car为Math.ceil(cnt*1.0/seats)
当u不为首都时,u到p的耗油量即为car,ans+=car
2472. 不重叠回文子字符串的最大数目
预处理+动态规划
预处理每个区间[i,j]是否回文
定义dp[i]表示s中前i个字符能选择的子字符串的最大数目
1105. 填充书架
动态规划
定义dp[i]表示放置前i本书需要的最小高度,由于书的放置顺序固定,
对于前i本书,可以枚举[j,i-1]的书放在新一层书架上,前提是[j,i-1]的宽度之和符合题意,那么新增高度为max(height[j],height[i-1])
1354. 多次求和构造目标数组
逆向思维 模拟
target中最大的数字(记下标为maxIndex)是上一轮的sum,因此可以推出在上一轮中下标为maxIndex的元素的值,不断往回推,判断最后能否得到全1数组(数组之和为n,n是数组长度)
当最大元素>其他元素之和时,每一次操作会让最大元素减少(sum-最大元素),需要优化计算使其可以一步得到多次操作后的最大值(使用求余)
1353. 最多可以参加的会议数目
贪心+排序+优先队列
将会议按照开始时间排序,遍历每个时间,将已开始的会议其放入优先队列中
优先队列按照结束时间早的优先,贪心的参加较早结束的会议,参加会议或会议已结束移除队列
909. 蛇梯棋
BFS
注意到n比较小,可以直接BFS得到答案
2547. 拆分数组的最小代价
动态规划
定义dp[i]表示前i个元素拆分的最小代价
2517. 礼盒的最大甜蜜度
排序+二分
二分判断甜蜜度是否可行,取最大可行甜蜜度作为答案
2741. 特别的排列
状态压缩+记忆化dfs
枚举每一个位置填的数字,得到合法完整排列后计入答案
2271. 毯子覆盖的最多白色砖块数
排序+双指针
结论:一定存在至少一种覆盖砖块最多的方案,毯子的左端点位置是在某一段连续瓷砖的第一个
对于离散区间是否覆盖的处理:使用p指针维护覆盖到了哪一片连续瓷砖
2251. 花期内花的数目
排序+优先队列
将花朵按开花时间排序,将人按时间先后排序,遍历每个人的看花时间
开花后将其结束时间放入优先队列,动态获取每个人能看到的花的数目
1210. 穿过迷宫的最少移动次数
BFS
数据量较小,直接BFS
1235. 规划兼职工作
排序+动态规划
将工作按照结束时间排序
定义dp[i]表示前i份工作能得到的最大报酬
枚举做第i份工作或不做第i份工作:
不做第i份工作时,dp[i]=dp[i-1]
做第i份工作时,需要找到前面的一个dp[j],使得第j件工作的结束时间data[j-1][2]<=第i件工作的开始时间data[i-1][0]
由于dp[j]已经是前j件工作的最大报酬了,因此找到这样的j后立即更新dp[i]的值并跳出循环,避免超时
2597. 美丽子集的数目
dfs+回溯
数据量较少,dfs枚举每个位置选或不选
1734. 解码异或后的排列
模拟
注意encoded的长度是n-1,perm的长度是n
1.将encoded中下标为奇数的元素进行异或,得到除perm第一个元素以外其他元素的异或运算结果
2.将1~n元素进行异或,可以得到perm所有元素的异或运算结果
将1和2的结果进行异或,从而得到perm第一个元素的值,perm其他元素的值可根据encoded的计算方式反推得到
1406. 石子游戏 III
动态规划
定义dp[i]表示剩余石子的下标范围是[i,n-1]时当前玩家与对方玩家的得分之差的最大值
1626. 无矛盾的最佳球队
排序+动态规划
对球员按分数进行排序,分数相同按年龄排序
定义dp[i]表示选择下标为i的球员得到的无矛盾球队中最高的得分
由于已排好序,对于dp[i],向前枚举无矛盾的dp[j],取最大的dp[j]+下标为i的球员的分数作为dp[i]的值
895. 最大频率栈
模拟
原始栈拆分成多个栈,使用一个哈希表来维护栈中元素及其出现个数
用一个列表来存多个栈,栈的下标表示出现频率,每次pop时从最后一个栈(频率最高)中取出元素
2772. 使数组中的所有元素都等于零
贪心+差分
遍历数组元素,若当前元素不为0则将当前位置以及后面k-1个位置同时减去当前元素,让当前位置为0
若后面出现负数,返回false。遍历完所有元素,说明可以让数组元素都等于0,返回true
使用差分数组快速进行区间操作
剑指 Offer 51. 数组中的逆序对
归并排序
进行归并排序,将数组分成前半段[left,mid]和后半段[mid+1,right],且这两部分再次递归进行归并,现已各自有序,进行合并
在合并时,若前半段中存在元素nums[i]>后半段中的nums[j],由于前半段已经有序,那么[i,mid]这区间的所有元素也会>后半段中的nums[j],对答案的贡献就是[i,mid]区间的长度mid-i+1
2426. 满足不等式的数对数目
归并排序
对于题目中的条件,移项可得nums1[i]-nums2[i]-diff <= nums1[j]-nums2[j]
对所有的nums1[i]-nums2[i]进行归并排序,合并时进行统计得到答案
1425. 带限制的子序列和
优先队列+动态规划
定义dp[i]表示到下标i为止,恰好选择了nums[i]的最大子序列和
使用优先队列维护往前k个dp值,先按时间先后排序,再按dp值从大到小排序,进行状态转移时取未过期的最大值
在遍历时维护答案,因为最大子序列和不一定出现在dp[n-1]
2262. 字符串的总引力
思维题/贡献法
对于s中的每一个字符ch,可以计算出ch对哪个范围内的子串做出贡献
规定一个子字符串,如果包含多个相同的字符ch,那么只有第一个ch对本子串做出贡献
统计s中每个字符的上一次出现位置pre[ch-'a'],遍历字符串,对于下标为i的ch有:
ch作用的子串范围:最小左端点pre[ch-'a']+1 最大右端点s.length()-1
以i为中心,左端点left和右端点right可任意组合,因此需要相乘,ch对答案的贡献为((long)i-pre[ch-'a'])*((long)s.length()-i)
累加所有ch对答案的贡献,得到最终答案
2136. 全部开花的最早一天
贪心
开花周期长的花优先种植,同时获取每朵花的开花时间,最大的开花时间就是答案
828. 统计子串中的唯一字符
预处理/贡献法
对于下标i的字符ch,预处理向前最近的一个相同字符ch的位置pre和向后最近的一个相同字符ch的位置suf,
可得到ch对哪些范围内的子串做出了贡献,以i为中心,左右端点可两两组合,因此需要相乘
947. 移除最多的同行或同列石头
并查集
根据同行或同列对石头进行合并,最后可以得到多个集合
每一个集合应该是呈网格形的,对于每个集合,可以不断移除石头,直至该集合剩下1个石头
数据量较小,使用双重循环进行合并
1335. 工作计划的最低难度
动态规划
特判:任务个数为n,计划天数为d,当n
由于每天至少完成1个任务,在转移时应注意上一个状态最少完成任务的数量
1140. 石子游戏 II
动态规划
定义dp[i][j]表示区间[i,len-1]先手玩家能得到的最多石子数
当i+2M>=len时,当前玩家可直接取走剩余的所有石子
否则,枚举当前选多少能得到最多石子数
1348. 推文计数
暴力模拟
0 <= endTime - startTime <= 1e4且最多操作1e4次
理论数据量为1e8(时间的遍历每次至少为60,实际数据量更少),可直接暴力模拟
2245. 转角路径的乘积中最多能有几个尾随零
数学+预处理+前缀和
结论1:至少存在一个两端位于数组边界的路径的尾随零数量最多。因为在得到某条存在尾随零且两端未到达数组边界的路径时,可继续向边界选取数字,尾随零的数量只多不少
结论2:至少存在一条尾随零数量最多的路径,且该路径属于转角路径。因为直线路径进行转角,尾随零的数量只多不少
计算优化:尾随零的数量等于路径上所有元素进行质因数分解后因子2的数目与因子5的数目的较小值
枚举所有可能的路径:
1.从上往下走,枚举左拐/右拐
2.从下往上走,枚举左拐/右拐
2116. 判断一个括号字符串是否有效
数学
如果字符串长度为奇数,无论如何也无法有效,返回false
令左括号分数为1,右括号分数为-1,那么对于一个括号字符串s,其有效的条件等价于:
任意前缀的分数均>=0 且 整个字符串的分数为0
1590. 使数组和能被 P 整除
数学+前缀和
如果整个数组可以被p整除,返回0,否则先记下数组元素和%p的值mod
使用哈希表记录每个前缀和及其出现下标,如果两个前缀和的值相差恰好为mod,说明存在一个子数组和为mod:对于整个数组而言,对p求余为mod,减去这个子数组对p求余就恰好为0了
记录前缀和的变量sum也需要不断对p求余,因为要找的mod(子数组和)是通过对p求余得来的,对于sum而言,需要找到前面一个前缀和的值为(sum-mod+p)%p
记录最短的子数组长度作为答案
1201. 丑数 III
容斥原理+二分
[1,i]中,能被a整除的数的个数为i/a
由容斥原理可以求出[1,i]中能被a或b或c整除的数的个数之和count=i/a + i/b + i/c - i/lcmAB - i/lcmBC - i/lcmAC + i/lcmABC
二分查找满足count>=n的最小i即为答案
1371. 每个元音包含偶数次的最长子字符串
哈希表记录前缀状态+状态压缩
对于5个元音字母出现的次数的状态,使用5个2进制位来表示,1代表出现奇数次,0代表出现偶数次
使用哈希表记录最早出现某个状态status的下标,当后面再次出现这个状态status时,说明每个元音字母的奇偶性再次相同,即两个status之间的子字符串满足条件,根据长度维护答案
1751. 最多可以参加的会议数目 II
排序+动态规划+二分查找
按照结束时间排序
定义dp[i][j]表示前i个会议最多参加j个能得到的最大价值
不参加第i个会议时,dp[i][j]=dp[i-1][j]
参加第i个会议,dp[i][j]=max(dp[p][j-1]+value[i]),p是与第i个会议没有冲突的,结束的最晚的一个会议,p通过二分查找得到
2735. 收集巧克力
预处理+枚举
数组长度较小,旋转对每个元素同时生效,可以枚举旋转的次数,设旋转次数为count,则有:
收集下标为i的元素的成本为nums中区间[i,(i+count)%n]的最小值
可以预处理每个区间的最小值,把数组多复制一份,预处理时区间就不需要跨越数组边界
2551. 将珠子放入背包中
排序+贪心
问题等价于需要在数组中切k-1刀,得到k个非空子数组,每个子数组的左右边界的元素之和就是分数
枚举每一个可以切的位置i,切下去可以得到nums[i]和nums[i+1]的分数,对每个位置可以得到的分数进行排序,取最小的前k-1个位置可得到最小分数,取最大的前k-1个位置可得到最大分数,从而得到答案
1553. 吃掉 N 个橘子的最少天数
数学+记忆化搜索
吃掉1个橘子的操作是最少的,每次选择吃二分之一或三分之二的橘子,两个方案取最少天数
1648. 销售价值减少的颜色球
贪心+排序+二分
贪心地每次都卖出剩余个数最多的球,得到最大价值
二分查找一个值,满足给定orders次减少的机会,令数组中的最大值最小,得到这个值记为max
那么最后结果数组中,最大值就只能为max,先把数组中所有大于max的卖到只剩max
如果orders还不为0,保留一个max,对其他max再减1,直到次数被用完
这是由于如果可以把所有的max缩小,那么max就会是max-1了
1856. 子数组最小乘积的最大值
单调栈+前缀和
使用单调栈预处理左边更小值和右边更小值出现的位置
遍历数组,对于每个nums[i]计算出以nums[i]为最小值时的最长子数组的分数,维护最大值作为答案
1224. 最大相等频率
哈希表+模拟+分类讨论
遍历数组,使用一个哈希表统计每个元素及其出现次数,另一个哈希表统计频率以及该频率出现的次数
如果频率只存在一种,如果频率为1或者出现次数为1则满足条件
如果频率只存在两种,如果其中一种是1且出现1次则满足条件,另外如果两个频率相差为1,且较大的频率出现次数为1,也满足条件
如果频率出现两种以上,不满足条件
2467. 树上最大得分和路径
DFS
bob到达0的路径只有一条,从bob出发进行dfs,得到bob经过每个节点的时间
alice从0出发,dfs枚举到达每个叶子节点的得分,维护最大得分作为答案
839. 相似字符串组
并查集
数据量较小,枚举哪些字符串是相似的,使用并查集维护集合数量作为答案
1575. 统计所有可行路径
记忆化搜索dfs
枚举下一步可以到达的城市
注意每次到达finish都可以终止作为一条合法路径,也可以不终止继续行驶
2542. 最大子序列的分数
排序+优先队列
先把nums1和nums2绑定成二维数组data,再按照data[1]的值从大到小对二维数组排序
枚举nums2中的最小值为data[i][1],使用优先队列维护最大的k个nums1的值,动态维护答案
1850. 邻位交换的最小次数
模拟
使用下一个排列算法得到目标妙数的序列,再模拟交换相邻位数字得到目标妙数,同时统计答案
1915. 最美子字符串的数目
前缀和+状态压缩
字符串只由前10个英文字母组成
使用10个二进制位的整数mask存储字母出现的奇偶情况前缀和,0代表偶数次,1代表奇数次
子字符串是连续的字符序列,若下标[left,right]的子字符串至多一个字母出现奇数次,那么有:
1.子字符串所有字母均出现偶数次:下标left处mask与下标right处mask相同
2.子字符串仅一个字母出现奇数次:下标left出mask与下标right处mask仅有一位不同
存储每一个前缀的字符串对应的mask出现的次数,对于每个mask状态枚举前面可以转移而来的前缀,如果出现过就统计次数加到答案中
1654. 到家的最少跳跃次数
BFS
注意每个位置最多可以经过两次,其中一次是前面的位置往后跳得到的,另一次是后面的位置往前跳得到的
题目限制不能连续后跳两次,BFS时应存储方向,避免连续后跳
871. 最低加油次数
贪心+优先队列
判断每个加油站是否能到达,若可以则将该加油站的油量放入优先队列,若不能到达则从优先队列中取出最大的加油量进行使用
直到油量>=0或队列为空,若油量>=0说明该加油站仍可到达,反之无解
1473. 粉刷房子 III
三维动态规划
定义dp[i][j][k]表示[0,i]区间房子全部上色且下标为i的房子的颜色为j且[0,i]被划分为k个区间的最少花费
2662. 前往目标的最小代价
最短路径
建图,求start到target的最短路径
如果特殊路径的cost > 曼哈顿距离,则记为曼哈顿距离
为 起点和终点 到 所有特殊点,以及 特殊路径的两端点 到 其他点,特殊路径的起点 到 特殊路径的终点 添加边
2197. 替换数组中的非互质数
栈
遍历数组,对于nums[i],如果与栈顶元素满足gcd > 1,不断将栈顶元素取出与nums[i]进行合并,nums[i]将变成lcm(nums[i],栈顶元素)
直到栈为空或者当前nums[i]无法再与栈顶元素合并,将nums[i]放入栈顶。最后栈中元素就是答案
1131. 绝对值表达式的最大值
数学
将目标表达式转化为关于i,j的函数|f(i)-f(j)|,对于f(i)可能的情况有以下四种:
arr1[i]+arr2[i]+i
arr1[i]+arr2[i]-i
arr1[i]-arr2[i]+i
arr1[i]-arr2[i]-i
求出四种可能的最大值作为答案
2508. 添加边使所有节点度数都为偶数
分类讨论
建图,统计度数为奇数的点的个数odd.size(),简称size
如果size为0,返回true
如果size为2,且这两个点没有边,可以连一条边后满足条件,返回true
如果size为2,且这两个点有边,如果能找到一个点i与这两个点都没有边,那么添加两条边后满足条件,返回true
如果size为4,将这四个点记为a,b,c,d:
如果a和b没有边 且 c和d没有边 分别添加边后满足条件,返回true
如果a和c没有边 且 b和d没有边 同上
如果a和d没有边 且 b和c没有边 同上
其他情况无法满足条件,返回false
2681. 英雄的力量
排序+数学 贡献法
选取与顺序无关,对数组进行排序
遍历数组,对于每个nums[i],把nums[i]作为最大值,考虑对答案的贡献
推出计算式,对每个nums[i]计算贡献值,累加得到答案
2366. 将数组排序的最少替换次数
贪心+数学
倒序遍历数组,维护目前出现的最小值min
若nums[i]大于min,则将nums[i]进行拆解,拆解后最小值应尽可能大
2106. 摘水果
前缀和+枚举
枚举:
1.先向右走i步,再向左走(k-i)步的情况
2.先向左走i步,再向右走(k-i)步的情况
使用前缀和快速求出某个区间内的水果数量
2156. 查找给定哈希值的子串
数学+大数
对于子串[i,i+k-1]的哈希值,通过哈希表达式比较,发现可由[i+1,i+k-1+1]在 O(1) 时间内计算得出
倒序计算每个长度为k的哈希值,找到目标哈希值则返回答案
计算数字较大,可使用BigInteger防止溢出
801. 使序列递增的最小交换次数
动态规划
只能交换相同下标的元素,题目保证有解
定义dp[i][0]表示到下标i为止让两个数组严格递增,且下标i不进行交换的最小交换次数
定义dp[i][1]表示到下标i为止让两个数组严格递增,且下标i进行交换的最小交换次数
801. 使序列递增的最小交换次数
动态规划
定义dp[i][0]表示到下标i为止,下标i不进行交换的最小交换次数
定义dp[i][1]表示到下标i为止,下标i进行交换的最小交换次数
855. 考场就座
枚举
使用有序集合TreeSet维护有人的座位,编号为n-1的座位需要特判
调用seat时会枚举每两个座位中间的位置,维护离左右有人位置最远的空位置作为答案
493. 翻转对
归并排序+二分
进行归并排序分治处理区间,枚举右部分区间,在左部分区间进行二分查找满足条件的元素个数,累加答案
1770. 执行乘法运算的最大分数
动态规划
定义dp[i][j]表示在nums中取前i个数字和后j个数字能得到的最大分数
初始化:dp[0][0]=0,dp[0][i]由dp[0][i-1]转移而来,dp[i][0]由dp[i-1][0]转移而来
状态转移:dp[i][j]由dp[i-1][j]和dp[i][j-1]转移而来
由于答案可能为负数,ans初始化为Integer.MIN_VALUE 枚举选择前i个,则从后面取m-i个,维护最大分数
1368. 使网格图至少有一条有效路径的最小代价
最短路径
使用dijkstra算法求出起点到终点的最短距离即为答案
对于一个格子在四个方向上的相邻格子,只有grid与对应方向相同时到达该相邻格子的距离为0,其他情况距离为1
2514. 统计同位异构字符串数目
组合计数+小费马定理求逆元
每个单词的排列是独立的,计算出每个单词的总排列数进行相乘得到答案
对于一个单词str[i] 长度为len 排列总数为(len的阶乘)/(26个字母的出现个数的阶乘的累乘) 记分母为a,分子为b
现在需要计算(a/b)%mod,对大数除法求余而言存在精度问题,使用小费马定理求逆元将(a/b)%mod转化为a*pow(b,m-2)%mod
可以使用上述等式的前提有两个
1.mod是质数(本题中mod为1e97,是质数)
2.a不能被m整除(在计算a时,将a不断对m求余可满足这个条件)
343. 整数拆分
数学+贪心
对于正整数n,拆分出来的正整数个数越多,乘积越大
由于k>=2,n为2或n为3时单独讨论
n>=4时,讨论n对3取余的余数mod:
mod为0,将n拆成 n/3 个 3 得到最大乘积
mod为1,将n拆成(n-4)/3 个 3 和 2 个 2 得到最大乘积
mod为2,将n拆成(n-2)/3 个 3 和 1 个 2 得到最大乘积
1808. 好因子的最大数目
数学+贪心
本质上与 343. 整数拆分 大致相同,343这题要求至少拆为两个数,本题可以不拆
给定n,需要构造一个整数num,num可以分解为一些已去重的质因数a1,a2,a3....
设a1有b1个,a2有b2个,a3有b3个.... 需要满足b1+b2+b3....=n
好因子由a1,a2,a3...分别取不同个数得来的,a1,a2,a3....具体取什么值并不重要,只需考虑如何把m拆成一些数b1,b2,b3....
因此答案就是求b1*b2*b3....的最大值,题目等价于拆m使得拆出来的数相乘得到最大值
2607. 使子数组元素和相等
分组+中位数贪心+裴蜀定理
假设数组不循环,对于子数组[i,i+k-1],与子数组[i+1,i+k]的元素总和需要相等,则有:nums[i]=nums[i+k],整个数组被划分为k个组,组内元素值相等
但数组是循环数组,那么有周期n,n为数组长度,同时有周期k,由裴蜀定理可得数组必定有周期gcd(n,k)
因此将数组分为gcd(n,k)组。对于一组元素,要通过操作变成相等,有结论:变成中位数的操作次数最少,分组计算运算次数得到答案
1799. N 次操作后的最大分数和
状态压缩+记忆化搜索
dfs(status,index,nums) status表示已选数字 index表示已选次数
2354. 优质数对的数目
等价转换+枚举
结论:bitCount(x|y)+bitCount(x&y)=bitCount(x)+bitCount(y)
优质数对(x,y)只计一次,因此数组中有多个x和多个y冗余,先对数组进行去重
使用map统计去重后的数组每个元素的bitCount以及出现次数
双重循环枚举bitCount1和bitCount2,满足相加>=k时根据乘法原理统计到答案中
2449. 使数组相似的最少操作次数
排序+贪心
题目保证有解 对数组进行排序 贪心地将nums中较小的数字变成target中较小的数字能得到最少操作次数
由于奇数和偶数不能相互转换,因此需要分为奇、偶两组进行计算
当奇偶性相同时nums[i]与target[i]最少差距为2
对于同奇偶的两个数字,先除4得到操作次数,如果对4求余为2,额外记录差距为2的个数cnt,注意cnt在奇偶两组是共享的,最后加上cnt/2次
843. 猜猜这个单词
启发式极小化极大算法猜测单词
1786. 从第一个节点出发到最后一个节点的受限路径数
最短路径+记忆化dfs
将节点映射到[0,n-1],使用dijkstra算法求出n-1到其他点的最短路径
从0开始dfs搜索到达n-1的受限路径数
1712. 将数组分成三个子数组的方案数
前缀和+二分
使用前缀和快速计算区间总和
将数组拆分为[0,i] [i+1,j] [j,n-1]
固定i,j的取值范围为[i+1,n-2]:
二分查找最小的j满足sum(0,i)<=sum(i+1,j),记最小的j为min
二分查找最大的j满足sum(i+1,j)<=sum(j+1,n-1),记最大的j为max
如果min和max都是合法的,那么ans+=max-min+1
975. 奇偶跳
预处理+记忆化dfs
使用TreeMap
对每个下标进行dfs,判断最终能否到达数组末尾
2560. 打家劫舍 IV
二分
二分查找最小的窃取能力
1643. 第 K 条最小指令
动态规划+组合计数
V>H,对于一个位置index,如果放V,则所有在index位置放H的字符串的字典序都比它小,比它小的字符串的总数有 (组合数:从h+v-1中取h-1个)
使用动态规划来计算组合数,dp[i][j]表示从i中取j个的组合数数量
根据k与dp值的大小依次确定每个位置放V还是放H
1718. 构建字典序最大的可行序列
dfs+回溯
数据量较小,按题意进行dfs+回溯,枚举每个位置填的数
2555. 两个线段获得的最多奖品
滑动窗口+动态规划
定义dp[i]表示下标i之前一条线段可获得的最多奖品数量
维护满足条件的一条线段[left,right],每次得到这样一条线段时,用right-left+1+dp[left]来更新答案
同时dp[right+1]=max(dp[right],right-left+1)
1639. 通过给定词典构造目标字符串的方案数
记忆化dfs
dfs(index,k)表示当前正在寻找target的index下标,当前words中被限制的起始位置为k
index>=target.length()时说明找到一种可行方案 k>=words[0].length()时说明当前状态无解
1847. 最近的房间
排序+有序列表
对房间面积从大到小排序,对查询按至少需要的面积从大到小排序
遍历排序后的查询,把所有满足面积>=当前minSize的房间的id放入有序列表TreeSet中
对每次查询,调用有序列表的floor和ceiling获取距离当前preferred最近的id,检查是否合法,填入本次查询的答案
1494. 并行课程 II
状态压缩+动态规划
对于每个状态i,先枚举状态valid(不包含任何i的前置课程),如果valid课程数<=k,dp[i]可由dp[i ^ valid] + 1转移得到
否则枚举valid的子集sub,dp[i]由最小的dp[i ^ sub] + 1转移得到
2050. 并行课程 III
记忆化dfs
dfs(i)表示完成课程i最少月份数,取最大的dfs(i)作为答案
对于一个课程i,如果没有前置课程,完成时间为time[i]
如果有前置课程,则完成时间为max(dfs(pre))+time[i]
2009. 使数组连续的最少操作数
去重排序+双指针
先对nums进行去重并排序,枚举以每个nums[i]为起点使数组连续的最少操作数
以nums[i]开头,nums[i]+all-1结尾时,使用指针p找出nums中满足值在该区间内的元素个数cnt,操作次数为all-cnt
2267. 检查是否有合法括号字符串路径
记忆化BFS+剪枝
容易想到BFS解法,存储到每个位置时括号的状态,令左括号值为1,右括号值为-1,可能合法的括号字符串始终有值>=0,到达终点时合法的字符串值==0
剪枝:搜索到(i,j)位置时,若剩余格子数目小于当前左右括号之间的差值,后续不可能找到合法括号序列
使用三维数组cache来存储某状态是否已访问过,同一个下标(x,y)同一个字符串值val只访问一次,对应cache[x][y][val]
982. 按位与为零的三元组
枚举
题目需要求nums[i]&nums[j]&nums[k]等于0的三元组个数,可以使用双重循环预处理每个nums[i]&nums[j]的出现次数,存入哈希表中
再枚举nums[k],从哈希表中获取key和value,若nums[k]&key==0,则ans+=value
1192. 查找集群内的关键连接
tarjan算法
模板题
1563. 石子游戏 V
动态规划+前缀和
定义dp[i][j]表示在区间[i,j]的石子中Alice能得到的最大分数
初始化:所有dp[i][i]表示剩余一个石子,游戏结束,Alice得分为0,因此dp[i][i]初始化为0
对每个区间[i,j]枚举割点k,分割为[i,k] [k+1,j] 通过前缀和快速获取两部分的值a,b
如果a 如果a>b dp[i][j]由b+dp[k+1][j]转移而来
如果a==b Alice自行选择,dp[i][j]由max(a+dp[i,k],b+dp[k+1][j])转移而来
2318. 不同骰子序列的数目
动态规划
定义dp[i][j][k]表示满足条件,长度为i,倒数第二个元素为j,最后一个元素为k的序列数目
n为1时,返回6
初始化长度为2的序列数目,枚举j、k,若j!=k且gcd(j,k)==1,则初始化dp[2][j][k]为1
从长度3开始计算到长度n,对于每个长度,枚举后两个位置j、k,枚举上一个排列的后两个位置t、j,dp[i][j][k]由所有合法的dp[i-1][t][j]相加得到
最后累加所有合法的dp[n][j][k]得到答案
2193. 得到回文串的最少操作次数
贪心
数据量较小,可直接模拟交换
解决[left,right]区间变成回文串:对于每个s[left],找到距离右侧最近的相同字符s[i],将s[i]换到s[right]的位置,变成解决[left+1,right-1]子问题
如果没有找到这样的s[i],说明s[i]出现奇数次,模拟将其换到字符串最中间,解决[left+1,right]子问题
注意奇数次的s[i]不能先模拟换到中间,这样做接下来解决[left+1,right-1]子问题时会再次移动该s[i],产生额外开销
因此遇到奇数次s[i]先记录交换到中间所需要的次数,等解决所有问题,即解决[left+1,right]子问题后再将其换到字符串最中间
1383. 最大的团队表现值
排序+优先队列
构造二维数组data同时存每个工程师的速度和效率,将data按效率从高到低排序
遍历data数组,优先队列存放速度最大的前k个工程师的速度,同时使用sum维护优先队列中的速度之和
对于每个i,当data[i][1]为最低效率时,更新ans=max(ans,sum*data[i][1])
2751. 机器人碰撞
排序+栈模拟
对pos进行排序,使用栈进行模拟碰撞
2412. 完成所有交易的初始最少钱数
思维
将交易分成loss亏损,earn盈利两组,有两种情况钱可能最少:
1.进行所有亏损的交易,其中能得到最大cashback的这笔交易的cashback还没得到时
2.进行所有亏损的交易,再进行盈利交易中最大cost的这笔交易,且对应的cashback还没得到时
取两种情况钱最少的情况就是需要的初始最少钱数
1049. 最后一块石头的重量 II
动态规划 01背包
将题目转化为:一个数组nums,元素和为sum,现在需要取出一部分元素与另一部分元素取差,求差的最小值
要得到差的最小值,分出来的两部分值需要尽可能接近target=sum/2
定义dp[i][j]表示从前i个元素中取出一些元素,在元素和不超过j的情况下能得到的最大元素和
最后dp[n][target]就是最接近target的元素和,另一部分为sum-dp[n][target],求两部分的差的绝对值作为答案
630. 课程表 III
贪心+优先队列
对课程按截止时间从小到大排序,优先完成截止时间早的课程
使用优先队列保存完成的每个课程的持续时间duration,同时维护优先队列中所有课程需要的时间总和allTime
当完成课程i(duration_i,lastDay_i)后,allTime > lastDay_i ,则需要从优先队列中取消掉占用时间最大的课程
最后优先队列中课程的数量就是答案
2402. 会议室 III
优先队列+双堆模拟
使用两个优先队列分别保存空闲房间和使用中的房间的编号,模拟安排会议,记录每个房间的使用次数
2444. 统计定界子数组的数目
枚举
遍历数组,维护上一个minK,maxK的位置preMin,preMax以及上一个超出[minK,maxK]范围的元素位置preUnValid
对于下标i,如果当前下标元素合法且preMin,preMax都出现过,则对答案的贡献为min(preMin,preMax)-preUnValid
如果下标i元素非法,重置preMin,preMax为-1,且更新preUnValid=i
1976. 到达目的地的方案数
最短路+优先队列
使用dijkstra算法计算到达终点的最短距离,同时维护以最短路径到达每个节点的方案数
注意:new PriorityQueue<>((o1,o2)->Long.compare(dis[o1],dis[o2]))
当修改了 dis 数组中的值时,优先队列中的元素不会及时更改顺序
在修改dis[v]值后,应remove(v)再add(v)
899. 有序队列
思维
当k=1时,可以不断改变字符串的首字符,将s复制一份,枚举每种情况,取字典序最小的作为答案
当k>1时,可以得到任意顺序的字符串s,对s中的字符排序,得到最小字典序的字符串作为答案
原因:当k>1时,可以选择字符串 s 中的任意两个相邻字母交换它们的顺序,重复这个过程直到得到任意顺序的字符串
778. 水位上升的泳池中游泳
二分+BFS
将题目转化为:一条路径的开销是该路径经过的格子中的最大值,求到达终点的所有路径中的最小开销
二分查找最小开销,BFS判断能否到达终点
172. 阶乘后的零
数学
每一个0都是由2因子和5因子组合得到,因此需要找到所有乘数中的2和5因子的个数,记为a,b
阶乘后的零个数就是min(a,b)个,由于5的因子永远少于2的因子的个数,因此统计因子5的个数即可
793. 阶乘函数后 K 个零
二分+数学
二分查找阶乘后零的个数>=k+1的最小数,记为a
二分查找阶乘后零的个数>=k的最小数,记为b
答案为a-b
1970. 你能穿过矩阵的最后一天
二分+BFS
二分查找能从矩阵第一行到达矩阵最后一行的最后一天
2430. 对字母串可执行的最大删除数
动态规划
预处理出s的每个后缀之间的最长公共前缀长度
定义dp[i]表示[i,n-1]子串可执行的最大删除数,dp[0]作为答案
2812. 找出最安全路径
二分+BFS
二分查找最大的安全系数
2749. 得到整数零需要执行的最少操作数
枚举
枚举操作次数i,i最少为0,最多为60
对于操作次数i,问题转化为target=num1-i*num2是否可以拆分为i个2^t(t可取[0,60])
如果target 否则获取target对应的二进制中1的数量count,如果i>=count则一定可以拆分,返回i
为什么i>=count时一定可以拆分?因为一个2^t 可以拆分为两个2^(t-1) 当i大于count时可以多拆分几次凑出i个2^t
2376. 统计特殊整数(数位dp)
dfs+记忆化搜索
数位dp模板
2842. 统计一个字符串的 k 子序列美丽值最大的数目
贪心+组合数
统计字母出现次数,优先选择出现次数最大的字母组成子序列能得到最大美丽值
对于出现次数为key次的字符,有value个,如果value>k 需要从value中取k个,否则可以全选
若全选 一共value种字符,每种字符都要选一个,而每种字符一共有key个 根据乘法原理 对答案的贡献为pow(key,value)
若从value中选取k个 对答案的贡献为pow(key,k) * comb(value,k) 其中comb函数用来求组合数
1937. 扣分后的最大得分
动态规划
定义dp[i][j]表示到下标为i的行为止且下标为i的行选择下标为j的格子的最大得分
dp[i][j]由dp[i-1][j']得到,分类讨论j'与j的关系
当j'<=j 时,Math.abs(j-j') = j-j' 此时dp[i][j]=max(dp[i-1][j']-Math.abs(j-j'))=max(dp[i-1][j']+j')-j
当j'>j 时,Math.abs(j-j')=j'-j 同理dp[i][j]=max(dp[i-1][j']-j')+j
1906. 查询差绝对值的最小值
前缀和
注意到1 <= nums[i] <= 100,使用pre[][101]记录每个下标位置每个数字的个数
对于查询[left,right],通过pre数组可以获取该区间内每个数字的个数,差绝对值的最小值只可能出现在相邻的数字之间
使用num维护上一个出现过的数字,当j出现的次数不为0时,res=min(res,j-num),最后res即为本次查询的答案
2088. 统计农场中肥沃金字塔的数目
动态规划
定义dp[i][j]表示以格子(i,j)为顶部的最大金字塔高度
先考虑正金字塔的计算:从矩阵最后一行最后一列开始倒着遍历每个格子(i,j)
对于一个格子(i,j)如果是肥沃的,且(i+1,j-1)、(i+1,j)、(i+1,j+1)都是肥沃的,那么dp[i][j]=min(dp下方三个格子)+1
最后累加所有dp值得到所有正金字塔的数量,上下翻转矩阵,再计算一遍dp,得到倒金字塔的数量,正、倒金字塔数量加起来作为答案
2768. 黑格子的数目
哈希表
对于一个黑格子,记录受影响的四个块的左上角下标,存入哈希表中
遍历所有受影响的块,对每个块记录黑格子的数量
2781. 最长合法子字符串的长度
滑动窗口
forbidden中的字符串长度最多为10
使用滑动窗口枚举最长合法字符串
对于某个子串,遍历前10个字符和后10个字符,使用哈希表快速检查是否合法
2616. 最小化数对的最大差值
排序+二分
对数组排序后二分查找最大差值的最小值
2746. 字符串连接删减字母
记忆化dfs
定义dfs(index,left,right)表示:
从下标index开始直到连接完毕,之前字符串的首字符为left,尾字符为right对答案的总贡献
答案:dfs(1,words[0][0],words[0][-1])+len(words[0])
1944. 队列中可以看到的人数
倒序遍历+单调栈
倒序遍历队列,使用高度单调递减栈(存放下标)维护右侧可以看到的人
对于下标i,栈顶元素top,如果h[i]<=h[top],i看不到top右侧的人,跳出循环
如果h[i]>h[top],则i左侧的人也看不到top及top右侧的人,将top出栈
2209. 用地毯覆盖后的最少白色砖块
动态规划
定义dp[i][j] 表示使用i条地毯,覆盖到下标为j的砖块时所剩余的最少白色砖数
使用0条地毯的情况:dp[0][i]=dp[0][i-1]+floor.charAt(i)-'0';
当j 当j>=i*carpetLen时,dp[i][j]=Math.min(dp[i][j-1]+floor.charAt(j)-'0',dp[i-1][j-carpetLen]);
上述转移式中,dp[i][j-1]+floor.charAt(j)-'0'表示对于下标j不铺地毯,后者表示在下标j铺一条地毯,且地毯末尾在下标j
1172. 餐盘栈
模拟+有序集合
使用两个TreeSet分别维护非满栈的下标、非空栈的下标,模拟操作
2584. 分割数组使乘积互质
质因数分解
遍历数组,对每一个元素进行质因数分解,同时记录某个质因数最晚出现的下标
遍历[0,n-1],如果对于[0,i]出现过的质因数的最晚出现下标均<=i,则i为答案
2709. 最大公约数遍历
质因数分解+并查集
特判:出现元素1时,当且仅当数组长度为1时返回true
对每个nums[i]进行质因数分解,假设x为nums[i]的质因数,则合并nums[i]与x,以及nums[i]与nums[i]/x
通过并查集判断所有nums[i]是否位于同一个集合中,若是返回true
1483. 树节点的第 K 个祖先
树上倍增(动态规划)
定义dp[i][j]表示节点i的第2^j个祖先节点
初始化:当j=0时,dp[i][0]表示节点i的第一个祖先,也就是parent[i]
状态转移:枚举祖先2^j,再枚举节点,对于节点i:i的第2^(j+1)个祖先是i的第2^j个祖先的第2^j个祖先
表达式:dp[i][j+1]=dp[ dp[i][j] ][j]
查询祖先:查询第k个祖先时,将k拆分为2的幂相加,例如当k=13时,拆为1+4+8:
从当前节点跳到第1个祖先,接着从这个祖先跳到这个祖先的第4个祖先,最后从现在的祖先跳到现在祖先的第8个祖先完成查询
没有祖先时用-1表示,状态转移时没有祖先的状态将继续转移
1601. 最多可达成的换楼请求数目
状态压缩+dfs+回溯
数据量较小,且每一个请求都是相互独立的,枚举每个请求是否被满足,最后判断状态是否合法,若合法则更新最多同时满足请求的数目
2872. 可以被 K 整除连通块的最大数目
dfs+思维
对于某个节点i,如果i及其所有子节点之和sum满足sum%k==0,则可以将节点i与其父节点连接的边删除
由于是无向树,每个节点都可以认为是根节点,为方便计算,认为0节点是根节点进行dfs得到答案
2561. 重排水果
哈希表+排序+思维
首先用map统计两个果篮中所有元素值及其出现个数,如果某个元素的出现次数v为奇数,则无法使两个果篮在重排后相等,返回-1
否则,一定存在交换方案。
对map中所有元素v除以2,得到在满足题目条件的情况下其中一个果篮的元素值及其个数,再将map与第一个果篮进行比较
从而得出哪些元素需要从第二个果篮换到第一个果篮,哪些元素需要从第一个果篮换到第二个果篮,分别存入to1、to2
对o1和o2排序,o1最小的元素与o2最大的元素进行交换,或者以最小值为中介,通过2*min成本进行交换,统计成本得到答案
312. 戳气球
记忆化搜索+逆向思维
逆向思考,考虑在区间里填充气球,最后填满整个区间。为计算方便,补充下标-1和下标n位置,记其元素值为1
定义dfs(int left,int right)表示将开区间(left,right)填满最多得到的硬币数
对于dfs(left,right)枚举先填充的位置mid,则得到的硬币为nums[left]+nums[mid]+nums[right]+dfs(left,mid)+dfs(mid,right)
返回所有可能的mid中得到硬币最多的作为结果,记忆化dfs函数可以优化时间复杂度
1547. 切棍子的最小成本
记忆化搜索
为cuts添加左侧0和右侧n,存入list
定义dfs(left,right)表示将list中下标left到下标right切割开的最小成本
枚举中间点i,i的范围为[left+1,right-1],切割成本为list.get(right)-list.get(left)+dfs(left,i)+dfs(i,right)
注意当left+1>=right时,无需切割,切割成本为0
对数组执行操作使平方和最大
等价转换
选择两个互不相同的下标 i 和 j ,同时 将 nums[i] 更新为 (nums[i] AND nums[j]) 且将 nums[j] 更新为 (nums[i] OR nums[j]):
等价于把一个数的 0 和另一个数的同一个比特位上的 1 交换。统计每个比特位上1的个数,尽可能凑出1个数最多的k个数将其平方和相加得到答案
2732. 找到矩阵中的好子集
枚举+思维
枚举好子集中的元素个数
好子集中元素个数为1的情况:如果出现某一行i全为0,则[i]是好子集,返回答案
好子集中元素个数为2的情况:如果行i,行j每一列相加<=1,则[i,j]是好子集,返回答案
好子集中元素个数为3的情况:每列相加之和<=1,那么可以去掉其中一行,变成元素个数为2的情况
又由于列最多为5,可以证明好子集中元素个数>=4可以转化为好子集中元素个数为0或1或2的情况
因此只需考虑1和2,都没找到答案则返回空列表
2763. 所有子数组中不平衡数字之和
枚举+哈希计数
固定子数组左端点i,枚举右端点j(j>=i+1,因为长度为1的子数组没有不平衡数字)
每新增一个元素num,子数组的不平衡度count+1
如果子数组中存在num-1,则count--,如果存在num+1,则count--
累加所有子数组的不平衡度count得到答案
2659. 将数组清空
树状数组
使用指针p标记当前数组的第一个元素,问题转化为用指针p从小到大依次遍历数组中的所有元素
使用树状数组维护某个区间已删除的元素个数count,指针p移动到p2时需要减去count
当p2>=p时,将p移动到p2;当p2