20/05/27
LeetCode 974
题目描述
给定一个整数数组 A
,返回其中元素之和可被 K
整除的(连续、非空)子数组的数目。
类别
前缀和
示例:
1 | 输入:A = [4,5,0,-2,-3,1], K = 5 |
数据范围
1 <= A.length <= 30000
-10000 <= A[i] <= 10000
2 <= K <= 10000
分析
首先肯定先暴力想一波,直接两层for循环,sum保存和,然后依次模K进行判断。但是时间复杂度肯定O(N^2),看范围肯定是过不了了,然后再想想其他办法。分析题目,对于子数组求和的问题,肯定先前缀和一波,前缀和存在数组P[]中,注意P[0]=0初始化别忘了(后面会有坑),即前0个元素和为0。然后再解析题目,要求元素之和能被K整除,即(P[j]-P[i])%K==0,到这里还是没有什么突破口,因为这样还是得遍历,时间复杂度肯定上去了。所以就很关键的数学定理出现了,同余定理。即保证P[i]%K==P[j]%K就肯定能保证(P[j]-P[i])%K==0了。接下来就是遍历了,计算每个P[i]%K的值,然后相同的值之间肯定存在子数组符合条件。如果存在多个相同的值,那就可以组合出很多情况,比如如果存在四个P[i]%K的值相同,那肯定就存在6种组合方式组成的子数组,这肯定不难相到排列组合问题。然后就针对这个进行下一步优化,因为要求相同的值的个数,所以可以用Map实现,遍历P数组,P[i]%K的值以及该值的出现次数存入Map中,然后最后遍历Map的value,进行相应的排列组合运算再相加就解决了。
需要注意的地方就是,如果我们最开始没有初始化的话,如果遇到P[i]%K==0的时候,该子数组肯定是满足的,不需要另一个P[j]与其配对,如果没有这个初始化的0,我们算法在统计的时候就会少,这个bug还很难找到。如果不太理解,可以看这个例子:[2,2,1] K=5,我们按照算法来,没有初始化,P数组就为[2,4,5],然后依次%K,得到[2,4,0],然后放入Map中,每个都是1,即最后得到结果为0,但实际上的结果应该是1,子数组[2,2,1]是满足的,所以这就是需要初始化的原因了。还有需要注意的地方就是P[i]是会出现负数的情况的,所以在%K的时候应该加上一部分值使其为正值再取模。
优化
可以用数组代替Map来提高效率降低空间利用率,即因为%K的结果就在0-K-1之间,则可以设定数组times[MAX],初始化为0,MAX取K值,然后每次得到P[i]%K时,就times[P[i]%K]++即可,这样就免去了Map的复杂操作,但是空间复杂度就会高一些,后面的遍历也是一样的道理了。
代码
1 | import java.util.*; |
LeetCode 1
题目描述
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
分析
简单题,就不放代码了,就是依次存入hash表中,也就是存入Map中,value作为键,下标作为值,然后再进行遍历并判断即可。写到这里肯定会问如果有重复的value怎么办,如果重复,那肯定要么这个value的是target的1/2,要么不是组成target的一部分,因为题目保证只有一个解。所以我们考虑这个value的是target的1/2的情况,那hash表内就只存了一个value及其下标,但其实并不影响,因为当遍历到没有存进Map的那个value对应的nums[i]时,进行判断,如果成功就输出当前元素的下标和对应存进Map那个value的下标即可,也同样可以完成题目要求,并不影响。
LeetCode 2
题目描述
给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例
1 | 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) |
分析
首先,第一个思路肯定是每一位挨着挨着求和存数组,然后再把数组每一个元素乘以10相应的幂再相加,就得到了最终需要的数字sum,再把这个数每一位放在另一个链表上,就ok了。这样肯定能做,但需要注意的是sum的范围,可能是很大很大的,所以这里是第一个可以进行改进的地方,我们可以不对其求和,每次得到两个链表某一位上相加得到的数后,直接对其进行处理,即放在链表上。比如num[cnt]+=l1.val+l2.val,然后判断num[cnt]是否>=10,如果>=10,则num[cnt+1]=1;这也是为什么前面使用+=的原因了。这样得到num[cnt]后,再通过new ListNode(num[cnt]%10)来构建当前数的链表。细心的朋友应该发现了,其实根本不必使用num数组,直接用num就可以了,只需要添加一句else num=0,这样就形成了第二个优化。
需要注意的地方就是,l1和l2可能不一样长,所以需要补0.还有就是特殊情况的处理,可能l1和l2的最后一位进位了,同时又退出了循环,所以需要判断一下。
代码
1 | /** |
20/05/30
LeetCode 84
题目描述
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
类别
单调栈
示例
1 | 输入: [2,1,5,6,2,3] |
分析
首先可以想到暴力算法,即固定每个柱子高度,然后枚举矩形的宽度。这样时间复杂度就O(N^2),肯定不是我们所希望的。然后我们考虑优化,因为要找最大的面积,而且柱子的高度是有高有低的,我们可以想到单调栈来完成,如果对单调栈没什么了解的,可以看看这道题后面的一个小题,帮助理解单调栈的用法和好处。然后言归正传,我们就考虑维护一个单调栈,如果遇到比当前栈顶的柱子更高的柱子,就将该柱子入栈,如果遇到了比当前栈顶矮的柱子,那就出栈,直到遇到比当前柱子矮的栈顶,然后再将该柱子入栈,同时在其他柱子出栈的时候需要注意更新相应的最大矩形面积,这里是比较难理解的。比如,当前柱子比栈顶矮,然后需要出栈,然后因为出栈的这个柱子是栈中柱子最高的,所以以这个柱子为顶的矩形面积可以求出,并比较更新相应的最大矩形面积。用一句能够帮助理解的话就是每个柱子出栈的时候,我们都已经计算了以它为顶的矩形的面积的最大值,并对最后结果进行了更新,即不会导致出栈后得到的面积不是最大面积。可以通过看代码来帮助理解。
需要注意的地方是,我们在具体实现的时候,需要在高度数组最后插入一个高度为0或者-1的元素,保证栈中所有元素都能出栈。还有我们栈中保存的实质是柱子对应的下标。然后因为需要对给的高度数组进行操作,我们采用c++来描述。
代码
1 | int largestRectangleArea(vector<int>& height) { |
帮助理解单调栈,[click here][https://zhuanlan.zhihu.com/p/26465701]