剑指offer-数据结构
1. 数组
1.1 数组中的重复数字
(1)面试题3(一):找出数组中重复的数字
题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2, 3, 1, 0, 2, 5, 3},那么对应的输出是重复的数字2或者3。
思路分析:
- 排序,然后遍历数组,时间复杂度为O(nlogn),空间复杂度为O(1);
- 哈希表,时间复杂度为O(n),空间复杂度也为O(n);
- 利用数字的范围为从0n-1和数组下标也正好为0n-1的特性来做,如果数组中没有重复的数字,那么数组中的第i个元素一定是在第i个位置,如果有重复的数字证明,数组中有些位置有重复的数字,有些位置没有重复的数字,从数组头开始遍历数组,将遇到的数字arri,和位置i进行比较,如果相等,则证明数字放在正确的位置继续向后扫描数组,如果不相等,则比较arr[i],和第arr[i](m)个位置的元素,如果这两个元素相等则证明找到了重复的元素,继续往后寻找,如果不相等,则交换这两个位置的元素,如此重复,直到数组的末尾为止。使用这种方式,虽然有两层循环,但是每个数组中的元素只需要改变两次位置就能找到正确的位置,所以时间复杂度为O(n),空间复杂度为O(1)。
// 参数:
// arr: 一个整数数组
// len: 数组的长度
// duplication: (输出) 数组中的所有重复的数字,使用set可以避免多次输出重复的数字
// 返回值:
// true - 输入有效,并且数组中存在重复的数字
// false - 输入无效,或者数组中没有重复的数字
bool FindDuplication(int arr[], int len, set<int>& duplication) {
// 指针duplication返回的是出现重复的数字的指针位置;
bool flag = false;
while (!duplication.empty())
duplication.clear();
if (arr == nullptr || len <= 0) {
return false;
}
for (int i = 0; i < len; i++) {
if (arr[i] < 0 || arr[i] > len - 1)
return false;
}
for (int i = 0; i < len; i++) {
while (arr[i] != i) {
if (arr[i] == arr[arr[i]]) {
// 找到了重复的数据
duplication.insert(arr[i]);
flag = true;
break;
}
// 交换arr[i]和arr[arr[i]]的值
int temp = arr[i];
arr[i] = arr[temp];
arr[temp] = temp;
}
}
return flag;
}
(2)面试题3(二):不修改数组找出重复的数字
题目:在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至
少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的
数组。例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的
输出是重复的数字2或者3。
分析思路:
思路一: 按照上面的思路,可以借助一个大小为n+1的数组,可以在O(n)的时间内完成不修改数组找出重复的数字;
思路二:可以使用二分查找的思路,假设一个数组的带下为8,18的中间数字为4,那么我们分别统计14的数字个数和58的数字的个数,如果14的数字的个数超过4那么这里面一定有重复的数字,如果58的数字的个数超过4,这里面的数字也一定有重复的数字,再接着把14或者5~8的范围缩小,继续比较,直到只剩下两个数字的时候分别统计两个数字在数组中重复出现的次数,这种方式二分有logn次,每一次需要遍历一整个数组,因此时间复杂度是O(nlogn),空间复杂度是O(1),相比较思路一是用时间换取空间。注意:这种思路不能保证找出数组中出现的所有重复的数字,比如数组{2, 3, 5, 4, 3, 2, 6, 7},这个数组是用思路二不能找出重复的数字2,因为在范围12内的数字有两个,而12内的数字2出现的次数也正好是2。
// 参数:
// numbers: 一个整数数组
// length: 数组的长度
// 返回值:
// 正数 - 输入有效,并且数组中存在重复的数字,返回值为重复的数字
// 负数 - 输入无效,或者数组中没有重复的数字
int countRange(int numbers[], int length, int start, int end) {
// 计算从start到end之间的数字一共有多少个
if (numbers == nullptr || length <= 0)
return -1;
int count = 0;
for (int i = 0; i < length; i++) {
if (numbers[i] >= start && numbers[i] <= end)
++count;
}
return count;
}
int getDuplication(int numbers[], int length) {
if (numbers == nullptr || length <= 0) {
// 输入的数组为空或者数组的长度为0,返回-1
return -1;
}
for (int i = 0; i < length; i++) {
// 数组中的数字的范围不符合题目的要求,返回-1
if (numbers[i] <= 0 || numbers[i] >= length)
return -1;
}
// 开始的数字应该是从1~n的范围
int start = 1;
int end = length - 1;
while (end >= start) {
int middle = (start + end) / 2;
int count = countRange(numbers, length, start, middle);
if (start == end) {
if (count > 1) {
return start;
} else {
break;
}
}
if (count > middle - start + 1) {
end = middle;
} else {
start = middle + 1;
}
}
return -1;
}
1.2 二维数组中的查找
面试题4:二维数组中的查找
题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按
照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个
整数,判断数组中是否含有该整数。
思路分析:从数组的右上角开始寻找,如果右上角的数字比目标数字要大,那么我们舍弃右上角数字所在的那一行,因为那一行的所有元素都比右上角的数字要大,如果右上角的数字比目标数字要小,那么我们舍弃右上角数字所在的那一行,因为那一行的所有数字都比右上角的数字要小,目标数字肯定也不在那一行。
class Solution {
public:
bool Find(int target, const vector<vector<int> > array) {
int row = array.size();
if (row <= 0) return false;
int col = array.at(0).size();
if (col <= 0) return false;
// 从数组的右上角开始寻找
int indexr = 0;
int indexc = col - 1;
bool flag = true;
while (flag) {
if (array[indexr][indexc] == target) {
return true;
} else if (array[indexr][indexc] > target && indexc > 0) {
indexc--;
} else if (array[indexr][indexc] < target && indexr < row - 1) {
indexr++;
} else {
return false;
}
}
return false;
}
};
2.字符串
为了节省内存,C/C++经常把常量字符串放到单独的一个内存区域,当几个指针赋值给相同的常量字符串的时候,它们会指向相同的内存地址,比如以下的程序:
#include <iostream>
using namespace std;
int main() {
char str1[] = "hello, world";
char str2[] = "hello, world";
char str3* = "hello, world";
char str4* = "hello, world";
if (str1 == str2) {
cout << "str1 and str2 are same\n";
} else {
cout << "str1 and str2 are not the same\n";
}
if (str3 == str4) {
cout << "str3 and str4 are same\n";
} else {
cout << "str3 and str4 are not the same\n";
}
return 0;
// 程序最终的输出结果为:
// str1 and str2 are not the same
// str3 and str4 are same
}
2.1 替换字符串
面试题5:替换空格
题目:请实现一个函数,把字符串中的每个空格替换成"%20"。例如输入“We are happy.”,
则输出“We%20are%20happy.”
注意:假设内存中已经有足够的空间足够我们使用"%20"替换掉原字符串中的空格。字符串的保存在字符串末尾会有’\0’标记字符串的结束,所以字符串的下标为从0~length.
class Solution {
public:
void replaceSpace(char* str,int length) {
int count = 0;
for (int i = 0; i < length; i++) {
if (str[i] == ' ')
++count;
}
int p1 = length;
int p2 = length + count * 2;
while (p1 != p2) {
if (str[p1] != ' ') {
str[p2--] = str[p1--];
} else if (str[p1] == ' ') {
str[p2--] = '0';
str[p2--] = '2';
str[p2--] = '%';
--p1;
}
}
}
};
相关题目:数组的合并
已知有两个数组A1和A2,并且A1和A2都是按升序排列的数组,并且保证A1数组的后面有足够的内存空间容纳数组A2的元素,请设计算法合并这两个数组。从两个数组的末尾开始,将两个数组中比较大的元素放入A1的末尾,数组指针往前移动,直到遍历完这两个数组。时间复杂度为O(m+n).
2.3 链表
2.3.1 从尾到头打印链表
面试题6:从尾到头打印链表
题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值。
思路分析:借助栈来实现从尾到头打印链表
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> vec;
if (head == nullptr) return vec;
stack<int> sta;
ListNode* ptr = head;
while (ptr != nullptr) {
sta.push(ptr -> val);
ptr = ptr -> next;
}
while(!sta.empty()) {
vec.push_back(sta.top());
sta.pop();
}
return vec;
}
};