
华为OD机试 新系统 题库疯狂收录中,刷题点这里
专栏导读
本专栏收录于《华为OD机试(JAVA)真题》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天优快云在线答疑。
一、题目描述
给定一个包含
n
n
n 个整数的数组 nums 和一个整数 target,请从数组中找出所有 不重复 的三元组 [nums[i], nums[j], nums[k]],满足以下两个条件:
-
和为目标:
n u m s [ i ] + n u m s [ j ] + n u m s [ k ] = t a r g e t nums[i] + nums[j] + nums[k] = target nums[i]+nums[j]+nums[k]=target
-
奇偶性约束:
三个元素中 至少有两个 是奇数。
返回所有满足条件的不重复三元组。
提示:
-
3 ≤ n u m s . l e n g t h ≤ 3000 3 \le nums.length \le 3000 3≤nums.length≤3000
-
− 10 5 ≤ n u m s [ i ] ≤ 10 5 -10^5 \le nums[i] \le 10^5 −105≤nums[i]≤105
补充说明
返回值输出格式:每个子数组内部的元素按数值升序排列,所有子数组之间按字典序升序排列(即先比较第一个元素,若相同再比较第二个,以此类推)。
示例:
[[-1,-1,2],[-1,0,1]]
二、输入描述
输入为一个数组 nums 和一个目标整数 target。
三、输出描述
输出所有满足条件的不重复三元组。
四、测试用例
测试用例1:
1、输入
-1,0,1,2,-1,-4
0
2、输出
-1,-1,2
-1,0,1
3、说明
两个三元组都满足和为 0,并且至少两个元素为奇数。
测试用例2:
1、输入
1,2,3,4,5
9
2、输出
1,3,5
3、说明
2,3,4 虽然和为 9,但只有 3 一个奇数,因此排除。
五、解题思路
本题本质上是在经典“三数之和”问题上增加了两个要求:三元组不能重复,并且三个元素中至少有两个奇数。
首先对数组进行升序排序。排序有三个作用:第一,每个找到的三元组天然满足内部元素升序;第二,相同数字会排列在一起,可以通过跳过重复元素完成去重,不需要额外使用 HashSet;第三,排序后可以使用双指针,将原本三层循环的 O(n³) 时间复杂度优化为 O(n²)。
具体做法是枚举三元组中的第一个元素 nums[i]。如果 nums[i] 与前一个元素相同,则直接跳过,避免产生重复答案。随后令 left=i+1、right=n-1,计算 nums[i]+nums[left]+nums[right]。如果和小于 target,说明需要增大总和,因此 left++;如果和大于 target,则 right–;如果正好等于 target,再统计三个元素中的奇数数量。奇数个数不少于 2 时,将当前三元组加入结果。
当找到一个和等于 target 的组合后,无论其是否通过奇偶性检查,都需要跳过 left 和 right 位置上的重复值,因为相同数值再次组合只会得到完全相同的三元组。
此外,因为数组已经排序,可以利用当前 nums[i] 对应的最小三数和与最大三数和进行剪枝。如果最小和已经大于 target,后续 nums[i] 更大,可以直接结束;如果最大和仍小于 target,则当前 nums[i] 不可能产生答案,可以直接进入下一轮。
由于 i 按升序遍历,left 也按升序移动,因此最终结果天然满足三元组之间的字典序要求。整体时间复杂度为 O(n²),可以满足 n≤3000 的数据规模;结果数组之外主要使用常数级双指针空间。
六、Java算法源码
public class OdTest {
/**
* 找出所有满足:
* 1. 三数之和等于 target
* 2. 至少包含两个奇数
* 3. 三元组不重复
*/
public static List<List<Integer>> findTriples(int[] nums, long target) {
List<List<Integer>> result = new ArrayList<>();
// 排序后可以使用双指针,把三重枚举 O(n^3) 优化为 O(n^2)。
// 同时,相同元素会相邻,便于直接跳过重复值,实现三元组去重。
Arrays.sort(nums);
int n = nums.length;
for (int i = 0; i < n - 2; i++) {
// 第一个元素去重:
// 如果 nums[i] 和上一次固定的数字相同,则可能产生完全相同的三元组。
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
/*
* 剪枝1:
* 当前 nums[i] 能组成的最小三数和已经大于 target。
* 因为后面的 nums[i] 只会更大,所以可以直接结束整个循环。
*/
long minSum = (long) nums[i] + nums[i + 1] + nums[i + 2];
if (minSum > target) {
break;
}
/*
* 剪枝2:
* 当前 nums[i] 和最大的两个数相加仍然小于 target,
* 说明固定当前 nums[i] 不可能找到答案,直接处理下一个 i。
*/
long maxSum = (long) nums[i] + nums[n - 2] + nums[n - 1];
if (maxSum < target) {
continue;
}
int left = i + 1;
int right = n - 1;
while (left < right) {
// 使用 long 计算,避免一般场景下三个 int 相加出现溢出。
long sum = (long) nums[i] + nums[left] + nums[right];
if (sum < target) {
// 当前和偏小,需要增大左边的数字。
left++;
} else if (sum > target) {
// 当前和偏大,需要减小右边的数字。
right--;
} else {
/*
* 三数之和已经满足 target,
* 接下来检查三个数字中是否至少有两个奇数。
*
* 注意:不能使用 x % 2 == 1 判断所有奇数,
* 因为 Java 中 -1 % 2 == -1。
* 使用 x % 2 != 0 可以同时判断正奇数和负奇数。
*/
int oddCount = 0;
if (nums[i] % 2 != 0) {
oddCount++;
}
if (nums[left] % 2 != 0) {
oddCount++;
}
if (nums[right] % 2 != 0) {
oddCount++;
}
if (oddCount >= 2) {
result.add(Arrays.asList(
nums[i],
nums[left],
nums[right]
));
}
/*
* 当前数值组合已经处理完毕。
*
* 无论它是否满足奇偶条件,相同的 left/right 数值再次出现时,
* 得到的仍然是完全相同的三元组,因此统一跳过重复值。
*/
int leftValue = nums[left];
int rightValue = nums[right];
while (left < right && nums[left] == leftValue) {
left++;
}
while (left < right && nums[right] == rightValue) {
right--;
}
}
}
}
return result;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
if (!scanner.hasNextLine()) {
return;
}
/*
* 题目示例输入:
* -1,0,1,2,-1,-4
*
* 同时兼容:
* [-1,0,1,2,-1,-4]
*/
String arrayLine = scanner.nextLine()
.trim()
.replace("[", "")
.replace("]", "");
if (!scanner.hasNextLine()) {
return;
}
long target = Long.parseLong(scanner.nextLine().trim());
String[] parts = arrayLine.split("\\s*,\\s*");
int[] nums = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
nums[i] = Integer.parseInt(parts[i].trim());
}
List<List<Integer>> result = findTriples(nums, target);
/*
* 排序 + 双指针的遍历顺序天然满足字典序:
* 第一个元素递增;
* 第一个元素相同时,第二个元素递增。
*/
for (List<Integer> triple : result) {
System.out.println(
triple.get(0) + "," +
triple.get(1) + "," +
triple.get(2)
);
}
}
}
七、效果展示
1、输入
3,3,2,2,3,2,4
8
2、输出
2,3,3
3、说明
专门验证重复数字。数组中有多个 2 和 3,但 2,3,3 只能输出一次。

🏆下一篇:华为OD机试 - 简易内存池 - 逻辑分析(Java 新系统 200分)
🏆本专栏收录于《华为OD机试(JAVA)真题》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天优快云在线答疑。

45万+



被折叠的 条评论
为什么被折叠?



