算法与数据结构知识点及面试题总结(持续更新中)

目录

 

1.经典排序复杂度分析及常考排序算法

2.判断链表是否有环

3.当排序几十个数的时候用哪种,几十万个数的时候用哪种?

4.判断一个字符串是否为另一个字符串的子串?

5.写二分查找

6.哈希表原理和冲突处理

7.合并链表

8.岛屿个数

9.如何从1000w个数中取出1000个最大的数

10.青蛙跳台阶问题

11.25匹马、5个赛道,怎么用最少的次数决出前三名

12.32位int的二进制中1的个数

13.寻找两个链表的公共节点

14.map unordered_map的区别,哪个内存利用率高

15.红黑树

16.s2是否是s1的子序列

17.字符串解码

18.B树和B+树

19.堆排序的原理?

20.讲讲STL

21.vector的底层实现

22.stl 里面有哪些排序函数


 

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.红黑树

答:

红黑树性质:

  1. 每个结点是黑色或者红色。
  2. 根结点是黑色。
  3. 每个叶子结点(NIL)是黑色。 [注意:这里叶子结点,是指为空(NIL或NULL)的叶子结点!]
  4. 如果一个结点是红色的,则它的子结点必须是黑色的。
  5. 每个结点到叶子结点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-树的特性:

  1. 关键字集合分布在整颗树中;
  2. 任何一个关键字出现且只出现在一个结点中;
  3. 搜索有可能在非叶子结点结束;
  4. 其搜索性能等价于在关键字全集内做一次二分查找;
  5. 自动层次控制;

B+树的特性:

  1. 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
  2. 不可能在非叶子结点命中;
  3. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;

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:

  1. push_back 在数组的最后添加一个数据
  2. pop_back 去掉数组的最后一个数据
  3. at 得到编号位置的数据
  4. begin 得到数组头的指针
  5. end 得到数组的最后一个单元+1的指针
  6. front 得到数组头的引用
  7. back 得到数组的最后一个单元的引用
  8. max_size 得到vector最大可以是多大
  9. capacity 当前vector分配的大小
  10. size 当前使用数据的大小
  11. resize 改变当前使用数据的大小,如果它比当前使用的大,者填充默认值
  12. reserve 改变当前vecotr所分配空间的大小
  13. erase 删除指针指向的数据项
  14. clear 清空当前的vector
  15. rbegin 将vector反转后的开始指针返回(其实就是原来的end-1)
  16. rend 将vector反转构的结束指针返回(其实就是原来的begin-1)
  17. empty 判断vector是否为空
  18. swap 与另一个vector交换数据
  19. 访问可以直接使用数组格式vector[i]

stack:

  1. stack<int> s;

  2. stack< int, vector<int> > stk; //覆盖基础容器类型,使用vector实现stk

  3. s.empty(); //判断stack是否为空,为空返回true,否则返回false

  4. s.size(); //返回stack中元素的个数

  5. s.pop(); //删除栈顶元素,但不返回其值

  6. s.top(); //返回栈顶元素的值,但不删除此元素

  7. s.push(item); //在栈顶压入新元素item

list:

#include <list>

list<int>lst1;          //创建空list

list<int> lst2(5);       //创建含有5个元素的list

  1. Lst1.assign() 给list赋值 
  2. Lst1.back() 返回最后一个元素 
  3. Lst1.begin() 返回指向第一个元素的迭代器 
  4. Lst1.clear() 删除所有元素 
  5. Lst1.empty() 如果list是空的则返回true 
  6. Lst1.end() 返回末尾的迭代器 
  7. Lst1.erase() 删除一个元素 
  8. Lst1.front() 返回第一个元素 
  9. Lst1.get_allocator() 返回list的配置器 
  10. Lst1.insert() 插入一个元素到list中 
  11. Lst1.max_size() 返回list能容纳的最大元素数量 
  12. Lst1.merge() 合并两个list 
  13. Lst1.pop_back() 删除最后一个元素 
  14. Lst1.pop_front() 删除第一个元素 
  15. Lst1.push_back() 在list的末尾添加一个元素 
  16. Lst1.push_front() 在list的头部添加一个元素 
  17. Lst1.rbegin() 返回指向第一个元素的逆向迭代器 
  18. Lst1.remove() 从list删除元素 
  19. Lst1.remove_if() 按指定条件删除元素 
  20. Lst1.rend() 指向list末尾的逆向迭代器 
  21. Lst1.resize() 改变list的大小 
  22. Lst1.reverse() 把list的元素倒转 
  23. Lst1.size() 返回list中的元素个数 
  24. Lst1.sort() 给list排序 
  25. Lst1.splice() 合并两个list 
  26. Lst1.swap() 交换两个list 
  27. Lst1.unique() 删除list中相邻重复的元素

map:

#include<map>

map<int, string> ID_Name;

  1.      begin()         返回指向map头部的迭代器
  2.      clear()        删除所有元素
  3.      count()         返回指定元素出现的次数
  4.      empty()         如果map为空则返回true
  5.      end()           返回指向map末尾的迭代器
  6.      equal_range()   返回特殊条目的迭代器对
  7.      erase()         删除一个元素
  8.      find()          查找一个元素
  9.      get_allocator() 返回map的配置器
  10.      insert()        插入元素
  11.      key_comp()      返回比较元素key的函数
  12.      lower_bound()   返回键值>=给定元素的第一个位置
  13.      max_size()      返回可以容纳的最大元素个数
  14.      rbegin()        返回一个指向map尾部的逆向迭代器
  15.      rend()          返回一个指向map头部的逆向迭代器
  16.      size()          返回map中元素的个数
  17.      swap()           交换两个map
  18.      upper_bound()    返回键值>给定元素的第一个位置
  19.      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>

  1. deq[ ]:用来访问双向队列中单个的元素。
  2. deq.front():返回第一个元素的引用。
  3. deq.back():返回最后一个元素的引用。
  4. deq.push_front(x):把元素x插入到双向队列的头部。
  5. deq.pop_front():弹出双向队列的第一个元素。
  6. deq.push_back(x):把元素x插入到双向队列的尾部。
  7. 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;
    }
};

 

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值