目录
14.map unordered_map的区别,哪个内存利用率高
1.经典排序复杂度分析及常考排序算法
经典常考排序算法:
1.归并排序
void merge(int arr[], int l, int m, int r)
{
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;
int L[n1], R[n2];
for (i = 0; i < n1; i++)
L[i] = arr[l + i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1+ j];
i = 0;
j = 0;
k = l;
while (i < n1 && j < n2)
{
if (L[i] <= R[j])
{
arr[k] = L[i];
i++;
}
else
{
arr[k] = R[j];
j++;
}
k++;
}
while (i < n1)
{
arr[k] = L[i];
i++;
k++;
}
while (j < n2)
{
arr[k] = R[j];
j++;
k++;
}
}
void mergeSort(int arr[], int l, int r)
{
if (l < r)
{
int m = l+(r-l)/2;
mergeSort(arr, l, m);
mergeSort(arr, m+1, r);
merge(arr, l, m, r);
}
}
2.快速排序
#include<iostream>
using namespace std;
void quickSort(int a[], int m,int n);
int partion(int a[], int m, int n);
int main()
{
int a[] = { 6,1,2,7,9,3,4,5,10,8 };
int m = 0;
int n = (sizeof(a) / 4)-1;
quickSort(a, m,n);
for (int i = 0; i < 10; i++)
{
cout << a[i] << " ";
}
}
void quickSort(int a[], int l, int r)
{
if (l < r)
{
int q = partion(a, l, r);
quickSort(a, l, q - 1 );
quickSort(a, q + 1, r);
}
}
int partion(int a[], int begin, int end)
{
int stone = a[begin];
while (begin < end)
{
while (begin < end && a[end] > stone)
{
end--;
}
swap(a[begin], a[end]);
while (begin < end && a[begin] <= stone)
{
begin++;
}
swap(a[begin], a[end]);
}
return begin;
}
3.冒泡排序
void bubbleSort(vector<int>& a)
{
bool swapp = true;
while(swapp){
swapp = false;
for (size_t i = 0; i < a.size()-1; i++) {
if (a[i]>a[i+1] ){
swap(a[i],a[i+1]);
/*a[i] += a[i+1];
a[i+1] = a[i] - a[i+1];
a[i] -=a[i+1];
*/
swapp = true;
}
}
}
}
4.选择排序
void selectionSort(vector<int>& arr) {
int len = arr.size();
int minIndex, temp;
for (var i = 0; i < len - 1; i++) {
minIndex = i;
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
swap(arr[i], arr[minIndex]);
}
return;
}
2.判断链表是否有环
答:
bool hasCycle(ListNode *head) {
if(head == NULL) return false;
ListNode* fast = head;
ListNode* slow = head;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if(slow == fast) return true;
}
return false;
}
3.当排序几十个数的时候用哪种,几十万个数的时候用哪种?
答:
影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:
1.待排序的记录数目n的大小;
2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;
3.关键字的结构及其分布情况;
4.对排序稳定性的要求。
设待排序元素的个数为n.
1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序。
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序 : 如果内存空间允许且要求稳定性的
归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。
2) 当n较大,内存空间允许,且要求稳定性 =》归并排序
3)当n较小,可采用直接插入或直接选择排序。
直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。
直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序
4)一般不使用或不直接使用传统的冒泡排序。
5)基数排序
它是一种稳定的排序算法,但有一定的局限性:
1、关键字可分解。
2、记录的关键字位数较少,如果密集更好
3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。
4.判断一个字符串是否为另一个字符串的子串?
答:
方法1:调用系统函数
- string::npos意味着没有找到,我一个无效的标记,一般等于-1
if (a.find(b) != string::npos)
cout << b << " is substring of " << a;
方法2:KMP算法
int* getNext(string p)
{
int* next = new int[p.length()];
next[0] = -1; //while the first char not match, i++,j++
int j = 0;
int k = -1;
while (j < (int)p.length() - 1)
{
if (k == -1 || p[j] == p[k])
{
j++;
k++;
next[j] = k;
}
else
{
k = next[k];
}
}
return next;
}
int KMP(string T,string p)
{
int i=0;
int j=0;
int* next=getNext(T);
while (i < (int)T.length() && j < (int)p.length())
{
if (j == -1 || T[i] == p[j])
{
i++;
j++;
}
else
{
j=next[j];
}
}
if (j == (int)p.length())
{
return i-j;
}
return -1;
}
5.写二分查找
答:
static int binarySerach(int[] array, int key) {
int left = 0;
int right = array.length - 1;
// 这里必须是 <=
while (left <= right) {
int mid = (left + right) / 2;
if (array[mid] == key) {
return mid;
}
else if (array[mid] < key) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
return -1;
}
6.哈希表原理和冲突处理
答:
哈希表原理:首先在元素的关键字k和元素的存储位置p之间建立一个对应关系f,使得p=f(k),f称为哈希函数。创建哈希表时,把关键字为k的元素直接存入地址为f(k)的单元;以后当查找关键字为k的元素时,再利用哈希函数计算出该元素的存储位置p=f(k),从而达到按关键字直接存取元素的目的。
冲突:当关键字集合很大时,关键字值不同的元素可能会映象到哈希表的同一地址上,即 k1≠k2 ,但 H(k1)=H(k2),这种现象称为冲突,此时称k1和k2为同义词。实际中,冲突是不可避免的,只能通过改进哈希函数的性能来减少冲突。
冲突处理:
1.开放寻址法:这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。
2.链表法
这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。
7.合并链表
答:
1.迭代法
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* s1 = l1;
ListNode* s2 = l2;
ListNode* result = new ListNode(0);
ListNode* res = result;
while(s1 && s2){
if(s1->val < s2->val) {
res->next = s1;
res = res->next;
s1 = s1->next;
}
else{
res->next = s2;
res = res->next;
s2 = s2->next;
}
}
s1 == NULL?res->next = s2:res->next = s1;
ListNode* tem = result->next;
delete result;
return tem;
}
};
2.递归法
class Solution {
public:
ListNode* head = new ListNode(0);
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == nullptr) {
return l2;
} else if (l2 == nullptr) {
return l1;
} else if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
} else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
};
8.岛屿个数
答:leetcode 200题。
9.如何从1000w个数中取出1000个最大的数
答:使用快排,时间复杂度最好是O(1),最坏是O(n).
class Solution {
public:
vector<int> smallestK(vector<int>& arr, int k) {
int l = 0;
int r = arr.size() - 1;
int count = 0;
qsort(arr, l, r, k);
vector<int> res;
for(int i = 0; i < k; i++){
res.push_back(arr[i]);
}
return res;
}
void qsort(vector<int>& arr, int l, int r, int k){
if(l < r){
int p = partition(arr, l , r);
if(p == k-1){
return;
}
else if(p > k-1){
qsort(arr, l , p-1, k);
}
else{
qsort(arr, p+1, r, k);
}
}
return;
}
int partition(vector<int>& arr, int begin, int end){
int stone = arr[begin];
while(begin < end){
while(begin < end && arr[end] > stone) end--;
swap(arr[begin], arr[end]);
while(begin < end && arr[begin] <= stone) begin++;
swap(arr[begin], arr[end]);
}
return begin;
}
};
10.青蛙跳台阶问题
答:斐波拉契数列,动态规划,剑指offer10-2
class Solution {
public:
int numWays(int n) {
int a = 1;
int b = 1;
int c;
for(int i = 2; i <= n; i++){
c = (a + b)%1000000007;
a = b;
b = c;
}
return b;
}
};
11.25匹马、5个赛道,怎么用最少的次数决出前三名
答:7次。
第1-5次比赛:各组分别进行比赛,决出各组名次,取每组前三名。
第6次比赛:A1、B1、C1、D1、E1,淘汰D1,E1。
第7次比赛:A2、A3、B1、B2、C1比赛求出第2,第3即可
12.32位int的二进制中1的个数
int bit_count_2(int num)
{
int count = 0;
while (0 != num)
{
++count;
num = num & (num - 1);
}
return count;
}
13.寻找两个链表的公共节点
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *node1 = headA;
ListNode *node2 = headB;
while (node1 != node2) {
node1 = node1 != NULL ? node1->next : headB;
node2 = node2 != NULL ? node2->next : headA;
}
return node1;
}
};
14.map unordered_map的区别,哪个内存利用率高
答:
1.需要引入的头文件不同
map: #include < map >
unordered_map: #include < unordered_map >
2.内部实现机理不同
map: map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来。
unordered_map: unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。
优缺点以及适用处
map:
优点:
有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作
红黑树,内部实现一个红黑书使得map的很多操作在lgn的时间复杂度下就可以实现,因此效率非常的高
缺点: 空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间
适用处:对于那些有顺序要求的问题,用map会更高效一些。
unordered_map:
优点: 因为内部实现了哈希表,因此其查找速度非常的快
缺点: 哈希表的建立比较耗费时间
适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map
总结:
内存占有率的问题就转化成红黑树 VS hash表 。
但是unordered_map执行效率要比map高很多
对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的。
运行效率方面:unordered_map最高,hash_map其次,而map效率最低
占用内存方面:hash_map内存占用最低,unordered_map其次,而map占用最高
15.红黑树
答:
红黑树性质:
- 每个结点是黑色或者红色。
- 根结点是黑色。
- 每个叶子结点(NIL)是黑色。 [注意:这里叶子结点,是指为空(NIL或NULL)的叶子结点!]
- 如果一个结点是红色的,则它的子结点必须是黑色的。
- 每个结点到叶子结点NIL所经过的黑色结点的个数一样的。[确保没有一条路径会比其他路径长出俩倍,所以红黑树是相对接近平衡的二叉树的!]
时间复杂度:红黑树插入需要O(lg(n))次,对插入结点后的调整所做的旋转操作不会超过2次。删除结点后的调整所做的旋转操作不会操作3次,沿树回溯至多O(lg(n))次。
总而言之,红黑树的插入和删除的时间复杂度均为O(lg(n))
左左情况
这种情况很简单,想象这是一根绳子,手提起 P 节点,然后变色即可
左右
左旋: 使 X 的父节点 P 被 X 取代,同时父节点 P 成为 X 的左孩子,然后再应用 左左情况
右右
与左左情况一样,想象成一根绳子
右左
右旋: 使 X 的父节点 P 被 X 取代,同时父节点 P 成为 X 的右孩子,然后再应用 右右情况
16.s2是否是s1的子序列
答:leetcode 392题。
class Solution {
public:
bool isSubsequence(string s, string t) {
int l1 = s.length();
if(l1 == 0) return true;
int l2 = t.length();
int i = 0;
int j = 0;
for(; i < l2; i++){
if(t[i] == s[j]){
j++;
if(j == l1) return true;
}
}
return false;
}
};
17.字符串解码
答:leetcode 394题。
class Solution {
public:
string decodeString(string s) {
vector<string> stack;
int len = s.length();
int ptr = 0;
while(ptr < len){
if(isdigit(s[ptr])){
stack.push_back(getdigits(s, ptr));
}
else if(isalpha(s[ptr]) || s[ptr] == '['){
string tem = "";
tem += s[ptr];
stack.push_back(tem);
ptr++;
}
else{
ptr++;
vector<string> chuan;
while(stack.back() != "["){
chuan.push_back(stack.back());
stack.pop_back();
}
reverse(chuan.begin(), chuan.end());
string tem1 = getstring(chuan);
stack.pop_back();
int bei = stoi(stack.back());
stack.pop_back();
string big = "";
while(bei--){
big += tem1;
}
stack.push_back(big);
}
}
return getstring(stack);
}
string getdigits(string& s, int& ptr){
string digits = "";
while(isdigit(s[ptr])){
digits += s[ptr++];
}
return digits;
}
string getstring(vector<string>& chuan){
string res = "";
for(auto x:chuan){
res += x;
}
return res;
}
};
18.B树和B+树
B-树的特性:
- 关键字集合分布在整颗树中;
- 任何一个关键字出现且只出现在一个结点中;
- 搜索有可能在非叶子结点结束;
- 其搜索性能等价于在关键字全集内做一次二分查找;
- 自动层次控制;
B+树的特性:
- 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
- 不可能在非叶子结点命中;
- 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
19.堆排序的原理?
答:
1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;
2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区和新的有序区;
3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区和新的有序区。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
20.讲讲STL
答:参考至:http://c.biancheng.net/stl/
STL是标准模板库,是标准库的一部分。
STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离。例如,vector 的底层为顺序表(数组),list 的底层为双向链表,deque 的底层为循环队列,set 的底层为红黑树,hash_set 的底层为哈希表。
21.vector的底层实现
答:https://blog.csdn.net/u012658346/article/details/50725933
vector 是最常用的容器之一,其底层所采用的数据结构非常简单,就只是一段连续的线性内存空间。
通过分析 vector 容器的源代码不难发现,它就是使用 3 个迭代器(可以理解成指针)来表示的:其中,_Myfirst 指向的是 vector 容器对象的起始字节位置;_Mylast 指向当前最后一个元素的末尾字节;_myend 指向整个 vector 容器所占用内存空间的末尾字节。
22.stl 里面有哪些排序函数
答:
23.常见STL的用法
答:
vector:
- push_back 在数组的最后添加一个数据
- pop_back 去掉数组的最后一个数据
- at 得到编号位置的数据
- begin 得到数组头的指针
- end 得到数组的最后一个单元+1的指针
- front 得到数组头的引用
- back 得到数组的最后一个单元的引用
- max_size 得到vector最大可以是多大
- capacity 当前vector分配的大小
- size 当前使用数据的大小
- resize 改变当前使用数据的大小,如果它比当前使用的大,者填充默认值
- reserve 改变当前vecotr所分配空间的大小
- erase 删除指针指向的数据项
- clear 清空当前的vector
- rbegin 将vector反转后的开始指针返回(其实就是原来的end-1)
- rend 将vector反转构的结束指针返回(其实就是原来的begin-1)
- empty 判断vector是否为空
- swap 与另一个vector交换数据
- 访问可以直接使用数组格式vector[i]
stack:
-
stack<int> s;
-
stack< int, vector<int> > stk; //覆盖基础容器类型,使用vector实现stk
-
s.empty(); //判断stack是否为空,为空返回true,否则返回false
-
s.size(); //返回stack中元素的个数
-
s.pop(); //删除栈顶元素,但不返回其值
-
s.top(); //返回栈顶元素的值,但不删除此元素
-
s.push(item); //在栈顶压入新元素item
list:
#include <list>
list<int>lst1; //创建空list
list<int> lst2(5); //创建含有5个元素的list
- Lst1.assign() 给list赋值
- Lst1.back() 返回最后一个元素
- Lst1.begin() 返回指向第一个元素的迭代器
- Lst1.clear() 删除所有元素
- Lst1.empty() 如果list是空的则返回true
- Lst1.end() 返回末尾的迭代器
- Lst1.erase() 删除一个元素
- Lst1.front() 返回第一个元素
- Lst1.get_allocator() 返回list的配置器
- Lst1.insert() 插入一个元素到list中
- Lst1.max_size() 返回list能容纳的最大元素数量
- Lst1.merge() 合并两个list
- Lst1.pop_back() 删除最后一个元素
- Lst1.pop_front() 删除第一个元素
- Lst1.push_back() 在list的末尾添加一个元素
- Lst1.push_front() 在list的头部添加一个元素
- Lst1.rbegin() 返回指向第一个元素的逆向迭代器
- Lst1.remove() 从list删除元素
- Lst1.remove_if() 按指定条件删除元素
- Lst1.rend() 指向list末尾的逆向迭代器
- Lst1.resize() 改变list的大小
- Lst1.reverse() 把list的元素倒转
- Lst1.size() 返回list中的元素个数
- Lst1.sort() 给list排序
- Lst1.splice() 合并两个list
- Lst1.swap() 交换两个list
- Lst1.unique() 删除list中相邻重复的元素
map:
#include<map>
map<int, string> ID_Name;
- begin() 返回指向map头部的迭代器
- clear() 删除所有元素
- count() 返回指定元素出现的次数
- empty() 如果map为空则返回true
- end() 返回指向map末尾的迭代器
- equal_range() 返回特殊条目的迭代器对
- erase() 删除一个元素
- find() 查找一个元素
- get_allocator() 返回map的配置器
- insert() 插入元素
- key_comp() 返回比较元素key的函数
- lower_bound() 返回键值>=给定元素的第一个位置
- max_size() 返回可以容纳的最大元素个数
- rbegin() 返回一个指向map尾部的逆向迭代器
- rend() 返回一个指向map头部的逆向迭代器
- size() 返回map中元素的个数
- swap() 交换两个map
- upper_bound() 返回键值>给定元素的第一个位置
- value_comp() 返回比较元素value的函数
set:
#include <set>
- 1. begin()--返回指向第一个元素的迭代器
- 2. clear()--清除所有元素
- 3. count()--返回某个值元素的个数
- 4. empty()--如果集合为空,返回true
- 5. end()--返回指向最后一个元素的迭代器
- 6. equal_range()--返回集合中与给定值相等的上下限的两个迭代器
- 7. erase()--删除集合中的元素
- 8. find()--返回一个指向被查找到元素的迭代器
- 9. get_allocator()--返回集合的分配器
- 10. insert()--在集合中插入元素
- 11. lower_bound()--返回指向大于(或等于)某值的第一个元素的迭代器
- 12. key_comp()--返回一个用于元素间值比较的函数
- 13. max_size()--返回集合能容纳的元素的最大限值
- 14. rbegin()--返回指向集合中最后一个元素的反向迭代器
- 15. rend()--返回指向集合中第一个元素的反向迭代器
- 16. size()--集合中元素的数目
- 17. swap()--交换两个集合变量
- 18. upper_bound()--返回大于某个值元素的迭代器
- 19. value_comp()--返回一个用于比较元素间的值的函数
queue:
#include <queue>
empty() | 如果 queue 中没有元素的话,返回 true。 |
size() | 返回 queue 中元素的个数。 |
front() | 返回 queue 中第一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。 |
back() | 返回 queue 中最后一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。 |
push(const T& obj) | 在 queue 的尾部添加一个元素的副本。这是通过调用底层容器的成员函数 push_back() 来完成的。 |
emplace() | 在 queue 的尾部直接添加一个元素。 |
push(T&& obj) | 以移动的方式在 queue 的尾部添加元素。这是通过调用底层容器的具有右值引用参数的成员函数 push_back() 来完成的。 |
pop() | 删除 queue 中的第一个元素。 |
swap(queue<T> &other_queue) | 将两个 queue 容器适配器中的元素进行互换,需要注意的是,进行互换的 2 个 queue 容器适配器中存储的元素类型以及底层采用的基础容器类型,都必须相同。 |
deque:
#include<deque>
- deq[ ]:用来访问双向队列中单个的元素。
- deq.front():返回第一个元素的引用。
- deq.back():返回最后一个元素的引用。
- deq.push_front(x):把元素x插入到双向队列的头部。
- deq.pop_front():弹出双向队列的第一个元素。
- deq.push_back(x):把元素x插入到双向队列的尾部。
- deq.pop_back():弹出双向队列的最后一个元素。
24.手撕string的compare函数
答:
int strcmp(const char* dest, const char* source)
{
assert((NULL != dest) && (NULL != source));
while (*dest && *source && (*dest == *source))
{
dest++;
source++;
}
return *dest - *source;
/*如果dest > source,则返回值大于0,如果dest = source,则返回值等于0,如果dest < source ,则返回值小于0。*/
}
25.反转单链表
答:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL;
ListNode* cur = head;
while(cur){
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
26.二叉树中序遍历
答:
class Solution {
public:
void dfs(TreeNode* root, vector<int>& ans){
if(!root) return;
if(root->left) dfs(root->left, ans);
ans.push_back(root->val);
if(root->right) dfs(root->right, ans);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ans;
if(!root) return ans;
dfs(root, ans);
return ans;
}
};
27.拷贝构造 手写,并且问为什么要取引用&
答:参考:https://www.cnblogs.com/alantu2018/p/8459250.html
深拷贝:
#include<iostream>
#include<assert.h>
using namespace std;
class Rect
{
public:
Rect()
{
p=new int(100);
}
Rect(const Rect& r)
{
width=r.width;
height=r.height;
p=new int(100);
*p=*(r.p);
}
~Rect()
{
assert(p!=NULL);
delete p;
}
private:
int width;
int height;
int *p;
};
int main()
{
Rect rect1;
Rect rect2(rect1);
return 0;
}
为什么要加引用:避免对象调用拷贝构造函数,从而无限递归下去。
以下参考:https://www.cnblogs.com/cxq0017/p/10617313.html
1 STRING( const STRING& s ):_str(NULL)
2 {
3 STRING tmp(s._str);// 调用了构造函数,完成了空间的开辟以及值的拷贝
4 swap(this->_str, tmp._str); //交换tmp和目标拷贝对象所指向的内容
5 }
6
7 STRING& operator=(const STRING& s)
8 {
9 if ( this != &s )//不让自己给自己赋值
10 {
11 STRING tmp(s._str);//调用构造函数完成空间的开辟以及赋值工作
12 swap(this->_str, tmp._str);//交换tmp和目标拷贝对象所指向的内容
13 }
14 return *this;
15 }
28.无序数组找到最大和最小的元素,比较次数小于2n
答:参考:https://blog.csdn.net/HugoChen_cs/article/details/105105700
使用两个值记录最大值和最小值, 每次取出两个值,先进行比较,小的与最小值比较,大的与最大值比较 , 比较次数: 1.5 ∗ N。
// 找到数组元素的最大值和最小值
vector<int> findMinMax(vector<int> arr) {
int min_ = INT_MAX,max_ = INT_MIN;
// 处理前面偶数个元素
for(int i=0;i<arr.size()/2;++i) {
// 得到两个元素的最大值和最小值
int tmp_min,tmp_max;
if(arr[i] < arr[i+1]) {
tmp_min = arr[i];
tmp_max = arr[i+1];
} else {
tmp_min = arr[i+1];
tmp_max = arr[i];
}
// 比较,更新最大值和最小值
if(tmp_max > max_) max_ = tmp_max;
if(tmp_min < min_) min_ = tmp_min;
}
// 处理数组个数为奇数的情况 // 处理最后一个元素
if(arr.size()%2) {
int tmp = arr.back();
if(tmp > max_) max_ = tmp;
if(tmp < min_) min_ = tmp;
}
return {min_,max_};
}
29.堆排序和快排的区别
时间复杂度 | 最好时间复杂度 | 最差时间复杂度 | 空间复杂度 | 是否是稳定排序 | |
快速排序 | O(nlogn) | O(nlogn) | O(n*n)平方 | O(1)原地 | 否(涉及到左右数据交换) |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1)原地 | 否(涉及数据层与层的交换) |
1. 10w 数据量两种排序速度基本相当,但是堆排序交换次数明显多于快速排序;10w+数据,随着数据量的增加快速排序效率要高的多,数据交换次数快速排序相比堆排序少的多。
2. 实际应用中,堆排序的时间复杂度要比快速排序稳定,快速排序的最差的时间复杂度是O(n*n),平均时间复杂度是O(nlogn)。堆排序的时间复杂度稳定在O(nlogn)。但是从综合性能来看,快速排序性能更好。
30.strcpy,strncpy,memcpy.
答:
strcpy
char * strcpy(char *dest, char *src)
{
if (dest == NULL || src == NULL)
{
return NULL;
}
char *pTemp = dest;
while ((*dest++ = *src++) != '\0')
{
//空函数体
}
return pTemp;
}
void main
{
char src[] = "this is a test";
char *dest = new char[strlen(src) + 1];
strcpy(dest, src);
}
strncpy
char* strncpy(char* dest, const char* source, size_t count)
{
char* start=dest;
while (count && (*dest++=*source++))
count--;
if(count)
while (--count)
*dest++='\0';
return(start);
}
memcpy
void* memcpy(void* memTo, void* memFrom, size_t size)
{
assert(memTo != NULL && memFrom != NULL);
char* temFrom = (char*)memFrom;
char* temTo = (char*)memTo;
while(size-- > 0)
*temTo++ = *temFrom++;
return memTo;
}
三者的特点:
strcpy:只能复制字符串,不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。
strncpy:只能复制字符串,需要指定长度。长度n >= strlen(s1)+1;这个1 就是最后的“\0”。
memcpy:可以复制任意内容,例如字符数组、整型、结构体、类等。memcpy没有考虑内存重叠的情况,所以如果两者内存重叠,会出现错误。
memcpy防止内存重叠:
void* Memcpy(void* dst,const void* src,int count)
{
assert(dst!=nullptr && src!=nullptr);
void* ret = dst;
if(dst<=src || (char*)dst>=((char*)src+count))
{
while(count--)
{
*(char*)dst =*(char*)src;
dst = (char*)dst+1;
src = (char*)src+1;
}
}
else
{
dst = (char*)dst + count -1;
src = (char*)src + count -1;
while(count--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst-1;
src = (char*)src-1;
}
}
return ret;
}
31.M进制转N进制
答:
#include<iostream>
#include<stdio.h>
#include<string>
#include<vector>
using namespace std;
char IntToChart(int x)
{
if (x < 10)
{
return x + '0';
}
else
{
return x - 10 + 'A';
}
}
int CharToInt(char c)
{
if (c >= '0' && c <= '9')
{
return c - '0';
}
else
{
return c - 'A' + 10;
}
}
int main()
{
int m, n;
cin >> m >> n;
string str;
cin >> str;
long long number = 0;
for (int i = 0; i < str.size(); ++i)
{
number =number*m + CharToInt(str[i]);
}
vector<char> answer;
while (number)
{
answer.push_back(IntToChart(number % n));
number /= n;
}
for (int i = answer.size() - 1; i >= 0; --i)
{
cout << answer[i];
}
return 0;
}
32.统计树的深度
class Solution {
public:
void dfs(Node* root, int depth, int& maxd){
if(!root) return;
if(depth > maxd) maxd = depth;
int len = root->children.size();
for(int i = 0; i < len; i++){
dfs(root->children[i], depth + 1, maxd);
}
}
int maxDepth(Node* root) {
int depth = 1;
int maxd = 0;
dfs(root, depth, maxd);
return maxd;
}
};