剑指offer 学习笔记 数组
int GetSize(int data[]) {
return sizeof(data);
}
int main() {
int data[5] = { 1,2,3,4,5 };
cout << sizeof(data) << endl;
int *p = data;
cout << sizeof(p) << endl;
cout << GetSize(data) << endl;
return 0;
}
以上代码输出为20 4 4,sizeof(data)求的是数组大小,p虽然和数组名一样能代表指向第一个元素的指针,但本质上它只是指针,在32位下占4字节。C/C++中当数组作为函数的参数传递时,数组自动退化为同类型的指针,因此函数返回值为4。
面试题3:数组中重复的数字
(1)在一个长度为n的数组里的所有数字都在0~n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。
方法一:将数组排序,然后从排序的数组中可以很快找到重复数字。排序一个长度为n的数组需要O(nlogn)的时间。
方法二:哈希表,从头到尾顺序扫描数组,每扫描到一个元素时,都可以用O(1)的时间判断哈希表中是否已经有该数字,如果没有就将这个数字加入哈希表,如果哈希表里已经存在该数字,就找到。此算法时间复杂度为O(n),但它提升的时间效率是以一个大小为O(n)的哈希表为代价的。
方法三:从头到尾依次扫描数组中每个数字,当扫描到下标为i的数字m时,首先比较i和m是否相等,如相等,则扫描下一个数字,如不相等,将下标为m的数字与m进行比较,如果相等,则找到一个重复数字(此数字在下标m和i处都出现了),如不相等,就把下标是m的数字和m交换,这样m就到了属于它的位置。接下来继续重复比较、交换这个过程,直到找到重复数字:
#include <iostream>
using namespace std;
bool duplicate(int nums[], int length, int* duplication) {
if (length <= 0 || nums == nullptr) {
return false;
}
for (int i = 0; i < length; ++i) {
if (nums[i] > (length - 1) || nums[i] < 0) { // 如果下标为i的值超出了题目范围
return false;
}
}
for (int i = 0; i < length; ++i) {
while (nums[i] != i) { // 如下标和数字值不相同
if (nums[i] != nums[nums[i]]) {
int temp = nums[i];
nums[i] = nums[temp];
nums[temp] = temp; // 交换下标为i的值和下标为nums[i]的值
} else { // 当下标为i的值与下标为nums[i]的值相等
*duplication = nums[i];
return true;
}
}
}
return false;
}
int main() {
int data[5] = { 0,1,0,0,3 };
int duplication = -1;
if (duplicate(data, sizeof(data) / sizeof(*data), &duplication)) {
cout << "重复的数字为:" << duplication << endl;
}
return 0;
}
总的时间复杂度为O(n),空间复杂度为O(1)。
(2)在一个长度为n+1的数组里,所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3。
方法一:创建一个长度为n+1的辅助数组,然后逐一把数字复制到辅助数组。如果被复制的数字是m,那么就把它复制到辅助数组下标为m的位置,如果这个位置已经有值,那么就能发现重复的数字了。这个方案需要O(n)的辅助空间,时间复杂度为O(n)。
方法二:把从1~n的数字从中间的数字m分为两部分,前面一半为1~m,后面一半为m+1~n。如果1~m的数字数目超过m,那么这一半的区间里一定包含重复的数字,否则另一半m+1~n的区间里一定包含重复的数字:
#include <iostream>
using namespace std;
int countRange(int nums[], int length, int start, int end) {
int count = 0;
for (int i = 0; i < length; ++i) {
if (nums[i] >= start && nums[i] <= end) { // 遍历数组,找到指定数字范围内的数字个数
++count;
}
}
return count;
}
int dup(int nums[], int length) {
if (length < 0 || nums == nullptr) { // 保证nums[]中的数字在1~n范围内并且nums不为空指针
return -1;
}
int start = 1; // 最小值为1
int end = length - 1; // 最大值为n
while (start <= end) {
int middle = start + ((end - start) >> 1);
int count = countRange(nums, length, start, middle);
if (start == end) {
if (count > 1) { // 若此时count>1说明找到了重复数字
return start;
} else { // 若此时count也为1,说明输入数据有误,这也是为什么不在开始检查输入数组是否合法
break;
}
}
if (count > middle - start + 1) { // 如果start和middle范围内的数字个数大于start和middle之间的数(包括两端),则重复的数字在此范围内
end = middle;
} else { // 否则说明重复数字在后面范围内,注意此处必须用else而不能重新写一个if语句,重新写if有可能造成两个if判断都失败
start = middle + 1;
}
}
}
int main() {
int data[6] = { 2,1,4,3,5,5 };
int length = sizeof(data) / sizeof(*data);
int duplication = dup(data, length);
if (duplication != -1) {
cout << "重复数字为" << duplication << endl;
}
return 0;
}
这种方法按二分法思路,countRange函数被调用O(logn)次,每次需O(n)时间,总的时间复杂度为O(nlogn),空间复杂度为O(1),与第一种方法相比,相当于以时间换空间。并且这个方法不能找出所有的重复数字。
二维数组的查找:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序,请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数:
方法:首先选取数组中右上角的数字,如果该数字等于要查找的数字,则查找过程结束;如果该数字大于要查找的数字,则剔除这个数字所在的列;如果该数字小于要查找的数字,则剔除这个数字所在的行:
#include <iostream>
using namespace std;
bool Find(int* matrix, int columns, int rows, int number) {
if (matrix == nullptr || columns <= 0 || rows <= 0) { // 如果传入的是空数组或行列数小于0,返回false
return false;
}
int row = 0;
int column = columns - 1;
while (row < rows && column >= 0) {
if (matrix[row * columns + column] > number) { // 如果目标数小于右上角
--column; // 去掉最后一列
} else if (matrix[row * columns + column] < number) { // 如果目标数大于右上角
++row; // 去掉最上面一行
} else {
return true;
}
}
return false;
}
int main() {
int nums[9] = { 1,2,3,4,5,6,7,8,9 };
cout << Find(nums, 3, 3, 8) << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)