本篇文章是对leetcode array和Top Interview Questions标签下32道array类型题目的总结
leetcode 283. Move Zeroes
此题属于数组交换型,交换数组中元素的位置以满足某种条件
解:交换数组位置,一般使用两个指针的办法,已到达O(1) space 的效果
并且此题有个特殊之处,题目中将数组中的元素分为0项和非0项,也可分为数组分类型,数组分类型的问题,假如有n个类,往往不必全部考虑这n个类,只要将n-1个类处理完,那最后一类自然被处理完。如本题中,只要处理了非0类,那么0类的数自然排到了末尾。
leetcode 169. Majority Element
此题属于数组计数问题,要找到数组中出现次数最多的那个数
解:
- 首先观察本题,发现此题可以从大问题分解为小问题,因此非常适合使用分治法
- 多数投票算法(Boyer-Moore Voting Algorithm)
论文翻译:假设有一群投票的人,每个人都会投票个某个候选人。为了选择最终赢的选取的候选人,可以采用这样的选举方式:每个投票人找到其他的投票人,并且这个投票人支持的候选不同于自己的支持的候选人,PK过后,这一对投票人同时出局。经过全部的PK之后,那么还没有出局的投票人支持的候选人,就有可能是最终的选举胜利者(获得半数以上的选票)。最后,选举主席,需要检查这位可能赢得选举的候选人的票数,来确认他是否赢得了选举。
https://www.cnblogs.com/javanerd/p/6262249.html
因此,数组计数问题可以考虑摩尔投票算法 - 数字数量上的问题,最后会反应到32位上的每一位,因此可以从位操作的角度出发
leetcode 268. Missing Number
此题的特点在于限制了数组中出现数的范围,寻找缺失的那一个
解:
- 根据数组的和与每个数的关系,可以得到缺失数
- 数组位置i及其存储的值nums[i] 之间的关系,因为一个数异或它本身为0,而数组位置0~n-1,数组存储的值包含 0 ~ n,又因为异或具有交换律,所以将数组位置 i 与nums[i]异或,最后的结果便是缺失的数
leetcode 1. Two Sum
寻找数组中是否有特定的数,可以通过hash来减少查找时间
解:
- 使用hash减少查找时间,因为用hash可以达到O(1)的查找时间
leetcode 53. Maximum Subarray
寻找数组中的连续最大和子序列,属于寻找数组区间(不像上一道题是寻找某个数)
解:
- 观察本题,发现此题仍然可以从大问题分解为小问题,因此使用分治法
- 动态规划,区间的问题非常适合动态规划。而且发现在数组中,i 位置的情况是和 i-1情况相关的,因此使用dp
- 贪心, 发现 i 位置只需要依赖 i-1 位置的情况(在 i-1 情况明确时), 可以将动态规划简化为贪心
leetcode 26. Remove Duplicates from Sorted Array
此题特点:数组元素出现重复
解:
- 使用两根指针,一根指非重复数字,一根指向重复数字,并通过while循环跳过重复数组
- 也可看做数组分类问题
Leectode 88. Merge Sorted Array
此题特点:合并问题
解:
- 使用两根指针,合并时从后往前合并,这样就不需要考虑从前往后排时,要将后面的数字统一往后移动一格的问题。正向遍历困难时,通常可以考虑反向遍历
Leetcode 189. Rotate Array
此题特点:数组反转
解:
- 使用循环替代
- 先反转整个数组,然后再翻转前k个元素,再翻转后n-k个元素
leetcode 238. Product of Array Except Self
此题特点:数组区间
解:
- 使用两个动态数组。leftDp[i] 表示i左边的数的乘积,rightDp[i] 表示i右边的数的乘积,那么res[i] = leftDp[i] * leftDp[i]
- 发现 i 只与 i-1/i+1有关,因此可以将动态数组简化为1个变量
leetcode 78. Subsets
得到数组中所有的子集,属于排列组合型
解:
- 回溯法,find里面的循环是寻找第n层递归的起点,依次从前往后添加元素,以避免重复,第n层递归时,添加长度为n的子集。要注意,回溯为了消除前一次的影响,在回溯完之后,要移除回溯前添加的数
leetcode 287. Find the Duplicate Number
此题特点:数组重复
解:
- 检测重复问题,可以使用环检测算法,两根指针
https://blog.csdn.net/xyzxiaoxiong/article/details/78761940 - 二分查找
使用鸽笼原理,当数组内的数<= mid的个数多余mid时,将search范围放在mid~high,
当个数小于等于mid时,将search范围放在low~mid,最后返回low/high(指的是索引)
巧妙的运用二分法
如n=10,mid=5,如果数组内小于等于5的个数多余5个,那么根据鸽笼原理,重复的数必在1~5内
leetcode 48. Rotate Image
特点:属于数组交换问题,交换位置
解:
- 先根据斜对称轴对折,再根据中线y轴对折
leetcode 62. Unique Paths
特点:没有特点
解:
- 很明显的动态规划,数组 位置 i 上的情况依赖于附近位置的情况
Leetcode 289. Game of Life
特点: 根据某个规则,修个整个数组
解:
- 使用辅助数组,记录修改之前的情况
- 不适用辅助数组,在原位置做标记,标记该位置做了修改或者需要修改,要注意做的标记不会影响到逻辑判断
leetcode 11. Container With Most Water
特点:寻找符合要求的数组区间
解:
- 使用两根指针,思考逻辑链:算面积->需要知道长宽-> 宽的话取决于两边短的一边-> 两边-> 两个指针法
每次移动短的那一段,因为如果移动大的,那么在另一端不变的情况下,水量可能会变小,首先宽变小一位,而高度始终是以小的算的。相对来说移动小的,在宽度变小的情况下,更容易获得更大的面积
leetcode 75. Sort Colors
特点: 数组交换,数组分类问题
解:
- 将数组分为三类0类、1类、2类,对0和1两类进行交换,当交换完成时,2类也自动完成
需要在数组上交换而不引入额外空间-> 两根指针
leetcode 105. Construct Binary Tree from Preorder and Inorder Traversal
特点:并无什么特点
leetcode 79. Word Search
解:
- 回溯法,记录访问轨迹,注意在这个分支回溯完成后,需要把访问数组中该位置置回原来的值
- 也可以不适用辅助数组记录访问,可以访问时将本位置的数标记,置为不会影响逻辑的其他数字,回溯完成时重新置回原来的数,这里有一个问题,难道不记录原来的数是什么吗?使用异或的性质,一个数异或本身为0, 设原来的数为A,回溯前A^256, 回溯后A ^ 256 就变回原来的A了
leetcode 73. Set Matrix Zeroes
特点:根据某项规则,修改整个数组
解:
- 使用辅助数组
- 进行标记,但要注意,这里标记如果原位置标记的话,会对后面的判断造成影响,所以对行首/列首进行标记。这样只需要对行首/列首单独处理即可
leetcode 162. Find Peak Element
寻找数组中是否有特定的数,使用二分查找
解:
- 线性扫描,遍历每一个数,比较是否左边的数要低一点,右边的数要高一点。当然,如果发现右边的数要低一点,就没有必要再看右边的数是否符合,直接看右边的数的下一个数
- 这样的题使用二分查找,一开始还是很惊奇的,因为印象中都是对有序数组才使用二分法,但这是对二分法的误解,要理解二分法的精髓在于在每一步都减少待搜索的区间
考虑本题,一个数mid根据和其右边的数进行比较,可以得到该数是在下降的斜坡上还是在上升的斜坡,如果右边的数更小,可以认为是在下降的斜坡上,那么峰值一定是在mid的左边(包含其本身);如果右边的数更大,可以认为是在上升的斜坡上,那么峰值一定是在mid的右边,所以搜索范围可以限制在mid及其右边的子数组
当然,比如右边的数比mid更大,是在上升的斜坡中,将舍弃mid左边的子数组中也可能存在peak元素,但本题不是求最大的数,本题只要求找到一个peak即可,甚至边界值(i=0,i=n-1)也算peak,所以才可以这样使用二分法
使用二分法减少搜索空间
leetcode 56. Merge Intervals
特点:数组区间问题,排序处理
解:将每个区间进行排序,按照start进行排序,start小的排在前面,遍历排序后的intervals,如果第n个interval的end < 第n+1个的interval的start,那么就将第n个interval当作一个新的interval加入结果中。如果第n个interval的end >= 第n+1个的interval的start,那么就进行合并,end取两个区间中最大那一个
leetcode 34. Find First and Last Position of Element in Sorted Array
特定: 寻找数组中特定的数,使用二分查找
解:
- 遍历数组找到最大最小
- 使用二分查找,减小搜索空间。
按照正常binary search首先在mid处找到一个target,因为数组已经排序好了,如果还有其他的target,那一定是在target的左边还有右边,如果有,那就分别以mid-1作为最右边界/mid+1作为最左边界继续查找,得到position更小/更大的数
leetcode 33.Search in Rotated Sorted Array
特点:寻找数组中特定的数,使用二分查找
解:
- 观察题目发现,因为数组被旋转了,所以可以确定,左边被旋转的那一部分的第一个数比右边被旋转的部分都大,右边被旋转的最后一个数也是比左边被旋转的所有数都小,所以每次二分查找时,判断mid的左边和右边是否正常,比较target和nums[start]的大小关系,如果nums[start] 比 target大,说明target应该位于mid的右边
leetcode 55. Jump Game
特点:
解:
- 自顶向下动态规划,从method 1中我们可以看出,有些位置会重复遍历,因此我们引入记忆化搜索,还会回溯,但每到一个位置i时,先查看该位置是否是能够到达终点的位置(GOOD), 还是不能到达终点的位置(BAD),如果是不知道的位置(UNKNOWN),则遍历这个位置
通过使用dp降低了复杂度,存储了index i是否能到达终点的结果,当再经过这些index的时候不再需要计算! - 从method 3 中再次得到启发,如果在位置i,且i~nums[i]+i这之中有一个GOOD INDEX,那么代表位置i也是个GOOD INDEX
遍历完如果lastPos = 0 则表示起点也是good Index,那么从起点也可以跳到终点 - 当方法超时的时候)发现是不是有些步骤被重复计算,如果有重复计算,那么就引入动态规划
- 反向思考,问的是从起点到终点,如果能从终点到起点,结果也是等价的
leetcode 54. Spiral Matrix
特点:遍历数组
解:
- method1 中 if和else过多过于繁杂,观察题目我们可以发现,遍历的方向是有顺序的,永远是往右,往下,往左,往上。因此,我们可以通过加法+求余来得到下一个方向,因此,也能够知道x,y该加还是该减,还是该不变
dr 表示row上的操作,dc 表示在column上的操作,当往左或往右时,行数不变,因此dr[0]/dr[2]都为0
如果遍历的方向是有规律的,是有循环的,那么可以使用加法和求余来控制方向!
leetcode 152. Maximum Product Subarray
特定:数组区间型
解:
- 单纯用Maximum Sum Subarray的方法来做本道题是错的,问题就在于dp[1]可能不仅和dp[i-1]有关,甚至和dp[i-2]有关,例如负,正,负的情况,在这样的情况下,dp不仅需要记录以i-1结尾的最大积,也需要记录以i-1结尾的最小积,因为如果异数相乘,就变成了一个负数
- 使用一个变量代替数组,当动态规划数组中,dp[i]只与dp[i-1]相关,那么这个时候可以用一个变量代替dp数组
- 动态规划中弄清楚,当前位置的数究竟和附近哪几个位置有关
leetcode 15. 3Sum
特点:寻找数组中特定的数
解:
- 3sum问题转换为2sum问题,然后再用2sum的办法解决。这里使用两个指针的方
- 在使用两根指针的时候,考虑一下是否需要排序,让指针的移动更具有方向性
- 当处理去除重复性问题时,既可以使用set自动去重,也可以进行排序,当数重复的时候便跳过
leetcode 42. Trapping Rain Water
特定: 和leetcode 11一样,着眼于每个位置i能够积累多少水
解:
- 先找到数组中bar高度最高中最后一个的索引(因为可能存在多个高度一样的bar)
还是以每个位置i上能积累多少水量的思想计算,从左往右遍历时,每次遍历到位置i,只要在i+1~maxIndex这片区域中有连续的小于height[i]的bar(j),就证明这个bar可以积累水,并且积水量等于height[i]-height[j]
从右往左也是相同的
为了避免重复计算,会跳过已经计算过水的索引
leetcode 128. Longest Consecutive Sequence
特点:寻找数组区间
解:
- 滑动窗口 利用set的特性,不断地remove
使用一个set,对每一个数,检查是否有它的前继和后继,如果有则推进,感觉相当于滑动窗口的变种,使用remove方法可以减少遍历的数据量
leetcode 84. Largest Rectangle in Histogram
特点:数组区间问题
解:
- 分治法,RMQ思想,区间最值
http://dongxicheng.org/structure/lca-rmq/
https://blog.csdn.net/qq_41311604/article/details/79900893 - 等正式复习完补充
leetcode 41. First Missing Positive
leetcode 4. Median of Two Sorted Arrays
问题特点:
- 数组交换
283、48 - 数组分类
26、 - 数组计数
169、 - 限制数组中出现数的范围
268、 - 数组区间
53、238、11、56、152、128、42、84 - 数组元素重复
26、287 - 合并问题
88、 - 排列组合
78、 - 根据某个规则,修改整个数组
289、73
启示(array类型问题可以采用的方法):
- 两根指针
主要是应对数组交换、分类等问题,将space cost限制在O(1)
如果是重复为题,通过while循环跳过重复数组 - 如果观察问题,发现问题可以分解为小的问题,采用分治法
- 多数投票算法
- 使用位操作解决数字计数问题
- 寻找数组中是否有特定的数,可以通过hash来减少查找时间
- 如果数组位置i及其存储的值nums[i] 有对应关系,可以考虑异或
- (数组区间)如果发现数组i位置的情况是和 依赖于附近位置情况,那么可以使用动态规划
- 如果动态规划中, 发现 i 位置只需要依赖 i-1 位置的情况(在 i-1 情况明确时), 可以将动态规划简化为贪心
- 数组反转
(1)使用循环代替
(2)先反转整个数组,然后再翻转前k个元素,再翻转后n-k个元素 - 排列组合型,使用回溯法,find里面的循环是寻找第n层递归的起点,依次从前往后添加元素,以避免重复,第n层递归时,添加长度为n的子集。要注意,回溯为了消除前一次的影响,在回溯完之后,要移除回溯前添加的数
- 检测重复问题,也可以使用两根指针,使用环检测算法
- 二分查找,查找数组中的某个数,可以使用二分查找减少数组的搜索空间,不一定要求数组是有序的,只要能够按照二分查找的思想,每次舍弃一半的无用的查找空间
(1)leetcode 287
使用鸽笼原理,当数组内的数<= mid的个数多余mid时,将search范围放在mid~high,
当个数小于等于mid时,将search范围放在low~mid,最后返回low/high(指的是索引)
巧妙的运用二分法
如n=10,mid=5,如果数组内小于等于5的个数多余5个,那么根据鸽笼原理,重复的数必在1~5内
(2)leetcode 162
这样的题使用二分查找,一开始还是很惊奇的,因为印象中都是对有序数组才使用二分法,但这是对二分法的误解,要理解二分法的精髓在于在每一步都减少待搜索的区间
考虑本题,一个数mid根据和其右边的数进行比较,可以得到该数是在下降的斜坡上还是在上升的斜坡,如果右边的数更小,可以认为是在下降的斜坡上,那么峰值一定是在mid的左边(包含其本身);如果右边的数更大,可以认为是在上升的斜坡上,那么峰值一定是在mid的右边,所以搜索范围可以限制在mid及其右边的子数组
(3)leetcode 34
按照正常binary search首先在mid处找到一个target,因为数组已经排序好了,如果还有其他的target,那一定是在target的左边还有右边,如果有,那就分别以mid-1作为最右边界/mid+1作为最左边界继续查找,得到position更小/更大的数
(4)leetcode 33
观察题目发现,因为数组被旋转了,所以可以确定,左边被旋转的那一部分的第一个数比右边被旋转的部分都大,右边被旋转的最后一个数也是比左边被旋转的所有数都小,所以每次二分查找时,判断mid的左边和右边是否正常,比较target和nums[start]的大小关系,如果nums[start] 比 target大,说明target应该位于mid的右边 - 回溯,根据某个规则,修改整个数组
(1)可以使用辅助数组
(2)不使用辅助数组,在原位置上做标记,要注意做的标记不会影响到逻辑判断
(3) 如果是在回溯中做标记,为了不记忆原来的数,使用异或的性质,一个数异或本身为0, 设原来的数为A,回溯前A^256, 回溯后A ^ 256 就变回原来的A了
(4)进行标记时要注意 这里标记如果原位置标记的话,会对后面的判断造成影响,所以对行首/列首进行标记。这样只需要对行首/列首单独处理即可 - 先对数组排序再处理,往往能减少复杂度
- 当方法超时的时候)发现是不是有些步骤被重复计算,如果有重复计算,那么就引入动态规划
- 反向遍历,反向思考,问的是从起点到终点,如果能从终点到起点,结果也是等价的。
- 使用数组来控制遍历的方向
遍历的方向是有顺序的,永远是往右,往下,往左,往上。因此,我们可以通过加法+求余来得到下一个方向,因此,也能够知道x,y该加还是该减,还是该不变
dr 表示row上的操作,dc 表示在column上的操作,当往左或往右时,行数不变,因此dr[0]/dr[2]都为0
如果遍历的方向是有规律的,是有循环的,那么可以使用加法和求余来控制方向! - 动态规划中弄清楚,当前位置的数究竟和附近哪几个位置有关
- 在使用两根指针的时候,考虑一下是否需要排序,让指针的移动更具有方向性
- 当处理去除重复性问题时,既可以使用set自动去重,也可以进行排序,当数重复的时候便跳过
- 对于数组区间问题,可以考虑使用滑动窗口问题,同时通过hash减少查找时间,将查找时间限制在O(1)
简略版:
- 两根指针
- 二分查找
- 分治法
- 回溯
- 动态规划
- 贪心
- hash减少查找时间(set/map)
- 位操作 异或
- 多数投票算法
- 环检测算法
- 环检测算法
- 反向遍历
- 排序
- 数组控制遍历方向
- 滑动窗口