这里写自定义目录标题
更多精彩内容
这里是带你游历编程世界的Dashcoding编程社,我是Dash/北航硕士/ICPC区域赛全国排名30+/给你呈现我们眼中的世界!
256题算法特训课,帮你斩获大厂60W年薪offer
原题
腾讯校招真题编程乐趣
B站动画详解
问题分析
本题要求将一个包含正整数的数组通过最少的操作转变为奇数和偶数数量各占一半的数组。可以对数组中的任何元素执行两种操作:将其乘以2或将其除以2并向下取整。由于每个操作会改变数组元素的奇偶性,我们需要精心设计操作顺序以最小化总操作次数。
关键是要理解数组中的初始状态。我们可以将数组分为两部分:奇数和偶数。奇数的数量无需修改,但如果当前的奇数数量超过一半,我们需要将多余的奇数变为偶数。如果奇数的数量不足一半,则需要将部分偶数变为奇数。对于偶数部分,必须通过除以2操作,将偶数减少到一半。
因此,该问题可以分为两个阶段:
- 统计数组中的奇数和偶数的数量。
- 根据当前奇偶数量,计算最小操作次数,使得奇偶数量相等。
思路分析
步骤1:统计奇数和偶数数量
遍历数组,分别统计奇数和偶数的数量。对于偶数部分,我们还需要计算将其转变为奇数所需的最小操作次数。奇数无需处理,除非奇数数量多于一半,此时需要将部分奇数变为偶数。
步骤2:处理奇偶数量
- 如果当前奇数数量大于等于目标奇数数量,则只需将多余的奇数变为偶数,计算这些操作的次数。
- 如果奇数数量不足,则需要将偶数部分中的一些数字转变为奇数。为了最小化操作次数,优先选择操作次数最少的偶数。
步骤3:计算最小操作次数
对偶数进行排序,选择最少次数的操作将偶数转变为奇数,直到奇偶数量达到要求。输出最终操作次数。
算法实现
为了让数组中的一半数字是奇数,另一半是偶数,并且通过最少的操作次数达到这个目标,我们可以设计如下算法步骤:
-
统计奇数和偶数:首先遍历整个数组,统计奇数和偶数的数量。如果数字是偶数,则进一步计算将其转换为奇数所需的最小操作次数(即每次将数字除以2,直到它变为奇数为止,计算除以2的次数)。
-
确定操作策略:
- 奇数多于或等于目标数量:如果初始数组中奇数的数量大于或等于目标数量(即总数的一半),那么我们只需要将多余的奇数变为偶数即可。这种情况下,不需要对偶数进行操作。
- 奇数不足目标数量:如果奇数数量不足,则需要将部分偶数转换为奇数。为了最小化操作次数,我们会选择那些转换成本最小的偶数。
操作排序:将偶数转换为奇数的操作次数排序,优先选择需要最少操作次数的偶数进行转换,直到达到目标奇数数量。
-
计算总操作次数:最终计算所需的最小操作次数,并输出。
代码详解
标准代码程序
C++代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int a[N], pos;
int main() {
int n, even = 0, odd = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
if (x % 2 == 0) {
int cnt = 0;
while (x % 2 == 0) {
x /= 2;
cnt++;
}
a[++even] = cnt;
} else {
odd++;
}
}
if (odd >= n / 2) {
cout << odd - n / 2 << endl;
} else {
sort(a + 1, a + 1 + even);
int ans = 0;
for (int i = 1; i <= even - n / 2 + odd; i++) {
ans += a[i];
}
cout << ans << endl;
}
return 0;
}
Java代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] a = new int[n];
int even = 0, odd = 0;
for (int i = 0; i < n; i++) {
int x = scanner.nextInt();
if (x % 2 == 0) {
int cnt = 0;
while (x % 2 == 0) {
x /= 2;
cnt++;
}
a[even++] = cnt;
} else {
odd++;
}
}
if (odd >= n / 2) {
System.out.println(odd - n / 2);
} else {
Arrays.sort(a, 0, even);
int ans = 0;
for (int i = 0; i < even - n / 2 + odd; i++) {
ans += a[i];
}
System.out.println(ans);
}
scanner.close();
}
}
Python代码
n = int(input())
a = list(map(int, input().split()))
even = 0
odd = 0
cnts = []
for x in a:
if x % 2 == 0:
cnt = 0
while x % 2 == 0:
x //= 2
cnt += 1
cnts.append(cnt)
even += 1
else:
odd += 1
if odd >= n // 2:
print(odd - n // 2)
else:
cnts.sort()
ans = sum(cnts[:even - n // 2 + odd])
print(ans)
Javascript代码
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('line', (input) => {
if (!globalThis.n) {
globalThis.n = parseInt(input.trim());
globalThis.a = [];
} else {
globalThis.a = input.trim().split(' ').map(Number);
solve(globalThis.n, globalThis.a);
rl.close();
}
});
function solve(n, a) {
let even = 0;
let odd = 0;
let cnts = [];
for (let x of a) {
if (x % 2 === 0) {
let cnt = 0;
while (x % 2 === 0) {
x = Math.floor(x / 2);
cnt++;
}
cnts.push(cnt);
even++;
} else {
odd++;
}
}
if (odd >= n / 2) {
console.log(odd - n / 2);
} else {
cnts.sort((a, b) => a - b);
let ans = 0;
for (let i = 0; i < even - n / 2 + odd; i++) {
ans += cnts[i];
}
console.log(ans);
}
}
复杂度分析
-
时间复杂度:
遍历数组统计奇数和偶数的数量,以及计算每个偶数转变为奇数所需的最小操作次数,时间复杂度为 O ( n ) O(n) O(n)。
对偶数部分的转变次数进行排序的时间复杂度为 O ( e v e n log e v e n ) O(even \log even) O(evenlogeven),在最坏情况下, e v e n even even 的数量为 n n n。
因此,总的时间复杂度为 O ( n log n ) O(n \log n) O(nlogn)。 -
空间复杂度:
该算法主要使用了一个数组 a [ N ] a[N] a[N] 来存储偶数部分的操作次数,空间复杂度为 O ( n ) O(n) O(n)。
整个算法的额外空间开销仅为线性,因此空间复杂度为 O ( n ) O(n) O(n)。