一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情。
一、元组问题 I
给定一个有序数组arr,给定一个正数aim,打印累加和为aim的所有不同二元组
1、分析
二元组问题:利用双指针,题目是有序数组,有三种可能性:
- 可能性一:
arr[L] + arr[R] < aim
,L++ - 可能性二:
arr[L] + arr[R] = aim
,三种情况都可以- L++,收集答案,但需要有排重机制,L-1 ≠ L作为排重条件
- R--,R ≠ R+1 作为排重条件
- L++,R--,L-1 ≠ L 或 R ≠ R+1 作为排重条件,这种机制是有单调性的
- 可能性三:
arr[L] + arr[R] > aim
,R--
有序数组具有单调性,L++,R--,如果不是有序数组,需要对数组进行排序,才可以利用双指针技巧
2、实现
public static void printUniquePair(int[] arr, int aim) {
if (arr == null || arr.length < 2) {
return;
}
int L = 0;
int R = arr.length - 1;
while (L < R) {
if (arr[L] + arr[R] < aim) {
L++;
} else if (arr[L] + arr[R] > aim) {
R--;
} else { // L + R = aim
if (L == 0 || arr[L - 1] != arr[L]) {
System.out.println(arr[L] + "," + arr[R]);
}
L++;
R--;
}
}
}
二、元组问题 II
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值target的那两个整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
1、分析
二元组问题
方法一:数组先排序,利用双指针技巧
方法二:采用哈希表HashMap,key存数组中每个数,value存数对应的下标
2、实现
public static int[] twoSum(int[] nums, int target) {
// key 某个之前的数 value 这个数出现的位置
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]), i};
}
map.put(nums[i], i);
}
return new int[]{-1, -1};
}
三、元组问题 III
给定一个有序数组arr,给定一个正数aim,打印累加和为aim的所有不同三元组
1、分析
三元组问题:转化为二元组问题,当前来到i位置,求i+1~N-1
范围上的两数累计和为aim - arr[i]
,也需要有排重机制
三元组问题转化为有边界的二元组问题
2、实现
public static void printUniqueTriad(int[] arr, int aim) {
if (arr == null || arr.length < 3) {
return;
}
for (int i = 0; i < arr.length - 2; i++) { // 0~N-3
if (i == 0 || arr[i] != arr[i - 1]) {
printRest(arr, i, i + 1, arr.length - 1, aim - arr[i]);
}
}
}
public static void printRest(int[] arr, int f, int L, int R, int aim) {
while (L < R) {
if (arr[L] + arr[R] < aim) {
L++;
} else if (arr[L] + arr[R] > aim) {
R--;
} else {
if (L == f + 1 || arr[L - 1] != arr[L]) {
System.out.println(arr[f] + "," + arr[L] + "," + arr[R]);
}
L++;
R--;
}
}
}
四、元组问题 IV
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为0且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:
输入:nums = []
输出:[]
示例 3:
输入:nums = [0]
输出:[]
1、分析
三元组问题可以转化为二元组问题,卡主第一个数(数组中的每一个数作为三元组的第一个数),然后就是二元组问题,如果之前出现过,则跳过,防重
把打印行为变为收集,收集的过程中需要考虑ArrayList插入方向效率问题
2、实现
public static List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
int N = nums.length;
List<List<Integer>> ans = new ArrayList<>();
for (int i = N - 1; i > 1; i--) { // 从后往前遍历
if (i == N - 1 || nums[i] != nums[i + 1]) {
List<List<Integer>> nexts = twoSum(nums, i - 1, -nums[i]);
for (List<Integer> cur : nexts) {
cur.add(nums[i]); // 效率高,直接添加到结尾
ans.add(cur);
}
}
}
return ans;
}
public static List<List<Integer>> twoSum(int[] nums, int end, int target) {
int L = 0;
int R = end;
List<List<Integer>> ans = new ArrayList<>();
while (L < R) {
if (nums[L] + nums[R] > target) {
R--;
} else if (nums[L] + nums[R] < target) {
L++;
} else {
if (L == 0 || nums[L - 1] != nums[L]) {
List<Integer> cur = new ArrayList<>();
cur.add(nums[L]);
cur.add(nums[R]);
ans.add(cur);
}
L++;
}
}
return ans;
}
五、元组问题 V
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n
请你计算有多少个元组 (i, j, k, l) 能满足:
- 0 <= i, j, k, l < n
- nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例 1:
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
示例 2:
输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1
1、分析
方法一:4个for循环,时间复杂度O(N^4)
方法二:2个for循环,时间复杂度O(N²),先两个for循环找出累加和及出现的次数,用map存起来,key:数组1和数组2中的每个数的累加和,value为累加和出现的次数,然后在拿两个for循环,根据数组3和数组4中的每个数的累加和,去从Map中找key为相反数的,value即为出现的元组
2、实现
public static int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
HashMap<Integer, Integer> map = new HashMap<>();
int sum = 0;
for (int i = 0; i < A.length; i++) {
for (int j = 0; j < B.length; j++) {
sum = A[i] + B[j];
if (!map.containsKey(sum)) {
map.put(sum, 1);
} else {
map.put(sum, map.get(sum) + 1);
}
}
}
int ans = 0;
for (int i = 0; i < C.length; i++) {
for (int j = 0; j < D.length; j++) {
sum = C[i] + D[j];
if (map.containsKey(-sum)) {
ans += map.get(-sum);
}
}
}
return ans;
}
六、元组问题 VI
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
- 0 <= a, b, c, d < n
- a、b、c 和 d 互不相同
- nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
1、分析
四数之和问题转化为三数之和问题,三数之和问题转化为两数之和问题
先排序,利用双指针
2、实现
public static List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
int N = nums.length;
List<List<Integer>> ans = new ArrayList<>();
for (int i = N - 1; i > 2; i--) {
if (i == N - 1 || nums[i] != nums[i + 1]) {
List<List<Integer>> nexts = threeSum(nums, i - 1, target - nums[i]);
for (List<Integer> cur : nexts) {
cur.add(nums[i]); // 效率高,直接添加到结尾
ans.add(cur);
}
}
}
return ans;
}
public static List<List<Integer>> threeSum(int[] nums, int end, int target) {
List<List<Integer>> ans = new ArrayList<>();
for (int i = end; i > 1; i--) { // 从后往前遍历
if (i == end || nums[i] != nums[i + 1]) {
List<List<Integer>> nexts = twoSum(nums, i - 1, target - nums[i]);
for (List<Integer> cur : nexts) {
cur.add(nums[i]); // 效率高,直接添加到结尾
ans.add(cur);
}
}
}
return ans;
}
public static List<List<Integer>> twoSum(int[] nums, int end, int target) {
int L = 0;
int R = end;
List<List<Integer>> ans = new ArrayList<>();
while (L < R) {
if (nums[L] + nums[R] > target) {
R--;
} else if (nums[L] + nums[R] < target) {
L++;
} else {
if (L == 0 || nums[L - 1] != nums[L]) {
List<Integer> cur = new ArrayList<>();
cur.add(nums[L]);
cur.add(nums[R]);
ans.add(cur);
}
L++;
}
}
return ans;
}