找出数组中和为0的三元组

一、题目

给你一个整形数组,数组中和为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("}");
	}
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值