刷题有术--数组题型技巧汇总

465 阅读3分钟

程序员面试算法题是必考的部分,我们刷LeetCode时,尝尝看完答案恍然大悟,但是下次依旧不会做。

这次总结数组题型中所有可能涉及到的解题技巧。

双指针

参见往期文章 刷题有术--双指针

背包问题

将问题转化为 「子集划分/选择」 问题,然后再次转化为**「 背包问题」。使用动态规划**来解决「子集划分/选择」 问题。

例题:416.分割等和子集

图片

解题关键:
将问题转换为 "挑选数组中的一部分来达成某种条件”。

例题可转化为挑选数组中一部分,使其来和为定值(total_sum/2)。然后数组中的每个元素都有“选”、"不选" 两个选择,可以使用回溯解决,但由于“内容冗余”,采用动态规划。

下面我们来谈谈动态规划

当有冗余存在的时候往往采用动态规划来优化,而怎么能发现是否冗余呢?

有 “公式同” 和 “内容同” 两个方面。

图片

在背包问题会因为 “内容同” 而必须使用动态规划进行优化。如下图中很可能Wn 与 Wn-2 数值是相同的,而造成重复问题,冗余计算。

图片

前缀和数组

通常涉及子数组的时候,可使用前缀和来解决。前缀和数组通常可以配合hashmap 辅助记录。

令 P[i] = nums[0] + nums[1] + .. + nums[i];则每个连续子数组的和sum(j,i) = P[i] - P[j-1]。

例题:560.和为K的子数组个数

图片

解题思路:

在遍历数组的过程中 通过累加就能得到P[i],只要知道满足P[j] = P[i] - k (j<i)条件的j的个数,就知道以i 为结尾的 “和为k子数组” 的个数。

而在遍历过程中,通过hash 记录每个“子数组和” 出现次数,便使得查找满足条件的 j 的时间复杂度变为O(1)。

排序

逆序对个数可以通过归并排序中的merge阶段来统计。只有当右数组当前元素小于左数组当前元素时候才会产生逆序对,逆序对个数为 左数组当前元素(含当前元素)之后的所有元素 即为(mid-i+1)。

单调栈

找数组中左or右侧比某元素 大(或小)的数位置的时候, 可以利用单调栈。

实现过程:要求每个元素都要进过栈。

1)递减栈:从栈底到栈顶元素依次减小。找下一个大的元素,使用递减栈。当栈中某个元素出栈的时候,说明它遇到了第一个大于自己的元素。

  ( 找下一个大的->递减栈)

2)递增栈:从栈底到栈顶元素依次增大。找下一个小的元素,使用递增栈。当栈中某个元素出栈的时候,说明它遇到了第一个小于自己的元素。

( 找下一个小的 ->递增栈)

原地hash

原地hash有两种打标记方法,分别为「乘-1」 和 「加N」。

图片

1. 乘-1。数组中所有元素为正的。如果i 出现了,将nums[i]标记为-1*nums[i],而元素的绝对值是原始元素值。

例题:41.原始元素值。

图片

解题思路:

由于要求使用常量级别空间,所以只能原地hash。

原地hash特点:由于数组长度为N,所以也只能用位置[0, N-1]来标记[1, N] 这N个正整数。

1)第一步:为了能使用“乘-1” 这种打标记手段,开始将数组中小于0的数字全部变为N+1。

2)第二步:遍历数组,使用“乘-1” 进行打标。

3)第三步:遍历打标后的数组,第一个为正数的位置index 代表 index+1 没有出现过。如果数组全为负数,代表[1,N] 都出现过,则返回N+1。

int firstMissingPositive(vector<int>& nums) {        int N = nums.size();        for(int i=0; i<nums.size();i++)        {            if (nums[i]<=0)            {                nums[i] = N+1;            }        }        for (int i = 0; i < nums.size(); i++)        {            if (abs(nums[i])>=1 && abs(nums[i])<=N)            {                if (nums[abs(nums[i])-1] >0)                   nums[abs(nums[i])-1] = -1*nums[abs(nums[i])-1];            }        }        for(int i=0;i<nums.size();i++)        {            if (nums[i]>=0)            return i+1;        }        return N+1;    }

2. 加N。数组中所有元素都在[1,N]中,N是数组长度。如果i出现了,将nums[i-1] 加上N,而nums[i]%N (如果nums[i]%N 是0,则为N)是原始元素值。

例题:448.找到所有数组中消失的数字

图片

解题思路:

由于数组中每个元素都在[1,n] 之间,而数组长度为N,可以使用原数组当hash记录元素是否出现。遍历到某个元素T, 将数组第T个位置的值加上N,代表出现过。

每个元素对N取模代表原始值,如果取模后值为0则原始值为N