leetcode_三数之和

三数之和跳转

暴力破解O(n³)不做说明

leetcode也过不去
在这里插入图片描述

改进后的暴力破解

本人一番改进,最后能过去了,但是速度感人
在这里插入图片描述

	public List<List<Integer>> threeSum1(int[] nums) {
	//数组排序
		Arrays.sort(nums);
		int len = nums.length;
		int top = len - 1;
		Set<List<Integer>> list = new HashSet<List<Integer>>();
		for (int i = 0; i < len - 2; i++) {
		//此处进入正数区域,直接跳出
			if (nums[i] > 0) {
//				System.out.println(nums[i] + ">0,进入正数区域,退出");
				break;
			}
			top = len - 1;
			for (int j = i + 1; j < len - 1; j++) {
				int two = nums[i] + nums[j];
				//因为从小往大排序,因此当前俩数需要的正数在j之前,跳出
				if (-two < nums[j]) {
//					System.out.println(two + "的相反数已经小于"+nums[j]+",不可能变为0,退出");
					break;
				}
				for (int k = top; k > j; k--) {
				//两个负数过于小找不到相应的大数,跳出
					if (-two > nums[k]) {
//						System.out.println(two+"相反数已经大于当前"+nums[k]+",退出");
						break;
					}
					if (two + nums[k] == 0) {
						List<Integer> li = new ArrayList<Integer>(3);
						li.add(nums[i]);
						li.add(nums[j]);
						li.add(nums[k]);
						//通过排序可以使set筛选出去
						li.sort(Comparator.comparingInt(Integer::intValue));
						list.add(li);
					}
					top = k;
				}
			}
		}
		return new ArrayList<List<Integer>>(list);
	}

看完题解之后的双指针方法

不得不说算法这个东西 真的是牛逼
在这里插入图片描述

	public List<List<Integer>> threeSum(int[] nums) {
		//数组排序
		Arrays.sort(nums);
		int len = nums.length;
		int k = 0;
		List<List<Integer>> list = new LinkedList<List<Integer>>();
		for (; k < len - 1; k++) {
			// 进行到正数部分,不可能等于0
			if (nums[k] > 0) {
				break;
			}
			//已经用该数查找过,跳过
			if (k > 0 && nums[k] == nums[k - 1]) {
				continue;
			}
			int i = k + 1;
			int j = len - 1;
			while (i < j) {
				int tmp = nums[i] + nums[k] + nums[j];
				if (tmp == 0) {
					ArrayList<Integer> li = new ArrayList<Integer>(3);
					li.add(nums[i]);
					li.add(nums[j]);
					li.add(nums[k]);
					list.add(li);
					//去除重复结果
					while (i < j && nums[i] == nums[i + 1]) {
						i++;
					}
					while (i < j && nums[j] == nums[j - 1]) {
						j--;
					}
				}
				if (tmp > 0) {
					j--;
				} else {
					i++;
				}
			}
		}
		return list;
	}

查看最快速度的代码

在这里插入图片描述
确实挺快,不过有个缺点就很明显,map数组的容量是个硬伤。类似于桶排序的缺点,而且更甚,取得是最大值与最小值的差。

	public List<List<Integer>> threeSum(int[] nums) {
		// 数组长度小于3,不可能成功
		if (nums.length < 3) {
			return Collections.emptyList();
		}
		List<List<Integer>> res = new ArrayList<>();
		int minValue = Integer.MAX_VALUE;
		int maxValue = Integer.MIN_VALUE;
		int negSize = 0;
		int posSize = 0;
		int zeroSize = 0;
		// 统计最大值,最小值,含有0的数量,正数数量,负数数量
		for (int v : nums) {
			if (v < minValue) {
				minValue = v;
			}
			if (v > maxValue) {
				maxValue = v;
			}
			if (v > 0) {
				posSize++;
			} else if (v < 0) {
				negSize++;
			} else {
				zeroSize++;
			}
		}
		// 含0大于等于3,添加[0,0,0]的情况
		if (zeroSize >= 3) {
			res.add(Arrays.asList(0, 0, 0));
		}
		// 正数或者负数有一方不存在,那么就不存在成功情况
		if (negSize == 0 || posSize == 0) {
			return res;
		}
		// 去掉无用的大值和小值
		if (minValue * 2 + maxValue > 0) {
			maxValue = -minValue * 2;
		} else if (maxValue * 2 + minValue < 0) {
			minValue = -maxValue * 2;
		}
		int[] map = new int[maxValue - minValue + 1];
		int[] negs = new int[negSize];
		int[] poses = new int[posSize];
		negSize = 0;
		posSize = 0;
		for (int v : nums) {
			if (v >= minValue && v <= maxValue) {
				// 类似与桶排序存储于map,每个数只在正负数数组添加一次,存贮本数与最小数的差值,确保大于等于0
				if (map[v - minValue]++ == 0) {
					if (v > 0) {
						poses[posSize++] = v;
					} else if (v < 0) {
						negs[negSize++] = v;
					}
				}
			}
		}
		// 对正负数分别排序
		Arrays.sort(poses, 0, posSize);
		Arrays.sort(negs, 0, negSize);
		int basej = 0;
		/**
		 * 从最大的负数开始遍历 依次寻找一个大于本次负数相反数一半的正数 如果0-负数-正数的差值处于该负数与该正数之间,那么就找到了一组
		 * 因为正数负数是两个边界,差值必须在两个数中间,所以该正数至少要大于等于该负数的相反数的一半
		 * 例如[-2,-1,3]的情况,第一次选用-1和3作为边界的时候,-2并不会被判定为一组,因为-2不在-1与3之间
		 * 假如本次寻找判定为成功了一组为[-1,3,-2],那么下次以-2和3为边界的时候,查到-1即[-2,3,-1],结果重复
		 * 因此从中间不断向外展开,确保不会重复
		 */
		// 对负数部分进行遍历,从最大的负数开始
		for (int i = negSize - 1; i >= 0; i--) {
			int nv = negs[i];
			// 查找时先确定两边,然后再中间寻找符合条件的,因为正数部分至少大于等于负数相反数的一半
			int minp = (-nv) >>> 1;
			// 查找第一个大于负数相反数一半的正数
			while (basej < posSize && poses[basej] < minp) {
				basej++;
			}
			// 对正数部分进行遍历
			for (int j = basej; j < posSize; j++) {
				int pv = poses[j];
				// 获取除当前两个数之外的差值
				int cv = 0 - nv - pv;
				// 该数必须在,两个边界之间,确保不会重复
				if (cv >= nv && cv <= pv) {
					// 差值与负数相同,即两个负数的和与正数互为相反数
					if (cv == nv) {
						// 判断是否存在两个或以上这样的数
						if (map[nv - minValue] > 1) {
							res.add(Arrays.asList(nv, nv, pv));
						}
					}
					// 差值与正数数相同,即两个正数的和与负数互为相反数
					else if (cv == pv) {
						if (map[pv - minValue] > 1) {
							res.add(Arrays.asList(nv, pv, pv));
						}
					}
					// 正常情况
					else {
						if (map[cv - minValue] > 0) {
							res.add(Arrays.asList(nv, cv, pv));
						}
					}
				} else if (cv < nv) {
					//差值已经比当前负数小了,而正数还在增大,绝无可能再成功了
					break;
				}
			}
		}
		return res;
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值