一、vector和list的区别
1.1 底层数据结构
vector 使用动态数组作为底层数据结构,元素在内存中是连续存储的;
list 使用双向链表作为底层数据结构,元素在内存中通过节点相互连接。
1.2 插入和删除操作
vector 在尾部插入或删除元素效率高,但在中间或头部插入/删除元素时需要移动后续元素,效率较低;
list 在任意位置插入或删除元素的效率都很高,因为它只需修改相邻节点的指针。
1.3 随机访问
vector允许通过下标进行快速随机访问,因为元素在内存中是连续的;
list不支持随机访问,访问元素需要从头结点开始遍历列表,效率较低。
1.4 内存分配
vector需要预先分配一块连续内存,如果元素数量动态增长,可能需要重新分配内存,导致内存浪费或复制操作;
list在插入或释放时只需分配或释放对应节点的内存,内存占用相对较小。
二、vector删除元素、删除区间的理解,erase返回什么?
C++的标准库中,std::vector是一个动态数组容器,你可以使用erase函数来删除单个元素或一个区间内的元素。erase函数的返回值是指向被删除元素之后的迭代器,或者如果删除的是最后一个元素,则返回end()。
如下例子所示:
#include
#include
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 删除单个元素
std::vector<int>::iterator it = numbers.begin() + 2;
it = numbers.erase(it); // 删除索引为2的元素(值为3)
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// 删除一个区间
std::vector<int>::iterator first = numbers.begin() + 1;
std::vector<int>::iterator last = numbers.begin() + 3;
numbers.erase(first, last); // 删除区间[1, 3)
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
首先删除了索引为2的元素(值为3),erase返回的迭代器指向被删除元素之后的位置,所以此时it指向了原来的索引为3的元素。然后,我们删除了区间[1, 3)内的元素,即索引1和2的元素,最终得到修改后的numbers。
三、数组和链表区别(相当于是对第一题的详细解释)?
3.1 存储方式
a. 数组:数组是一种连续的存储结构,元素在内存中按线性顺序排列,这使得数组支持随机访问,可以通过索引快速访问任何元素;
b.链表:链表是一种非连续的存储结构,元素以节点的形式存在,每个节点包含数据和指向下一个节点的指针;链表不支持随机访问,必须从头节点开始遍历才能找到特定元素。
3.2 大小固定性
a.数组:数组的大小通常是固定的,一旦分配内存空间,极不容易动态扩展或缩小,此时可通过动态数组来解决,如std::vector;
b.链表 链表大小可以动态增减,节点可以根据需要进行动态分配和释放。
3.3 插入和删除
a.数组:在数组中插入或删除元素通常需要移动其他元素,这可能涉及到大量的数据搬迁,导致性能下降;
b.链表:在链表中插入或删除元素只需要调整节点的指针,不需要移动大量的数据,因此插入和删除操作通常更高效。
四、单向链表,如何找到中间的节点
可使用双指针技巧,其中一个指针每次移动一个节点,另一个指针每次移动两个节点。当快指针到达链表尾部时,慢指针就会指向链表的中间节点。
参考代码如下:
#include
struct ListNode {
int data;
ListNode* next;
ListNode(int val) : data(val), next(nullptr) {}
};
ListNode* findMiddle(ListNode* head) {
if (head == nullptr) {
return nullptr;
}
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next; // 慢指针每次移动一个节点
fast = fast->next->next; // 快指针每次移动两个节点
}
return slow;
}
int main() {
// 创建一个单链表:1 -> 2 -> 3 -> 4 -> 5
ListNode* head = new ListNode(1);
head->next = new ListNode(2);
head->next->next = new ListNode(3);
head->next->next->next = new ListNode(4);
head->next->next->next->next = new ListNode(5);
// 找到中间节点
ListNode* middle = findMiddle(head);
if (middle != nullptr) {
std::cout << "中间节点的值为: " << middle->data << std::endl;
} else {
std::cout << "链表为空或长度为偶数,没有中间节点。" << std::endl;
}
// 释放链表内存
while (head != nullptr) {
ListNode* temp = head;
head = head->next;
delete temp;
}
return 0;
}
五、时间复杂度的概念,如何计算
时间复杂度通常用大O符号(O)来表示,表示算法的运行时间与输入规模之间的增长关系。常见的时间复杂度包括 O(1)、O(log n)、O(n)、O(n log n)、O(n2)、O(2n) 等。
5.1:O(1) - 常数时间复杂度:算法的运行时间是常数,与输入规模无关。例如,访问数组中的元素。
5.2:O(log n) - 对数时间复杂度:算法的运行时间随输入规模呈对数增长。例如,二分查找。
5.3:O(n) - 线性时间复杂度:算法的运行时间与输入规模成正比。例如,遍历数组。
5.4:O(n log n) - 线性对数时间复杂度:算法的运行时间随输入规模呈 n log n 增长。例如,快速排序和归并排序。
5.5:O(n^2) - 平方时间复杂度:算法的运行时间与输入规模的平方成正比。例如,简单的嵌套循环。
5.6:O(2^n) - 指数时间复杂度:算法的运行时间随输入规模成指数级增长。例如,暴力解法。
计算时间复杂度通常涉及分析算法中的循环和递归结构。你需要考虑算法中执行次数最多的那部分代码,并用输入规模 n 来表示。在分析过程中,通常忽略常数因子和低阶项,只关注增长最快的项,因为它们在大规模数据下占主导地位。
六、归并排序算法的实现
流程:
6.1、将待排序数组分为两半,直到每个子数组只包含一个元素。
6.2、递归地对左半部分和右半部分进行排序。
6.3、合并两个有序子数组为一个大的有序数组。
例:
#include
#include
// 合并两个有序数组为一个有序数组
void merge(std::vector<int>& arr, int left, int middle, int right) {
int n1 = middle - left + 1;
int n2 = right - middle;
std::vector<int> leftArr(n1);
std::vector<int> rightArr(n2);
// 将数据拷贝到左右两个子数组中
for (int i = 0; i < n1; i++) {
leftArr[i] = arr[left + i];
}
for (int i = 0; i < n2; i++) {
rightArr[i] = arr[middle + 1 + i];
}
// 归并过程
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j]) {
arr[k] = leftArr[i];
i++;
} else {
arr[k] = rightArr[j];
j++;
}
k++;
}
// 处理剩余元素
while (i < n1) {
arr[k] = leftArr[i];
i++;
k++;
}
while (j < n2) {
arr[k] = rightArr[j];
j++;
k++;
}
}
// 归并排序主函数
void mergeSort(std::vector<int>& arr, int left, int right) {
if (left < right) {
int middle = left + (right - left) / 2;
// 递归排序左右两半
mergeSort(arr, left, middle);
mergeSort(arr, middle + 1, right);
// 合并已排序的两部分
merge(arr, left, middle, right);
}
}
int main() {
std::vector<int> arr = {38, 27, 43, 3, 9, 82, 10};
int n = arr.size();
std::cout << "Original Array: ";
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
mergeSort(arr, 0, n - 1);
std::cout << "Sorted Array: ";
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
七、如何对排好序的数组去除重复元素?
流程:
7.1 初始化两个指针,一个指向当前元素,另一个指向下一个元素。
7.2 检查当前元素和下一个元素是否相等。
7.3 如果相等,将下一个元素删除,继续检查下一个元素,直到找到不同的元素。
7.4 如果不同,将第一个指针移到下一个位置,继续检查下一个元素。
#include
std::vector<int> removeDuplicates(std::vector<int>& sortedArray) {
int n = sortedArray.size();
if (n <= 1) {
return sortedArray; // 如果数组为空或只有一个元素,无需去重
}
int index = 0; // 用于记录非重复元素的位置
for (int i = 1; i < n; i++) {
if (sortedArray[i] != sortedArray[index]) {
index++;
sortedArray[index] = sortedArray[i];
}
}
sortedArray.resize(index + 1); // 调整数组大小,去除重复元素后的大小
return sortedArray;
}
int main() {
std::vector<int> sortedArray = {1, 2, 2, 3, 4, 4, 4, 5, 5, 6, 6};
std::cout << "原始数组:";
for (int num : sortedArray) {
std::cout << num << " ";
}
std::cout << std::endl;
sortedArray = removeDuplicates(sortedArray);
std::cout << "去除重复后的数组:";
for (int num : sortedArray) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}