
专栏导读
本专栏收录于《华为OD机试真题(Python/JS/C/C++)》。
刷的越多,抽中的概率越大,私信哪吒,备注华为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 的数据规模;结果数组之外主要使用常数级双指针空间。
六、Python算法源码
def find_triples(nums, target):
result = []
# 先排序:
# 1. 可以使用双指针,将复杂度降为 O(n^2)
# 2. 相同数字会相邻,方便直接完成去重
# 3. 最终三元组内部天然按照升序排列
nums.sort()
n = len(nums)
for i in range(n - 2):
# 固定的第一个元素去重。
if i > 0 and nums[i] == nums[i - 1]:
continue
# 最小可能三数和已经大于 target,
# 后面的第一个元素只会更大,可以直接结束。
if nums[i] + nums[i + 1] + nums[i + 2] > target:
break
# 最大可能三数和仍然小于 target,
# 当前 nums[i] 不可能产生答案。
if nums[i] + nums[n - 2] + nums[n - 1] < target:
continue
left = i + 1
right = n - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total < target:
left += 1
elif total > target:
right -= 1
else:
# 三数之和满足 target 后,再统计奇数数量。
# 使用 != 0 可以正确处理负奇数。
odd_count = sum(
x % 2 != 0
for x in (nums[i], nums[left], nums[right])
)
if odd_count >= 2:
result.append(
(nums[i], nums[left], nums[right])
)
# 当前数值组合已经处理过,
# 跳过左右两侧的重复数字,防止产生重复答案。
left_value = nums[left]
right_value = nums[right]
while left < right and nums[left] == left_value:
left += 1
while left < right and nums[right] == right_value:
right -= 1
return result
def main():
# 同时兼容:
# 1,2,3
# [1,2,3]
line = input().strip().replace('[', '').replace(']', '')
target = int(input().strip())
nums = [
int(x.strip())
for x in line.split(',')
]
result = find_triples(nums, target)
for a, b, c in result:
print(f"{a},{b},{c}")
if __name__ == "__main__":
main()
七、JavaScript算法源码
const fs = require('fs');
function findTriples(nums, target) {
const result = [];
/*
* JavaScript 默认 sort() 会按照字符串进行排序,
* 因此必须传入 (a, b) => a - b。
*
* 排序后使用双指针,可以将复杂度优化到 O(n^2),
* 同时方便跳过重复值。
*/
nums.sort((a, b) => a - b);
const n = nums.length;
for (let i = 0; i < n - 2; i++) {
// 第一个元素去重。
if (i > 0 && nums[i] === nums[i - 1]) {
continue;
}
// 当前能得到的最小三数和已经过大,
// 后续 nums[i] 更大,因此可以直接结束。
if (nums[i] + nums[i + 1] + nums[i + 2] > target) {
break;
}
// 当前能得到的最大三数和仍然过小,
// 当前固定元素不可能得到答案。
if (nums[i] + nums[n - 2] + nums[n - 1] < target) {
continue;
}
let left = i + 1;
let right = n - 1;
while (left < right) {
const sum =
nums[i] +
nums[left] +
nums[right];
if (sum < target) {
left++;
} else if (sum > target) {
right--;
} else {
/*
* 和满足 target 后检查奇偶约束。
* JavaScript 中负奇数 % 2 的结果可能为 -1,
* 所以应该使用 !== 0,而不是 === 1。
*/
let oddCount = 0;
if (nums[i] % 2 !== 0) {
oddCount++;
}
if (nums[left] % 2 !== 0) {
oddCount++;
}
if (nums[right] % 2 !== 0) {
oddCount++;
}
if (oddCount >= 2) {
result.push([
nums[i],
nums[left],
nums[right]
]);
}
// 无论是否满足奇偶条件,
// 当前相同数值组合都不需要再次检查。
const leftValue = nums[left];
const rightValue = nums[right];
while (
left < right &&
nums[left] === leftValue
) {
left++;
}
while (
left < right &&
nums[right] === rightValue
) {
right--;
}
}
}
}
return result;
}
// 读取控制台全部输入。
const lines = fs.readFileSync(0, 'utf8')
.split(/\r?\n/)
.filter(line => line.trim().length > 0);
if (lines.length >= 2) {
// 同时兼容 1,2,3 和 [1,2,3]
const arrayLine = lines[0]
.trim()
.replace(/\[/g, '')
.replace(/\]/g, '');
const target = Number(lines[1].trim());
const nums = arrayLine
.split(',')
.map(x => Number(x.trim()));
const result = findTriples(nums, target);
for (const triple of result) {
console.log(triple.join(','));
}
}
八、C算法源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_N 3005
#define MAX_LINE 40000
/*
* qsort 使用的整数比较函数。
* 不直接 return x - y,可以避免一般情况下减法溢出的风险。
*/
int compare_int(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y);
}
int main(void) {
char line[MAX_LINE];
// 题目 nums.length <= 3000,
// 因此申请 3005 个位置即可。
int nums[MAX_N];
int n = 0;
if (fgets(line, sizeof(line), stdin) == NULL) {
return 0;
}
/*
* 兼容:
* 1,2,3
* [1,2,3]
*
* 将方括号替换为空格,再按照逗号切分。
*/
for (int i = 0; line[i] != '\0'; i++) {
if (line[i] == '[' || line[i] == ']') {
line[i] = ' ';
}
}
char *token = strtok(line, ",");
while (token != NULL && n < MAX_N) {
nums[n++] = (int)strtol(token, NULL, 10);
token = strtok(NULL, ",");
}
char targetLine[100];
if (fgets(targetLine, sizeof(targetLine), stdin) == NULL) {
return 0;
}
long long target = strtoll(targetLine, NULL, 10);
/*
* 排序后使用:
* 固定第一个数字 + 左右双指针。
*
* 时间复杂度从 O(n^3) 优化为 O(n^2),
* 同时方便跳过重复数字。
*/
qsort(nums, n, sizeof(int), compare_int);
for (int i = 0; i < n - 2; i++) {
// 固定元素去重。
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
/*
* 使用 long long 计算和,
* 避免三个 int 相加时潜在的整型溢出。
*/
long long minSum =
(long long)nums[i] +
nums[i + 1] +
nums[i + 2];
if (minSum > target) {
break;
}
long long maxSum =
(long 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 long sum =
(long long)nums[i] +
nums[left] +
nums[right];
if (sum < target) {
left++;
} else if (sum > target) {
right--;
} else {
/*
* 和满足 target 后,再统计奇数数量。
*
* C 中负奇数取模可能得到 -1,
* 所以同样使用 % 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) {
printf(
"%d,%d,%d\n",
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 0;
}
九、C++算法源码
#include <iostream>
#include <vector>
#include <algorithm>
#include <sstream>
#include <string>
using namespace std;
vector<vector<int>> findTriples(
vector<int> nums,
long long target) {
vector<vector<int>> result;
/*
* 排序有三个目的:
* 1. 使用双指针,将复杂度优化到 O(n^2)
* 2. 相同元素相邻,方便去重
* 3. 每个答案内部天然为升序
*/
sort(nums.begin(), nums.end());
int n = static_cast<int>(nums.size());
for (int i = 0; i < n - 2; ++i) {
// 固定的第一个数字去重。
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
/*
* 当前 nums[i] 能组成的最小和已经大于 target,
* 后面的 nums[i] 只会更大,因此直接结束。
*/
long long minSum =
(long long)nums[i] +
nums[i + 1] +
nums[i + 2];
if (minSum > target) {
break;
}
/*
* 当前 nums[i] 与最大的两个数字相加仍然小于 target,
* 当前固定元素不可能产生答案。
*/
long long maxSum =
(long 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 long sum =
(long long)nums[i] +
nums[left] +
nums[right];
if (sum < target) {
++left;
} else if (sum > target) {
--right;
} else {
/*
* 三数和满足 target 后,
* 再检查三个数中是否至少存在两个奇数。
*
* 使用 % 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.push_back({
nums[i],
nums[left],
nums[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;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string line;
if (!getline(cin, line)) {
return 0;
}
// 同时兼容 1,2,3 和 [1,2,3]。
line.erase(
remove(line.begin(), line.end(), '['),
line.end()
);
line.erase(
remove(line.begin(), line.end(), ']'),
line.end()
);
vector<int> nums;
string token;
stringstream ss(line);
while (getline(ss, token, ',')) {
nums.push_back(stoi(token));
}
long long target;
cin >> target;
vector<vector<int>> result =
findTriples(nums, target);
for (const auto &triple : result) {
cout
<< triple[0] << ','
<< triple[1] << ','
<< triple[2] << '\n';
}
return 0;
}
🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 新系统 200分)
🏆本文收录于,华为OD机试真题(Python/JS/C/C++)
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。

5万+



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



