ACM算法精华总结 算法从入门到放弃系列笔记
Study Date :2020-01-20
1 算法思想
枚举(Enumerate)是基于现有知识来推断问题的解空间,遍历解空间验证是否符合问题描述的要求,从而对问题求解的思路。
2 解题要点
-
给出解空间
对问题建立数学模型,思考可能会出现的情况,需要枚举哪些要素。 -
缩小解空间
不是解空间中所有的元素都需要枚举出来进行验证,在枚举前应尽可能的缩小解空间,减少时间开销。 -
选择合适的枚举顺序
根据题目判断。比如要求的是最大的符合条件的素数,那自然是从大到小枚举比较合适。
3 例题
以下是一个使用枚举解题与优化枚举范围的例子。
题目
一个数组中的数互不相同,求其中和为0的数对的个数
题解
- 确定解空间
枚举两个数的代码很容易就可以写出来。
int length = array.length,ans=0;
for (int i = 0; i < length; ++i){
for (int j = 0; j < length; ++j){
if (array[i] + array[j] == 0) ++ans;
}
}
- 缩小解空间
原问题的答案由两部分构成:两个数相等的情况和不相等的情况。
由于题目已经说明数组中的元素互不相等且未要求数对的有序性,若数对<a,b>是题目的解,则<b,a>一定是题目的解。那么不妨要求所求的数对<a,b>一定满足a在数组中的位序< b在数组中的位序。
代码如下:
int length = array.length,ans=0;
for (int i = 0; i < length; ++i){
for (int j = 0; j < i; ++j){ // 内层循环不再遍历整个数组,改为遍历外层循环遍历过的元素
if (array[i] + array[j] == 0) ++ans;
}
}
ans<<1; //在最终于结果上*2
接着考虑到数组中每个元素互异,因此对于给定第一个枚举值,数对的另一个值是唯一确定的,因此当内存循环遍历到题目条件符合描述的解时即可跳出内层循环。
int length = array.length,ans=0;
for (int i = 0; i < length; ++i){
for (int j = 0; j < i; ++j){ // 内层循环不再遍历整个数组,改为遍历外层循环遍历过的元素
if (array[i] + array[j] == 0) {
++ans;
break;
}
}
}
ans<<1; //在最终于结果上*2
虽然已经减少了解解空间,但还不是最优的结果。
基于上面当数对其中一个枚举确定时,另一个枚举值的取值也唯一确定的思路,如果能找到一种方法直接判断题目要求的那个数是否存在,就可以省掉枚举后一个数的时间了。
// 1.求数组绝对值最大的数
int absoultMaxValue = Arrays.stream(data).map(Math::abs).max().getAsInt();
// 2.用于查询的boolean数组,注意List的 注意indexOf是通过遍历实现的
boolean[] indexArray = new boolean[absoultMaxValue * 2 + 1];// 注意indexArray需要长度为absoultMaxValue * 2 + 1,+1的原因是因为0坐标
Arrays.stream(data).forEach(d -> {
indexArray[absoultMaxValue + d] = true;
});
// 3.遍历数据数组,根据boolean数组判断每个元素是否存在使其满足条件的第二个枚举存在
int ans = Arrays.stream(data).reduce(0, (acc, item) -> {
if (indexArray[absoultMaxValue + 0 - item]) acc++;
return acc;
});