作为一名经历过多次技术面试的老兵,我深知数组类题目在算法面试中的重要性。数组作为最基础的数据结构,几乎出现在所有技术岗位的笔试和面试环节。今天我将分享7道LeetCode上经典的数组题目,涵盖简单到中等难度,每道题都配有详细解题思路和优化后的C++实现代码。
给定一个整数数组nums,将数组中的元素向右轮转k个位置,其中k是非负数。题目要求我们原地修改数组,不能使用额外的空间,即空间复杂度必须为O(1)。
这个问题的难点在于如何在有限的空间内完成元素的位置交换。最直观的解法是使用额外数组存储旋转后的结果,但这不符合题目要求。
经过多次实践和优化,我发现三次翻转法是最优雅的解决方案:
这种方法的正确性可以通过数学归纳法证明。以数组[1,2,3,4,5,6,7]和k=3为例:
在实际编码时,有几个关键点需要注意:
cpp复制class Solution {
void reverse(vector<int>& nums, int left, int right) {
while (left < right) {
swap(nums[left++], nums[right--]);
}
}
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k %= n; // 处理k大于n的情况
reverse(nums, 0, n - 1); // 整体翻转
reverse(nums, 0, k - 1); // 前k个翻转
reverse(nums, k, n - 1); // 剩余部分翻转
}
};
时间复杂度:O(n),因为每个元素被访问常数次
空间复杂度:O(1),完全原地操作
这种旋转方法在实际开发中很有用,比如图像旋转、环形缓冲区实现等场景。
给定一个大小为n的数组nums,返回其中的多数元素。多数元素是指在数组中出现次数大于⌊n/2⌋的元素。题目保证多数元素一定存在。
最直接的解法是用哈希表统计每个元素的出现次数,但这样空间复杂度是O(n)。
投票算法是解决这类问题的经典方法,其核心思想是"消消乐":
cpp复制class Solution {
public:
int majorityElement(vector<int>& nums) {
int candidate = nums[0], count = 1;
for (int i = 1; i < nums.size(); i++) {
if (count == 0) {
candidate = nums[i];
count = 1;
} else if (nums[i] == candidate) {
count++;
} else {
count--;
}
}
return candidate;
}
};
因为多数元素的数量超过一半,所以即使其他所有元素联合起来"抵消"它,最后剩下的也必定是多数元素。这个算法在数据流处理等场景特别有用,因为它只需要常数空间。
给定一个数组prices,其中prices[i]是第i天股票的价格。你只能选择某一天买入,并在未来某一天卖出,计算最大利润。
暴力解法是枚举所有可能的买入卖出组合,时间复杂度O(n²),显然不够高效。
关键观察:最大利润 = 最高卖出价 - 最低买入价(且买入在卖出前)。因此我们可以在遍历时:
cpp复制class Solution {
public:
int maxProfit(vector<int>& prices) {
int minPrice = INT_MAX;
int maxProfit = 0;
for (int price : prices) {
minPrice = min(minPrice, price);
maxProfit = max(maxProfit, price - minPrice);
}
return maxProfit;
}
};
这个问题的一个变种是允许多次买卖(LeetCode 122),此时可以采用贪心策略:只要今天价格比昨天高,就累加利润。这体现了算法设计中根据不同约束条件选择合适策略的重要性。
给定非负整数数组nums,你最初位于数组的第一个下标。每个元素表示在该位置可以跳跃的最大长度。判断是否能到达最后一个下标。
维护一个当前能到达的最远位置maxReach:
cpp复制class Solution {
public:
bool canJump(vector<int>& nums) {
int maxReach = 0;
for (int i = 0; i < nums.size(); i++) {
if (i > maxReach) return false;
maxReach = max(maxReach, i + nums[i]);
if (maxReach >= nums.size() - 1) return true;
}
return true;
}
};
在保证能到达终点的前提下,求最少跳跃次数。这个问题可以看作是按层BFS:
cpp复制class Solution {
public:
int jump(vector<int>& nums) {
int jumps = 0, curEnd = 0, farthest = 0;
for (int i = 0; i < nums.size() - 1; i++) {
farthest = max(farthest, i + nums[i]);
if (i == curEnd) {
jumps++;
curEnd = farthest;
}
}
return jumps;
}
};
跳跃游戏类问题在路径规划、网络路由等场景有实际应用。理解这类问题的解法有助于培养贪心算法的思维模式。
H指数是衡量学者科研产出的指标。定义为:一个科学家有h篇论文被引用至少h次,其他论文被引用不超过h次。
将引用次数降序排序后,找到最大的h满足citations[h-1] >= h:
cpp复制class Solution {
public:
int hIndex(vector<int>& citations) {
sort(citations.begin(), citations.end(), greater<int>());
int h = 0;
for (int i = 0; i < citations.size(); i++) {
if (citations[i] >= i + 1) h = i + 1;
else break;
}
return h;
}
};
对于大规模数据,可以使用计数排序将时间复杂度优化到O(n),但需要O(n)额外空间。这体现了时空权衡的思想。
在实际面试中,除了写出正确代码,与面试官的良好沟通同样重要。建议先阐述思路,再动手编码,最后进行测试和优化。