这是一道算法面试题,百度找到的答案不多,就算能找到也只是给出其实现代码,没把其原理讲清楚,分享一下我对这道题的看法。
一、分析
题目是:
输入一个int类型的值N,构造一个长度为N的数组arr并返回,要求:对任意的i < k < j,都满足arr[i] + arr[j] != arr[k] * 2。
既然要任意的i < k < j,就说明最少要有三个元素,那么就先可以定义一个满足这条件的数组
int[] data = new int[]{1,3,2};
该数组满足题目的要求,但是题目要求的输入一个N,生成长度为N的数组,也就是说生成的数组长度不是固定好了的,那么我们可以对已经满足条件的数组进行变换,写出一套算法,让其生成的任意长度的数组都满足该要求。
二、算法
如何让一个满足条件的数组变换出更多满足条件的数组呢?
①元素值加减
在数学中,不等式两边加上一个共同的数不等式符号不变
//已知
arr[i] + arr[j] != arr[k] * 2;
//给每个元素加上n,就变成了
arr[i]+n+ arr[j] +n!= (arr[k]+n) * 2;
//稍加变换等于
arr[i]+ arr[j] +2n!= 2arr[k]+n) * 2n;
两边都加了一个2n不等式是依旧成立,同理,减去一个n也照样成立。
②元素值乘正数
还有一点,不等式两边乘以一个正数,不等式符号不变。
//已知
arr[i] + arr[j] != arr[k] * 2;
//给每个元素乘n(n>0)就变成了
arr[i]*n+ arr[j] *n!=(arr[k]*n)*2;
//稍加变换等于
n*(arr[i]+ arr[j])!= arr[k] *2n;
两边同乘n,不等式依旧成立。
③取奇偶值
这个方法挺奇妙的,意思是取出数组元素,然后将元素对应的奇数或者偶数写入新数组。
我们知道,已知一个数n,
第n个偶数为:2×n (我们让2为第一个偶数)
第n个奇数为:2×(n-1)+1
下面开始证明
//已知
arr[i] + arr[j] != arr[k] * 2;
//取偶数就变成了
2*arr[i]+ 2*arr[j] !=(arr[k]*2)*2;
//稍加变换等于
2*(arr[i]+ arr[j])!= arr[k] *2*2;
使用取偶数的方法生成的新数组等于两边同乘2,依旧满足要求。
//已知
arr[i] + arr[j] != arr[k] * 2;
//取奇数就变成了
2*(arr[i]-1)+1+ 2*(arr[j]-1)+1 !=((arr[k]-1)*2+1)*2;
//稍加变换等于
2*(arr[i]+ arr[j])-2!= arr[k] *2*2-2;
相当于给不等式两边先乘2,再共同减2,所以取奇数的方法生成的新数组依旧满足要求。
上述方法生成的元素个数是不变的,那么怎么扩大数组呢?
方法就是:两个变换方法一起使用将生成的两个新数组进行合并,如果长度大于要求的长度就减掉一部分
①加法减法一起使用
在数组:1 3 2 的基础上,新数组a为:2 4 3,新数组b为:0 2 1
两个数组合并:2 4 3 0 2 1
可以看出 4 + 2 = 3×2
不满足要求,该方法pass。
②乘法加法一起用
在数组:1 3 2 的基础上,生成长度为4的数组,新数组a为:2 4 3,新数组b为:2 6 4
两个数组合:2 4 3 2
可以看出 4 + 2 = 3×2
不满足要求,该方法pass。
其他方法除了取奇偶方法合用外生成的数组都无法满足条件,读者可自行验证,这里不再赘述。
为什么取奇偶数的方法能成功呢?
其实想一想,合并之后,i、j、k取值分为三种情况:
- i和j取值都在奇数这边,毫无疑问不等式肯定是成立的 ,因为本身没合并前就成立;
- i和j取值都在偶数这边,同上这也肯定是成立的;
- i取在奇数,j取在偶数,arr[i] + arr[j]必定为奇数,而 2*arr[k]必定为偶数,所以不等式照样成立!
三、代码实现
上述讨论的时候,起始数组为1 3 2,其实该数组也是通过变换得来的,首先获得一个基本数组a[1] = {1},通过奇偶取值变换获得新数组a[2] = {1,2}(长度为偶数时偶数部分和奇数部分都取相同个数的元素),再次通过奇偶取值变换获得新数组a[3] = {1,3,2}(长度为奇数时奇数部分个数大于偶数部分),通过此方法而来的。
因此可以看出,如果要获取长度为N且符合条件的数组,就得利用递归思想,从只有一个元素开始,直到长度为N。
给出示例代码(java):
public class one {
public static int[] GetData(int size) {
int o, j;
if (size == 1) {
return new int[]{1};
}
j = (size + 1) / 2;
int[] temp = GetData(j);
o = size - j;
int[] js = new int[j];//奇数
int[] os = new int[o];//偶数
for (int i = 0; i < j; i++) {
js[i] = 2*(temp[i]-1)+1;
}
for (int i = 0;i < o; i++) {
os[i] = 2*temp[i];
}
int[] data = new int[size];
for (int i = 0, m = 0, n = 0; i < size; i++) {
if (i < j)
data[i] = js[m++];
else
data[i] = os[n++];
}
return data;
}
//检验函数
public static boolean JudgeData(int[] data) {
for (int i = 0; i < data.length; i++)
for (int j = i + 1; j < data.length; j++)
for (int k = j + 1; k < data.length; k++)
if (data[i] + data[k] == 2 * data[j]) {
System.out.println(data[i] + " " + data[k] + " " + data[j]);
return false;
}
return true;
}
public static void main(String[] args) {
int i = 0;
while(i<=100) {
int[] data = GetData(i++);
if (JudgeData(data)) {
System.out.println("构造成功!");
} else {
System.out.println("构造失败!");
}
}
}
}
注:因为main函数为静态函数,静态函数无法调用其他非静态函数,所以其他方法也都设置成了静态方法,并无其他意义。
顺便给出C代码:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int one[] = {1};
int* GetData(int size) {
int o,j;
if (size == 1) {
return one;
}
j = (size + 1) / 2;
int *temp = GetData(j);
o = size - j;
int *js = (int *)malloc(j);//奇数
int *os = (int *)malloc(o);//偶数
int i,m,n;
for ( i = 0; i < j; i++) {
js[i] = 2*(temp[i]-1)+1;
}
for (i = 0;i < o; i++) {
os[i] = 2*temp[i];
}
int *data = (int *)malloc(size);
for (i = 0, m = 0, n = 0; i < size; i++) {
if (i < j)
data[i] = js[m++];
else
data[i] = os[n++];
}
return data;
}
int JudgeData(int* data) {
for (int i = 0; i < sizeof(data); i++)
for (int j = i + 1; j < sizeof(data); j++)
for (int k = j + 1; k < sizeof(data); k++)
if (data[i] + data[k] == 2 * data[j]) {
return 0;
}
return 1;
}
int main(void)
{
int i = 1;
while(i<6)
{
int *data = GetData(i++);
if (JudgeData(data) == 1) {
printf("构造成功!\n");
} else {
printf("构造失败!\n");
}
}
return 0;
}
注:因为c每次都要给数组主动开辟空间,太多了卡死了,所以只试了1-5。
到此结束!