package com.interview.leetcode.arrays; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; /** * Created_By: stefanie * Date: 14-11-12 * Time: 下午9:21 * * Given an array S of N integers, find all unique combination of M elements in the array which gives the sum of K. * * 1. When M is 2: 2 Sum {@link #sum2(int[], int)} * 2. When M is 3: 3 Sum {@link #sum3(int[], int)} * 3. When M is 4: 4 Sum {@link #sum4(int[], int)} * 4. find the sum of 3 elements which is closest to given K. {@link #closest3(int[], int)} * * From LeetCode: * https://oj.leetcode.com/problems/3sum/ * https://oj.leetcode.com/problems/4sum/ * https://oj.leetcode.com/problems/3sum-closest/ * * Tricks: * 1. Simplify the question by settle one or more variables, and find the others. 3sum or 4sum * 2. Sort the array at first to find a quick and straight forward solution * 3. Skip the duplicate number when scanning array to avoid create duplicate solution. */ public class FindSum { /** * For binarysearch problem, if could use additional space, * using HashSet or HashMap to reduce the binarysearch time complexity to O(1) * * only visit once, put visited data in map. * Hash table: O(n) runtime, O(n) space */ public int[] sum2One(int[] numbers, int target) { HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(); for(int i = 0; i < numbers.length; i++){ int expected = target - numbers[i]; if(map.containsKey(expected)){ return new int[]{map.get(expected) + 1, i + 1}; } map.put(numbers[i], i); } return new int[]{-1, -1}; } public static List<List<Integer>> sum2(int[] num, int key) { Arrays.sort(num); return sum2(num, 0, key); } /** * Scan the sorted num from begin, to find 2 elements sum is key O(N) * have 2 indexes begin and end, when the sum(begin, end) * == key, found a answer * to avoid duplication * end move left to the one which is not the same value of the old end * begin move right to the one which is not the same value of the old begin * > key, end move left one step * < key, begin move right one step * have a loop until the 2 indexes meet. */ private static List<List<Integer>> sum2(int[] num, int begin, int key) { List<List<Integer>> sols = new ArrayList<>(); int end = num.length - 1; while (begin < end) { int sum = num[begin] + num[end]; if (sum == key) { List<Integer> sol = new ArrayList<>(); sol.add(num[begin++]); sol.add(num[end--]); sols.add(sol); while (begin < end && num[begin] == num[begin - 1]) begin++; //avoid duplication while (begin < end && num[end] == num[end + 1]) end--; //avoid duplication } else if (sum > key) end--; else begin++; } return sols; } /** * specify one element, find the other 2 elements in the right part. O(N^2) * every time select a new one which is different with previous one to avoid duplication */ public static List<List<Integer>> sum3(int[] num, int key) { Arrays.sort(num); List<List<Integer>> sols = new ArrayList<>(); for (int i = 0; i < num.length - 2; i++) { if (i > 0 && num[i] == num[i - 1]) continue; int target = key - num[i]; List<List<Integer>> subsols = sum2(num, i + 1, target); for (List<Integer> sol : subsols) { sol.add(0, num[i]); sols.add(sol); } } return sols; } /** * specify two element, find the other 2 elements in the right part. O(N^3) * every time select a new one which is different with previous one to avoid duplication */ public static List<List<Integer>> sum4(int[] num, int key) { Arrays.sort(num); List<List<Integer>> sols = new ArrayList<>(); for (int i = 0; i < num.length - 3; i++) { if (i > 0 && num[i] == num[i - 1]) continue; for (int j = i + 1; j < num.length - 2; j++) { if (j > i + 1 && num[j] == num[j - 1]) continue; int target = key - num[i] - num[j]; List<List<Integer>> subsols = sum2(num, j + 1, target); for (List<Integer> sol : subsols) { sol.add(0, num[j]); sol.add(0, num[i]); sols.add(sol); } } } return sols; } /** * keep tracking the closest for every combination found using 2 sum O(N^2) */ public static int closest3(int[] num, int key) { Arrays.sort(num); int closest = Integer.MAX_VALUE; for (int i = 0; i < num.length - 2; i++) { int j = i + 1; int k = num.length - 1; while (j < k) { int sum = num[i] + num[j] + num[k]; if (sum == key) return sum; else { if (Math.abs(sum - key) < Math.abs(closest)) closest = sum - key; if (key > sum) j++; else k--; } } } return closest + key; } }