一、题目
给你一个整形数组,数组中和为0的三元组,即三个元素的和为0,并且去重。
二、思路
这是室友去面测试岗位的笔试题。
我对题目的理解是:需要找出任意三个元素的和为0,比如{1,1,-2}和{-2,1,1}实际上是同一个结果,需要过滤重复的,只保留一个。去重我会,但怎么找出所有的这些三元组呢,我当时是想不到思路的,但隐约觉得需要遍历,可能需要循环嵌套多次,可能还需要排序。
我问了室友他当时怎么做的。他说他知道怎么找出三元组,但不知道怎么去重。
他说,他是对数组做三层循环,每层循环取一个数,判断这三个数的和是否为0,这么做就是时间复杂度比较高。但还是能找出三元组的。
我在内心暗暗骂自己,真笨,我自己怎么想不到。
我又上网上看了一下别人的写法。
三、网上有人给出的答案
《求三数之和为零的三元组集合》,这位老兄给出了具体代码。他的思路我没看明白,但我看了代码看出个大概。
四、我自己的思路
结合网上的这篇文章,我又向,终于找到了我自己的思路。
1、对数组排序,正序。
2、遍历数组。新建一个索引数组,记录首个0出现位置,记录最后一个0位置
3、如果数组中有0,以0为界,将数组分成两段,找出前后段互为相反数的两个数。
数组的前段肯定是为负数的数组;
数组的后段肯定是为正数的数组;
嵌套遍历前后两个数组段,从前段和后段分别取一个数字,如果互为相反数,则记录结果。
4、将数组分成两段,前段数组中取得2个数、后段数组中取得1个数;
数组的前段肯定是为负数的数组;
数组的后端肯定是为正数的数组;
前段数组重复嵌套,前段数组中取得2个数;再与后段数组嵌套,后段数组中取得1个数,三个数的和为0,则记录结果。
5、将数组分成两段,前段数组中取得1个数、后段数组中取得2个数;
数组的前段肯定是为负数的数组;
数组的后端肯定是为正数的数组;
后段数组重复嵌套,后段数组中取得2个数;再与前段数组嵌套,前段数组中取得1个数,三个数的和为0,则记录结果。
6、记录的结果拼接成字符串,放入到Set中,再遍历Set取得所有的字符串拆分转换成整形数字就完成了去重。
以上就是我的思路。当然我开始的时候思路也没有那么清晰,就一点一点写了。后边又不从。
写代码的时候需要做一些细节处理: 比如,参数中的数组为空或者长度小于2,这些需要处理;又如数组排序后,如果有连续3个0,那么需要将{0,0,0}添加到结果中;
又如排序后,数组的第0个元素值为0,那么说明数组根本就没有负数;又如排序后,数组的最后1个元素值为0,那么说明数组根本就没有正整数。再比如,数组排序后,前三个元素的值都为0,那么需要添加{0,0,0}作为结果;再比如,数组排序后,最后三个元素的值都为0,那么需要添加{0,0,0}作为结果;
五、代码
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* 给定一个整形数组,设计算法,找出和为0的三个元素组合,去重。
* @author luoch
*/
public class FindThreeElementArray {
public static void main(String[] args) {
// {
// int[] a = {1,2};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = null;
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {-2,-1,0};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {0,0,0};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {32,21,1};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {32,21,1,0};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {100,100,-100,0,200};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {-100,100,-100,0,200};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {-100,100,-100,0,0,0,200};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {-543,345,-100,100,-100,0,0,200,-345,543,456,};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
{
int[] a = {-543,345,-100,100,-100,200,-345,543,456,};
int[][] result = getThreeElentArray(a);
printArray(result);
}
{
int[] a = {-543,345,-100,100,0,-100,200,-345,543,456,};
int[][] result = getThreeElentArray(a);
printArray(result);
}
}
/**
* 找出和为0的三个元素组合(必须是去重数据)。
* 实现方式:1、将数组排序
* 2、在排序后数组中查找是否含有0
* 3、如果含有0,则需要验证
* @return int[][] 满足条件的三元组数组。如果不含有满足条件的数据,返回null
*/
public static int[][] getThreeElentArray(int[] a){
// 验证数组长度
if (a == null || a.length <= 2) {
//throw new Exception("数组长度不合法:数组长度必须大于等于3");
return null;
}
//1、排序
Arrays.sort(a);
// 验证数组内容
if (a[0] > 0 || a[a.length-1] < 0) {
//throw new Exception("数组内容不合法:数组内不含有要求的数据");
return null;
}
//三元组拼接成字符串,比如:{"-2,0,2","-2,1,1"}
Set<String> temp = new HashSet<>();
//2、查找元素0出现的位置:
// 索引0出存放最后一个负数位置,索引1处存放首个0出现位置,
// 索引2出存放最后一个0位置,索引3出存放第一个正数位置
int[] indexOfZeore = find(a);
//3、含0,则要将数组分成两段,找出前后段互为相反数的两个数
if (indexOfZeore[1] != -1) {
//数组不含有负数,那么和为0的三元组只能是[0,0,0]
if (indexOfZeore[1] == 0) {
if (a[1] ==0 && a[2] == 0) {
return new int[][]{{0,0,0}};
} else {
return null;
}
} else if (indexOfZeore[2] == a.length-1) {
//数组不含有大于0的正数,,那么和为0的三元组只能是[0,0,0]
if (a[a.length-2] ==0 && a[a.length-3] == 0) {
return new int[][]{{0,0,0}};
} else {
return null;
}
}
//以0出现在数组的位置将数组分为2段,找出前后段互为相反数的两个数
for (int i = 0; i <= indexOfZeore[0]; i++) {
for (int j = indexOfZeore[3]; j < a.length; j++) {
if (a[i] == -a[j]) {
temp.add(a[i]+",0,"+a[j]);
break;
}
}
}
//连续出现3个0,则加入结果
if (indexOfZeore[2]-indexOfZeore[1]>=2) {
temp.add(0+",0,"+0);
}
}
//4、 负数段取两个数、正数段取一个数,三个数和为0
for (int i = 0; i <= indexOfZeore[0]-1; i++) {
for (int j = i+1; j <= indexOfZeore[0]; j++) {
for (int k = indexOfZeore[3]; k < a.length; k++) {
if (a[i]+a[j]+a[k]==0) {
temp.add(a[i]+","+a[j]+","+a[k]);
break;
}
}
}
}
//5、负数段取一个数、正数段取两个个数,三个数和为0
for (int i = 0; i <= indexOfZeore[0]; i++) {
for(int j = indexOfZeore[3]; j < a.length-1; j++) {
for (int k = j+1; k < a.length; k++) {
if (a[i]+a[j]+a[k]==0) {
temp.add(a[i]+","+a[j]+","+a[k]);
break;
}
}
}
}
//6、将temp转换成最终int[][],即返回结果
if (temp.isEmpty()) {
return null;
}
int[][] result = new int[temp.size()][3];
int index = 0;
for (Iterator iterator = temp.iterator(); iterator.hasNext();) {
String string = (String) iterator.next();
String[] s = string.split(",");
result[index][0] = Integer.parseInt(s[0]);
result[index][1] = Integer.parseInt(s[1]);
result[index][2] = Integer.parseInt(s[2]);
++index;
}
return result;
}
/**
* 以顺序遍历的方式查找key出现在数组a的最初位置和最后位置
* @param a
* @param key
* @return int[] 长度为4的数组,
* 索引0出存放最后一个负数位置,索引1处存放首个0出现位置,
* 索引2出存放最后一个0位置,索引3出存放第一个正数位置
*
*/
private static int[] find(int[] a) {
int[] result = new int[] {-1,-1,-1,-1};
for (int i = 0; i < a.length; i++) {
if (a[i]<0) {
result[0] = i;//标记最后一个负数出现位置
}
if (a[i] == 0) {
if (result[1] == -1) {
result[1] = i;//标记第一个0出现位置
}
result[2] = i;//标记最后一个0出现位置
}
if (a[i] > 0) {
result[3] = i;
break;
}
}
return result;
}
private static void printArray(int[][] a) {
if (a == null) {
System.out.println("null");
return;
}
System.out.print("{");
for (int i = 0; i < a.length; i++) {
System.out.print("{");
for (int j = 0; j < a[i].length; j++) {
System.out.print(a[i][j]);
if (j != a[i].length-1) {
System.out.print(",");
}
}
if (i != a.length-1) {
System.out.print("},");
} else {
System.out.print("}");
}
}
System.out.println("}");
}
}
六、更进一步
在写完以上代码后,我又对自己的代码想了想。晕,我还是想复杂了。三次嵌套数组遍历就可以取得三个数了,再求和判断,OK,这不就行了么,为什么我之前要那么想?之前的代码可能复杂度上要更好一些,但不见得更好。应付一个面试不要把代码写得那么复杂。记住一点:从一个数组中任意取几个元素的组合,就数组嵌套几次。
于是我又改了代码,如下:
package com.luoch.study.algorithm;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* 给定一个整形数组,设计算法,找出和为0的三个元素组合,去重。
* @author luoch
*/
public class FindThreeElementArray {
public static void main(String[] args) {
// {
// int[] a = {1,2};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = null;
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {-2,-1,0};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {0,0,0};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {32,21,1};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {32,21,1,0};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {100,100,-100,0,200};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {-100,100,-100,0,200};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {-100,100,-100,0,0,0,200};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
// {
// int[] a = {-543,345,-100,100,-100,0,0,200,-345,543,456,};
// int[][] result = getThreeElentArray(a);
// printArray(result);
// }
{
int[] a = {-543,345,-100,100,-100,200,-345,543,456,};
int[][] result = getThreeElentArray(a);
printArray(result);
}
{
int[] a = {-543,345,-100,100,0,-100,200,-345,543,456,};
int[][] result = getThreeElentArray(a);
printArray(result);
}
}
/**
* 找出和为0的三个元素组合(必须是去重数据)。
* 实现方式:1、将数组排序
* 2、在排序后数组中查找是否含有0
* 3、如果含有0,则需要验证
* @return int[][] 满足条件的三元组数组。如果不含有满足条件的数据,返回null
*/
public static int[][] getThreeElentArray(int[] a){
// 验证数组长度
if (a == null || a.length <= 2) {
//throw new Exception("数组长度不合法:数组长度必须大于等于3");
return null;
}
// 验证数组内容
if (a[0] > 0 || a[a.length-1] < 0) {
//throw new Exception("数组内容不合法:数组内不含有要求的数据");
return null;
}
//三元组拼接成字符串,比如:{"-2,0,2","-2,1,1"}
Set<String> temp = new HashSet<>();
// 数组的三重嵌套,每次嵌套都是从中取得一个数字
for (int i = 0; i < a.length-2; i++) {
for (int j = i+1; j < a.length-1; j++) {
for (int k = j+1; k < a.length; k++) {
//计算数组中任意三个元素的和,检查值是否为0,如果为0,将数字排序,转成字符串存入结果
if (a[i]+a[j]+a[k] == 0) {
int[] arr = sortInt(new int[] {a[i],a[j],a[k]});
temp.add(arr[0]+","+arr[1]+","+arr[2]);
}
}
}
}
//将temp转换成最终int[][],即返回结果
if (temp.isEmpty()) {
return null;
}
int[][] result = new int[temp.size()][3];
int index = 0;
for (Iterator iterator = temp.iterator(); iterator.hasNext();) {
String string = (String) iterator.next();
String[] s = string.split(",");
result[index][0] = Integer.parseInt(s[0]);
result[index][1] = Integer.parseInt(s[1]);
result[index][2] = Integer.parseInt(s[2]);
++index;
}
return result;
}
public static int[] sortInt(int[] arr) {
if (arr == null || arr.length < 2) {
return arr;
}
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
}
}
}
return arr;
}
private static void printArray(int[][] a) {
if (a == null) {
System.out.println("null");
return;
}
System.out.print("{");
for (int i = 0; i < a.length; i++) {
System.out.print("{");
for (int j = 0; j < a[i].length; j++) {
System.out.print(a[i][j]);
if (j != a[i].length-1) {
System.out.print(",");
}
}
if (i != a.length-1) {
System.out.print("},");
} else {
System.out.print("}");
}
}
System.out.println("}");
}
}