OPPO
OPPO¶
直接笔试,2个小时 20道单选+3道编程题
第一题:贪心¶
题目描述¶
小红有一个长度为 n 的数组{a_1, a_2, ......, a_n} ,小红需要执行 k 次操作,操作内容如下: - 选择一个数组元素 a_i,令 a_i = a_i 按位异或 1 小红想让数组元素之和最大,请你输出这个值。
输入描述: 第一行输入两个整数 n, k 代表数组中的元素数量、操作数量。 第二行输入 n 个整数 代表数组元素。 其中 1<=n<=100000, 1<=k<=1000000000 数组中的每个元素 a_i 满足1<= a_i<=1000000000
输出描述: 在一行上输出一个整数,代表最大的数组元素之和。
示例: 输入: 5 3 1 2 3 4 5
输出 16
思路¶
这个问题的目标是通过执行最多 kkk 次按位异或操作,让数组的元素之和尽可能大。
按位异或操作 a_i = a_i 异或 1
的作用是:如果 a_i
是偶数,则将其变为奇数;如果 a_i
是奇数,则将其变为偶数。因此:
- 对偶数执行异或操作可以让它增加 1(变成更大的奇数)。
- 对奇数执行异或操作会让它减少 1(变成更小的偶数)。
优化思路:
-
贪心策略:
- 如果 a_i 是偶数,我们希望对其执行异或操作,这样可以让它增加 1。
- 如果 a_i 是奇数,我们尽量不对其执行异或操作,因为这样会减少它的值。
-
优先处理偶数:
-
将数组中的偶数尽可能变为奇数,因为这样可以增加总和。
- 如果操作次数 k 用完后还有剩余,剩下的操作需要交替对奇数和偶数进行反转。
-
处理剩余操作:
-
如果 k 次操作后还有剩余,且剩下的次数是偶数次,那么不需要再改变数组,因为两次操作对同一个数会恢复原值。
- 如果剩余次数是奇数次,则可以对任意一个元素进行一次异或操作,影响最小的选择是对某个奇数执行一次异或。
算法步骤:
- 遍历数组,将偶数按位异或,使其变成奇数。
- 计算剩余的操作次数。
- 如果剩余操作是奇数次,则对影响最小的元素(奇数)执行一次异或。
答案¶
import java.util.Scanner;
public class MaximizeArraySum {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 输入 n 和 k
int n = scanner.nextInt();
long k = scanner.nextLong();
// 输入数组
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = scanner.nextInt();
}
// 统计初始的数组总和
long sum = 0;
for (int num : arr) {
sum += num;
}
// 遍历数组,处理偶数元素
for (int i = 0; i < n && k > 0; i++) {
if (arr[i] % 2 == 0) { // 如果是偶数,执行异或操作
arr[i] = arr[i] ^ 1; // 使偶数变为奇数
sum += 1; // 偶数异或后加1,所以总和增加1
k--; // 执行了一次操作
}
}
// 如果剩下的 k 还不为0且是奇数,需要再处理一次异或
if (k > 0 && k % 2 == 1) {
// 对最小的元素再执行一次异或操作,选择改变后最小影响的元素
int minElement = Integer.MAX_VALUE;
for (int num : arr) {
minElement = Math.min(minElement, num);
}
// 异或操作会让这个最小的元素增减 1
sum -= minElement; // 先减去当前的最小值
sum += minElement ^ 1; // 再加上异或 1 后的值
}
// 输出最大的总和
System.out.println(sum);
scanner.close();
}
}
过了60%
代码解析:¶
- 输入处理:我们读取数组的长度 n 和操作次数 k,并初始化数组。
- 初始总和计算:在不进行操作的情况下,先计算出数组的总和。
- 处理偶数:我们遍历数组,将偶数通过异或 1 变成奇数,并增加总和。
- 处理剩余操作:如果剩余的操作次数 kkk 是奇数,那么就选择数组中最小的元素执行一次异或操作,确保对总和的影响最小。
- 输出结果:最后输出最大化的数组总和。
复杂度分析:
- 时间复杂度:O(n),我们只需遍历数组一遍,来处理所有偶数并计算总和。
- 空间复杂度:O(1),只需常数空间来存储几个变量。
第二题¶
题目描述¶
小红有一个长度为 n 的数组 {a_1, a_2, ..., a_n}。小红可以进行若干次操作,每次操作可以选择一个数p (1<=p<=n-1) ,将数组分成 [1, p] 和 [p+1,n] 两部分,然后分别对这两部分进行反转。 例如,对于数组 [1, 2, 3, 4, 5],如果 p=2,则操作后的数组为 [2, 1, 5, 4, 3]。 小红想对数组进行若干次操作,然后截取一个非空子数组,使得截取的子数组元素和最大,问最大的元素和是多少。
输入描述 第一行输入一个整数 n (1<=n<=100000)代表数组中的元素数量。 第二行输入 n 个整数 a_1, a_2, ..., a_n (1<=a_i<=1000000000)表示数组元素。
输出描述: 在一行上输出一个整数,代表最大的元素和
示例: 输入: 5 3 4 -5 -1 2 输出: 9
思路¶
为了充分利用反转操作的潜力,我们需要考虑以下两点:
- 不进行任何反转的最大子数组和:即直接使用 Kadane's Algorithm 得到当前数组的最大子数组和。
- 一次反转后的最大子数组和:通过反转操作,我们可以优化子数组的和。特别是考虑“前缀”和“后缀”的组合,反转会将前缀和后缀互换,因此我们需要计算可能通过反转获得的更优子数组和。
解决方案:
- 正向 Kadane's Algorithm:计算数组的最大子数组和,代表不做任何反转的情况。
- 反向 Kadane's Algorithm:计算数组从后往前的最大子数组和,代表进行一次反转后,可以将一些负数移动到后面或前面以优化结果。
我们需要计算出以下几个部分:
- 原始数组中正向的最大子数组和。
- 反向时的前缀和后缀的组合,找出反转后可以获得的最大子数组和。
答案¶
import java.util.Scanner;
public class MaxSubarraySumWithReverse {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取输入的 n
int n = scanner.nextInt();
// 读取数组
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = scanner.nextInt();
}
// 计算最大子数组和(不做反转)
long maxNormal = kadaneMaxSubarraySum(arr);
// 计算可能通过反转获得的最大子数组和
long maxWithReverse = maxSubarraySumWithReverse(arr);
// 输出最大值
System.out.println(Math.max(maxNormal, maxWithReverse));
scanner.close();
}
// Kadane's Algorithm 实现,计算最大子数组和
public static long kadaneMaxSubarraySum(int[] arr) {
long currentMax = arr[0];
long globalMax = arr[0];
for (int i = 1; i < arr.length; i++) {
currentMax = Math.max(arr[i], currentMax + arr[i]);
globalMax = Math.max(globalMax, currentMax);
}
return globalMax;
}
// 计算通过一次反转获得的最大子数组和
public static long maxSubarraySumWithReverse(int[] arr) {
int n = arr.length;
// 前缀最大和
long[] prefixMax = new long[n];
long currentPrefixSum = 0;
long maxPrefix = Long.MIN_VALUE;
for (int i = 0; i < n; i++) {
currentPrefixSum += arr[i];
maxPrefix = Math.max(maxPrefix, currentPrefixSum);
prefixMax[i] = maxPrefix;
}
// 后缀最大和
long[] suffixMax = new long[n];
long currentSuffixSum = 0;
long maxSuffix = Long.MIN_VALUE;
for (int i = n - 1; i >= 0; i--) {
currentSuffixSum += arr[i];
maxSuffix = Math.max(maxSuffix, currentSuffixSum);
suffixMax[i] = maxSuffix;
}
// 寻找通过一次反转可以得到的最大子数组和
long maxSumWithReverse = Long.MIN_VALUE;
for (int p = 0; p < n - 1; p++) {
maxSumWithReverse = Math.max(maxSumWithReverse, prefixMax[p] + suffixMax[p + 1]);
}
return maxSumWithReverse;
}
}
代码解释:¶
-
Kadane's Algorithm:用于计算数组的最大子数组和,这部分不考虑反转,直接应用 Kadane 算法。
-
maxSubarraySumWithReverse 函数:
- 计算数组的前缀和后缀最大值。
prefixMax[i]
表示从数组的开头到第i
个元素的最大子数组和。suffixMax[i]
表示从数组的第i
个元素到数组末尾的最大子数组和。- 在反转时,我们选择一个位置
p
,将数组分成前缀[1, p]
和后缀[p+1, n]
,分别反转这两部分。为了获得最优解,我们找出某个位置p
使得prefixMax[p] + suffixMax[p + 1]
最大。 - 比较最大值:我们取不反转时的最大子数组和
maxNormal
和反转后可能获得的最大子数组和maxWithReverse
中的较大值,作为最终的结果。
第三题:¶
题目描述¶
小红有两个长度为 n 的数组 {a_1, a_2, ..., a_n} 和 {b_1, b_2, ..., b_n} ,定义数组 a和数组 b 的距离为 (a_1-b_1)^2 + (a_2 - b_2)^2 + ... + (a_i - b_i)^2 + ... + (a_n - b_n)^2。 小红最多可以进行 k 次操作,每次操作可以选择数组 a 或数组 b 中的一个元素,将其加一或减一。请问小红最多可以将两个数组的距离缩小到多少。
输入描述: 第一行输入两个整数 n,k (1<=n<=100000, 0<=k<=1000000000) 分别代表数组中的元素数量、最多可以进行的操作次数。 第二行输入 n 个整数a_1, a_2, ..., a_n(1<=a_i<=1000000000) 表示数组 a。 第三行输入 n 个整数b_1, b_2, ..., b_n(1<=b_i<=1000000000) 表示数组 b。
输出描述: 输出一个整数,表示最多可以将两个数组的距离缩小到多少。由于答案可能很大,输出答案对 1000000000+7 取模的结果。
示例: 输入: 5 3 1 2 3 4 5 5 4 3 2 1 输出: 21
思路¶
-
初始距离计算:首先计算初始的距离,即对每一对元素 (ai,bi)(a_i, b_i)(ai,bi) 计算 (ai−bi)2(a_i - b_i)^2(ai−bi)2 的和。
-
优先减少差距大的项:我们希望尽量减小那些差值绝对值最大的项的平方值,因此每次操作我们需要选择绝对差值最大的元素进行调整。
-
调整后的差值:每次对差值最大的项进行调整,将其减少(加1或减1),然后重新计算这个位置的平方差。
-
贪心策略:每次找到当前最大差值的项,进行一次操作,使得距离尽可能减少。可以使用最大堆(优先队列)来追踪当前最大差值的位置和值。
-
取模计算:最后结果对 10^9 + 7 取模。
具体步骤:
- 初始化计算每一对元素 a_i 和 b_i 的差值平方,并计算出总距离。
- 将每一对元素的差值按绝对值大小放入优先队列。
- 每次从队列中取出当前绝对差值最大的项,执行一次操作(将其增大或减小1,取决于差值的符号),并更新优先队列中的该项。
- 重复该操作最多执行
k
次。 - 最后计算出更新后的距离,并对 10^9 + 7 取模。
代码¶
import java.util.PriorityQueue;
import java.util.Scanner;
public class MinimizeArrayDistance {
// 模数
static final long MOD = 1000000000 + 7;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 输入 n 和 k
int n = scanner.nextInt();
long k = scanner.nextLong();
// 输入数组 a 和 b
int[] a = new int[n];
int[] b = new int[n];
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
}
for (int i = 0; i < n; i++) {
b[i] = scanner.nextInt();
}
// 优先队列,存储绝对差值,最大堆
PriorityQueue<Integer> pq = new PriorityQueue<>((x, y) -> Integer.compare(Math.abs(y), Math.abs(x)));
// 初始计算距离并将差值放入优先队列
long initialDistance = 0;
for (int i = 0; i < n; i++) {
int diff = a[i] - b[i];
pq.offer(diff);
initialDistance += (long) diff * diff;
}
// 贪心减少距离
while (k > 0 && !pq.isEmpty()) {
int diff = pq.poll();
int newDiff;
if (diff > 0) {
newDiff = diff - 1;
} else if (diff < 0) {
newDiff = diff + 1;
} else {
newDiff = 0; // 如果差值为 0,不能再减少了
}
// 更新总距离
initialDistance -= (long) diff * diff;
initialDistance += (long) newDiff * newDiff;
pq.offer(newDiff);
k--;
}
// 输出结果,对 MOD 取模
System.out.println(initialDistance % MOD);
scanner.close();
}
}
并没有完全A