题目 1:移除有序数组中的重复元素
题目描述
给定一个升序排列 的数组 nums,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。不要使用额外的数组空间,必须在原地修改输入数组并在使用 O (1) 额外空间的条件下完成。
解题思路
采用快慢双指针法:
- 慢指针
slow指向当前已去重数组的最后一个位置 - 快指针
fast遍历整个数组 - 当
nums[fast]与nums[slow]不相等时,说明遇到了新元素,将慢指针后移一位,把快指针的值复制到慢指针位置 - 最终慢指针索引 + 1 就是去重后的数组长度
代码实现
cpp
运行
cpp
int removeDuplicates(vector<int>& nums) {
if (nums.empty()) return 0;
int slow = 0;
for (int fast = 1; fast < nums.size(); fast++) {
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}
复杂度分析
- 时间复杂度:O (n),仅遍历数组一次
- 空间复杂度:O (1),原地修改,无额外空间开销
面试考点
双指针思想是数组题的核心技巧,面试官常延伸问:如果允许每个元素最多出现 2 次怎么改?只需将判断条件改为 nums[fast] != nums[slow-1] 即可。
题目 2:旋转数组
题目描述
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。要求使用空间复杂度 O (1) 的原地算法解决。
解题思路
经典三次反转法,无需额外空间:
- 先反转整个数组
- 反转前 k 个元素
- 反转后 n-k 个元素 注意:k 可能大于数组长度,需要先对 k 取模
k %= n。
代码实现
cpp
运行
cpp
void reverse(vector<int>& nums, int left, int right) {
while (left < right) {
swap(nums[left], nums[right]);
left++;
right--;
}
}
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k %= n;
reverse(nums, 0, n - 1); // 反转整个数组
reverse(nums, 0, k - 1); // 反转前k个
reverse(nums, k, n - 1); // 反转后n-k个
}
复杂度分析
- 时间复杂度:O (n),每个元素被反转两次,总操作次数为 2n
- 空间复杂度:O (1),原地操作
面试考点
考察数组的原地操作技巧。面试官常追问:向左旋转怎么实现?如果用额外数组怎么写?对比不同方案的时空开销。
题目 3:最长公共前缀
题目描述
编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。
解题思路
纵向扫描法: 以第一个字符串为基准,逐字符检查其他所有字符串在该位置是否相同,遇到不同字符或某字符串提前结束时停止,返回当前前缀。
代码实现
cpp
运行
cpp
string longestCommonPrefix(vector<string>& strs) {
if (strs.empty()) return "";
// 以第一个字符串为基准
for (int i = 0; i < strs[0].size(); i++) {
char c = strs[0][i];
// 检查其他所有字符串的第i位
for (int j = 1; j < strs.size(); j++) {
// 其他字符串长度不足i,或字符不相等
if (i == strs[j].size() || strs[j][i] != c) {
return strs[0].substr(0, i);
}
}
}
return strs[0];
}
复杂度分析
- 时间复杂度:O (m*n),m 是字符串平均长度,n 是字符串数量
- 空间复杂度:O (1)
面试考点
考察字符串边界处理能力。延伸问法:用分治法怎么实现?字典树(Trie)解法的优劣?
题目 4:两数之和
题目描述
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值 target 的那两个整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案,且同一个元素不能重复使用。
解题思路
哈希表一次遍历法 : 遍历数组时,用哈希表存储「元素值 → 索引」的映射。对于当前元素 nums[i],检查 target - nums[i] 是否在哈希表中:
- 存在:直接返回两个索引
- 不存在:将当前元素和索引存入哈希表,继续遍历
代码实现
cpp
运行
cpp
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hash;
for (int i = 0; i < nums.size(); i++) {
int complement = target - nums[i];
if (hash.count(complement)) {
return {hash[complement], i};
}
hash[nums[i]] = i;
}
return {};
}
复杂度分析
- 时间复杂度:O (n),仅遍历一次,哈希表查找为 O (1)
- 空间复杂度:O (n),哈希表最多存储 n 个元素
面试考点
考察空间换时间的优化思想。面试官常对比:暴力法 O (n²)、排序 + 双指针法、哈希表法三种方案的优劣与适用场景。
文档 2:链表专项面试题
题目 1:反转单链表
题目描述
给你单链表的头节点 head,请你反转链表,并返回反转后的链表。分别用迭代和递归两种方式实现。
解题思路
迭代法(三指针) : 用三个指针 prev、curr、next 遍历链表,逐个改变节点的 next 指向,最终 prev 成为新的头节点。
递归法: 递归反转当前节点之后的子链表,然后将当前节点的下一个节点的 next 指向当前节点,当前节点的 next 置空。
代码实现
cpp
运行
cpp
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
// 迭代法
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr) {
ListNode* next = curr->next; // 保存下一个节点
curr->next = prev; // 反转指针
prev = curr; // prev后移
curr = next; // curr后移
}
return prev;
}
// 递归法
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;
ListNode* newHead = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return newHead;
}
复杂度分析
- 迭代法:时间 O (n),空间 O (1)
- 递归法:时间 O (n),空间 O (n)(递归栈开销)
面试考点
链表基础必考题。面试官常延伸:反转链表的前 k 个节点?反转指定区间内的链表?
谢谢