更详细的讲解和代码调试演示过程,请参看视频
如何进入google,算法面试技能全面提升指南
假设A是一个整数数组,长度为n,数组中的元素可能是重复的。设计一个算法,找到一系列下标的集合I = {i(0), i(1), i(2)….i(n)}. 使得(A[i(0)] + A[i(1)] + … A[i(n)] ) mod n = 0.例如假定A = {711, 704, 427, 995, 334, 62, 763, 98, 733, 721}, 于是I = {0,1,3}, 因为(A[0] + A[1] + A[3] ) mod 10 = 0.
请给出一个有效的算法找到满足条件的集合I, 无论A的元素如何取值,这样的集合总是存在的。
这是一道较为复杂的算法题,能在一个小时内完成绝非易事。我们需要解决两个问题,第一需要证明,为何这样的集合肯定存在,第二,集合存在,如何把它给挖掘出来。我们先证明第一个问题。
1:如果数组A中含有元素它的值是0,那么满足条件的集合存在,这个集合就只包含元素0.
2:如果数组A只有一个元素,也就是A的长度只有1,那么A本身就是满足条件的集合。
3:如果A包含不只一个元素,我们用归纳法来证明。假设n=k时,我们能从A中找到给定集合,使得(A[i(0)] + A[i(1)] + … A[i(n)] ) mod n = 0,那么当n=k+1时,我们从数组A中任意取出一个元素,如果被拿走的这个元素值是1,那么根据归纳法,我们可以从剩下的k个元素中,找到一个子集,子集中的元素之和能够整除k, 把子集中的元素加上拿走的那个值为1的元素形成新的集合,这个集合之和能够整除k+1.
如果拿走的元素值为t > 1, 剩下的元素个数为k, 那么肯定有 k > k+ 1 - t。我们从剩下的k 个元素中,随便选出 k+1-t 个元素,在这些元素中,根据归纳法,肯定存在一个子集,使得子集元素的和能整除 k + 1 - t. 把这些子集的元素加上前面拿走的元素,那么所形成的新集合,一定能整除 k + 1
综上,我们就证明了,这样的集合是一定存在的,接下来我们需要考虑的是,如何找到这个集合。
我们看看,把元素逐个加总后对n求余会有什么性质:
sum = (A[0] + A[1] + .... + A[j]) mod n
j 由0一直增加到n-1.
在这个过程中,一定会出现下面这种情况:
存在一个 i, 0 <= i < j, 使得 (A[0]+A[1]+...+A[i]) mod n = (A[0]+A[1] + ...A[j]) mod n. (*)
当上面情况出现时,我们有(A[i+1] + A[i+2] +... + A[j]) mod n = 0;
于是(A[i+1], ... ,A[j]) 就是满足条件的子集。为何(*)所描述的情况一定会发生呢?我们知道,对某个数求余,所得结果必然小于该数,因此对n求余,那么结果一定落入到1和n-1之间。
sum = (A[0] + A[1] + .... + A[j]) mod n
j 由0一直增加到n-1, 也就是上面的计算最多会进行n 次,得到n个求余的结果,然而求余的结果最多只可能有n-1中不同情况,那么n个结果中,肯定得有出现重复的时候,一旦出现重复的时候,(*)所描述的情况就出现了。
根据上面算法原理,我们可以实现如下代码:
public ArrayList moduleSubSet(int[] A) {
int[] B = new int[A.length];
for (int i = 0; i < B.length; i++) {
B[i] = 0;
}
int sum = 0;
ArrayList subSet = new ArrayList();
for (int i = 0; i < A.length; i++) {
sum += A[i];
subSet.add(i);
int t = sum % A.length;
if (t == 0) {
return subSet;
}
int pre_sum = 0;
if (t != 0) {
if (B[t] != 0) {
for (int j = 0; j < i; j++) {
pre_sum += A[j];
if (pre_sum % A.length != t) {
subSet.remove((Integer)j);
} else {
subSet.remove((Integer)j);
return subSet;
}
}
}
B[t] = 1;
}
}
return null;
}
在for循环中,我们把数组A的元素逐个相加,然后对长度n求余,所得结果记为t,然后拿这个结果到另一个校验数组B中检验一下,如果B[t] 的值是1,这表明(*)所描述的情况出现了,此时我们再遍历以便(A[0]....A[j]) 把i找出来,找到i的办法就是再次将元素逐个相加,当所得结果对n求余后,与前面算得的t相等,那么此时的元素下标i满足条件,找到i之后,再把下标0到i从集合中去除,那么集合中剩下的下标就是对应自己中的元素在原数组中的下标。
我们再看看主入口函数的实现:
public static void main(String[] args) {
ArrayAndString as = new ArrayAndString();
int[] A = new int[]{1,1,1,3};
//int[] A = new int[]{711, 704, 427, 995, 334, 62, 763, 98, 733, 721};
ArrayList subSet = as.moduleSubSet(A);
System.out.println("sub set is: ");
for (int i = 0; i < subSet.size(); i++) {
System.out.print(subSet.get(i) + " ");
}
}
上面代码运行后结果为:
sub set is:
1 2 3 4
也就是说(A[1] + A[2] + A[3] + A[4]) mod 10 = 0, 大家可以自己检测一下,所得结果确实是正确的。
更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号: