数组
1
在包含 n 个整数的已排序数组中,确定某个整数是否出现超过 n/2 次所需的最小比较次数是( ):
如果选择Θ(1),请考虑示例:
- {1, 2, 2, 2, 4, 4}
- {1, 1, 2, 2, 2, 2, 3, 3}
判断已排序(升序)数组中是否存在出现次数超过 n/2 次的整数的最佳方法是使用二分查找。具体步骤如下:
- 通过分治技术在 O(log(n)) 时间内找到目标元素的首次出现位置
i
- 通过分治技术在 O(log(n)) 时间内找到目标元素的末次出现位置
j
- 计算该元素的出现次数:
count = j - i + 1
时间复杂度分析:
O(log n) [首次查找]
+ O(log n) [末次查找]
+ 1 [计数计算]
= O(log n)
2
一个算法对一组键值来自线性有序集合的数据项执行 $(logN)^{1/2}$ 次查找操作、$N$ 次插入操作、$(logN)^{1/2}$ 次删除操作和 $(logN)^{1/2}$ 次减键操作。对于删除操作,会提供指向必须删除记录的指针;对于减键操作,也会提供指向其键值被减少的记录的指针。如果目标是通过考虑所有操作实现最佳的总渐近时间复杂度,那么以下哪种数据结构最适合该算法使用?( )
- 无序数组:插入操作的时间复杂度为 O(1),因为可以在末尾直接插入。
- 最小堆:插入操作需要 O(log n) 时间(参考二叉堆操作)。
- 有序数组:插入操作可能需要移动所有元素,最坏情况下为 O(n)。
- 有序双向链表:需要 O(n) 时间来找到插入位置。
由于插入操作的数量在渐近意义上最高(N 次),因此优先选择插入效率最高的 无序数组。尽管其他操作如删除和减键可能需要额外开销,但题目明确提供了直接指针,使得这些操作可在 O(1) 时间内完成,从而整体时间复杂度由插入操作主导。
3
考虑一个包含正数和负数的数组。将相同符号的数字分组(即所有正数在一边,所有负数在另一边)的算法最坏情况下的时间复杂度是多少?( )
解释:
- 可以采用快速排序的分区思想进行分组
- 通过单次遍历即可完成符号分组操作
- 时间复杂度与数组长度呈线性关系
- 最终时间复杂度为 O(N)
4
设 A[1...n]
是一个包含 n
个互不相同数字的数组。若 i < j
且 A[i] > A[j]
,则称 (i, j)
为 A
的一个逆序对。在任意 n
个元素的排列中,逆序对的期望数量是多少?( )
- 数组中共有 $ \frac{n(n-1)}{2} $ 个满足 $ i < j $ 的数对组合
- 对于任意一对 $ (a_i, a_j) $,由于元素互异且随机排列,出现逆序对的概率为 $ \frac{1}{2} $
- 根据期望的线性性质,总期望为: $$ \frac{1}{2} \times \frac{n(n-1)}{2} = \frac{n(n-1)}{4} $$
5
考虑一个二维数组 A[20][10]
。假设每个内存单元存储 4
个字,数组 A
的基地址为 100
,元素按行优先顺序存储且第一个元素是 A[0][0]
。A[11][5]
的地址是多少?( )
计算行偏移:
$$ \text{行偏移} = 11 \times 10 \times 4 = 440 $$
因此,A[11][0] 的地址为:
$$ 100 + 440 = 540 $$
计算列偏移: $$ \text{列偏移} = 5 \times 4 = 20 $$ 最终地址为: $$ 540 + 20 = 560 $$
6
数组 A
包含 n
个整数,位于 A[0], A[1]...A[n-1]
。要求将数组元素循环左移 k
位,其中 1 <= k <= (n-1)
。给出一个不完整的算法,在不使用额外数组的情况下以线性时间完成此操作。请通过填充空白处来完善该算法。假设所有变量均已正确声明。
min = n;
i = 0;
while(___________){
temp = A[i];
j = i;
while(________){
A[j] = ________;
j = (j + k) % n;
If(j < min) then min = j;
}
A[(n + i - k) % n] = _________;
i = ___________;
}
在题目给出的五个空白中,最后两个空白必须是
temp
和 i+1
,因为第四、第五个空白的所有选项都包含这两个值。现在考虑第一个空白,当初始时 i=0
且 min=n
时,若条件为 i>min
则会直接退出 while
循环,因此第一个空白应为 i < min
,这意味着选项 (B) 或 (D) 可能正确。假设选项 (B) 正确,则第二个 while
循环的条件为 j!=(n+i) mod n
。由于 (n+i) mod n=i
且代码第 3 行将 i
赋值给 j
,此时 j
始终等于 i
,导致控制流永远不会进入内部循环,而实际上需要进入内部循环才能实现左移 k
位的操作。因此选项 (D) 正确。7
以下哪项正确声明了一个数组?( )
解析:
选项 A 正确地声明了一个整数数组 geeks
,其大小为 20。
int
指定了数组元素的数据类型为整型。geeks
是数组的名称。[20]
表示数组可存储 20 个元素。
其他选项均不符合 C/C++ 的数组声明语法规范。
8
在 C++ 中,一个三维数组声明为 int A[x][y][z]
。假设数组元素按行优先顺序存储且索引从 0 开始。那么,位置 A[p][q][r]
的地址可以计算如下(其中 w 是整数的字长):( )
解析:
行优先存储原理
三维数组int A[x][y][z]
在内存中按行优先顺序存储,其访问顺序遵循:- 第一维(x)变化最慢
- 第二维(y)次之
- 第三维(z)变化最快
地址计算步骤
- 定位到第 p 行
需跨越前 p 行的所有元素: $$ \text{元素数量} = p \times (y \times z) $$ - 定位到第 q 列
需跨越当前行中前 q 列的所有元素: $$ \text{元素数量} = q \times z $$ - 定位到第 r 个元素
直接取当前列的第 r 个元素: $$ \text{元素数量} = r $$
- 定位到第 p 行
总偏移量与地址公式
- 总元素偏移量: $$ \text{Total Offset} = y \cdot z \cdot p + z \cdot q + r $$
- 最终地址: $$ \text{Address} = &A[0][0][0] + w \cdot (\text{Total Offset}) $$ 即: $$ &A[0][0][0] + w \cdot (y \cdot z \cdot p + z \cdot q + r) $$
结论
选项 B 的表达式完全匹配上述推导结果,因此 选项 B 正确。
9
设 A 是一个 n×n 的方阵。考虑以下程序。预期输出是什么?
C = 100
for i = 1 to n do
for j = 1 to n do
{
Temp = A[i][j] + C
A[i][j] = A[j][i]
A[j][i] = Temp - C
}
for i = 1 to n do
for j = 1 to n do
Output(A[i][j])
- 关键分析:观察第一个循环中的三步操作:
Temp = A[i][j] + C
A[i][j] = A[j][i]
A[j][i] = Temp - C
- 执行效果:当
i ≠ j
时,该操作实现了A[i][j]
与A[j][i]
的值交换(通过中间变量Temp
)。 - 双重交换机制:由于循环遍历所有
(i, j)
组合,每个非对角线元素会被交换两次(一次作为(i,j)
,另一次作为(j,i)
),从而恢复原始值。 - 结论:矩阵整体未发生任何变化,最终输出仍为原矩阵 A。
10
程序 P 读取 500 个范围在 [0..100] 的整数,这些整数代表 500 名学生的成绩。然后程序输出每个高于 50 的成绩的出现频率。对于 P 来说,存储这些频率的最佳方式是什么?( )
解析
- 题目要求统计高于 50 的成绩的频率
- 成绩范围是 [0..100],因此高于 50 的有效成绩范围是 51 到 100(共 50 个不同值)
- 每个值对应的频率可以用一个长度为 50 的数组存储
- 其他选项要么空间浪费(如 100/500/550),要么不符合实际需求
- 因此,一个包含 50 个数字的数组是最优解
11
以下程序中,调用函数 fun
的正确方式是什么?( )
#include <bits/stdc++.h>
#include <iostream>
using namespace std;
void fun(char* arr) {
int i;
unsigned int n = strlen(arr);
for (i = 0; i < n; i++)
cout << " " << arr[i];
}
// 主程序
int main() {
char arr[] = {'g', 'e', 'e', 'k', 's', 'q', 'u', 'i', 'z'};
// 如何在此处调用上述函数以打印字符元素?
return 0;
}
- 数组退化规则:在 C/C++ 中,数组作为函数参数传递时会自动退化为指向其首元素的指针
- 参数匹配:
char arr[]
在函数参数中等价于char*
类型 - 正确调用:直接使用
arr
即可(等价于&arr[0]
),与函数参数类型char*
完全匹配 - 错误选项分析:
fun(&arr)
实际传递的是char(*)[9]
类型(指向数组的指针)_arr
不是合法标识符None
选项不符合实际需求
12
设 A 是一个 n x n 的矩阵。考虑以下程序。预期输出是什么?
void fun(int A[][N]) {
for (int i = 0; i < N; i++)
for (int j = i + 1; j < N; j++)
swap(A[i][j], A[j][i]);
}
- 解析:
在函数fun
中,通过双重循环遍历矩阵的上三角区域(即 $i < j$ 的位置)。- 对于每个元素 $A[i][j]$,将其与 $A[j][i]$ 交换。
- 这一操作实际上将矩阵的行和列互换位置,最终得到的是矩阵 $A$ 的转置矩阵。
因此,正确答案是 (C)。
13
以下哪项是数组的局限性?( )
假设我们声明一个数组:
arr[5] = {1, 2, 3};
此时数组的大小被声明为 5,但实际仅初始化了 3 个元素。这种预分配空间与实际使用量的差异会直接导致内存资源的浪费。因此选项(D)描述的情况是数组的典型局限性。
14
考虑以下程序,该程序基本上在做什么?
#include<bits/stdc++.h>
using namespace std;
void print(char a[], int n, int ind) {
for(int i = ind; i < n + ind; i++)
cout << a[(i % n)] << " ";
}
int main() {
char a[] = {'A','B','C','D','E','F'};
int n = sizeof(a)/sizeof(a[0]);
print(a, n, 3);
return 0;
}
在上述程序中,我们只是将数组元素循环右移 3 位。循环从 i=3
运行到 9
,在循环内部:
a[3%6] = a[3] = 'D'
a[4%6] = a[4] = 'E'
a[5%6] = a[5] = 'F'
a[6%6] = a[0] = 'A'
a[7%6] = a[1] = 'B'
a[8%6] = a[2] = 'C'
因此选项 (B) 正确。
15
请填写空白以完成程序,实现将数组旋转 d 个元素。
/*Function to left rotate arr[] of size n by d*/
void Rotate(int arr[], int d, int n) {
int p = 1;
while(_______) {
int last = arr[0];
for(int i = 0; _______i++) {
arr[i] = arr[i+1];
}
__________
p++;
}
}
解析:
第一个空白处(while 循环条件)
p <= d
因为初始值p = 1
,需要循环执行d
次(每次左移一个元素),所以条件应为p <= d
。第二个空白处(for 循环条件)
i < n - 1
循环需将前n-1
个元素依次前移,因此终止条件为i < n - 1
。第三个空白处(更新最后一个元素)
arr[n - 1] = last;
将原首元素last
赋值给数组末尾,完成单次左移操作。
综上,选项 (A) 正确。
16
考虑以下程序。预期输出是什么?
void fun(int arr[], int start, int end) {
while (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
}
该函数通过交换首尾元素逐步向中间移动,最终实现数组反转