面试常考算法题c++

一些问题记录

① 抛洒物项目具体讲讲
② 项目几个人做的 难点(找创新点 效率)
③ 了解中兴的产业吗
④ 网路的7层模型
⑤ 有没有学习过数字电路
⑥ 薪资要求 最低
⑦ 生活环境介绍
⑧ 想在哪里工作

-项目(web server)
① 讲一下epoll怎么用的
② 项目用的多进程还是多线程 怎么考虑的
③项目难点在哪里(类的设计、优化、解析报文)
④mysql数据库怎么用的
⑤有没有解决tcp沾包问题

  • 数据库
    ① 数据库的存储引擎有哪些 有什么区别
    ② 慢查询如何排查 (explain!!!)
    ③ 回表具体如何进行的
  • 网络
    ① tcp和udp的区别
    ②https和http的区别 加密层具体如何实现的
  • c++
    ① 虚函数如何实现的
    ②熟悉哪些容器 操作复杂度是多少
    ③map操作复杂度
  • 数据结构
    ① 快排的具体思路 时间复杂度 是否稳定
    ②归并排序的时间复杂度
  • 算法(口述思路)
    ① 有序数组中找出和为target的两个数
    ②第k大的数

算法题

1.两个线程轮流打印A B

思路:用一个变量flag控制线程之间的执行顺序,线程A中判断flag的值,如果==‘A’,轮到该线程打印A,线程B也类似。

#include 
/*
直接用一个flag标识就可以
*/
using namespace std;
char flag = 'A';
void printA(){
  while(true) {
//  flag为A标识B已经打印完了 轮到打印A了 
  	if (flag == 'A') {
  		cout << "A" << endl;
  		flag = 'B';
	}
  }	
}
void printB(){
	while(true) {
//  flag为B标识A已经打印完了 轮到打印B了 
  	if (flag == 'B') {
  		cout << "B" << endl;
  		flag = 'C';
	}
  }
}
int main() {
	thread A(printA);
	thread B(printB);
//join的作用是当线程执行完之后才返回 
//如果后续有需要两个结果才能执行的process 需要先调用join保证任务完成 
	A.join();
	B.join();
}

轮流打印多个字母也一样 开线程用flag控制顺序就好。

2. 验证二叉搜索树-leetcode

  • 方法一:用min和max作为函数参数限制左子树和右子树的最大值和最小值。
// 当前节点的值应该在min_ 和 max_之间 
    bool dfs(TreeNode* root,long long min_,long long max_) {
        if (root == nullptr) {
            return true;
        }
        if (root->val <= min_ || root->val >= max_){
            return false;
        }        
        bool l = dfs(root->left,min_,root->val);
        bool r = dfs(root->right,root->val,max_);       
        return l && r;
    }
  • 方法二:中序遍历,在遍历的时候用last变量记录上一个值和当前值比较,如果比当前节点值小继续,反之return false。
long last = LONG_MIN;
    bool isValidBST(TreeNode* root) {
        if (root == nullptr) {
            return true;
        }
        bool l = isValidBST(root->left);
        if (last < root->val) {
            last = root->val;    
        }else{
            return false;
        }
        bool r = isValidBST(root->right);
        return l && r;
    }

2. LRU-leetcode

思路:用一个自建双向链表存储具体的key,value信息(struct Node)。链表前面是最近访问的节点,当访问一个值或者新加入一个值的时候需要将该值移到链表头部。链表尾部是空间不够的时候需要删除的不经常使用的节点。
用一个hash表存储key和对应Node指针,使得当访问一个数据的时候可以达到O(1)的时间。
注:对链表进行插入和删除操作也对应处理hash表。Node中也要存储key的信息,因为在删除尾部节点的时候需要根据key删除对应hash表中的节点。

class LRUCache {
    struct Node {
        int key,val;
        Node *next,*prev;
        Node(int key,int value) : key(key),val(value),prev(nullptr),next(nullptr){};
    };
    unordered_map<int,Node*> cache;
    Node* head;
    Node* tail;
    int capacity;
    int size;
public:
    LRUCache(int capacity) {
        this->capacity = capacity;
        size = 0;
        head = new Node(0,0);
        tail = new Node(0,0);
        head->next = tail;
        tail->prev = head;
    }
    
    int get(int key) {
        if (cache.find(key) == cache.end()) {
            return -1;
        }
        Node* cur = cache[key];
        MoveToHead(cur);
        return cur->val;
    }
    
    void put(int key, int value) {
        if (cache.find(key) != cache.end()) {
            // 如果已经存在 更改该值 并移动到到链表头部
            Node *cur = cache[key];
            cur->val = value;
            MoveToHead(cur);
            return;
        }
        // 不存在 
        if (size >= capacity) {
            // 容量不够 删除一个末尾的节点
            // 删除的时候需要知道该节点对应的key是多少 所以链表节点需要存储key的值
            cache.erase(tail->prev->key);
            DeleteTail();           
            --size;
        } 
        // 新建一个节点 插入头部
        Node* newnode = new Node(key,value);
        cache[key] = newnode;
        AddToHead(newnode);
        ++size;
    }

    void MoveToHead(Node* cur) {
        // 删除当前节点s
        cur->next->prev = cur->prev;
        cur->prev->next = cur->next;
        // 插入到头部 
        AddToHead(cur);
    }
    void AddToHead(Node* cur) {
        cur->next = head->next;
        head->next = cur;
        cur->prev = head;
        cur->next->prev = cur;
    }
    void DeleteTail() {
        tail->prev->prev->next = tail;
        tail->prev = tail->prev->prev;
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

3. 排序数组

  1. 快速排序
    每次选择一个主元pivot,将数组分为两部分,一部分小于pivot,另外一部分大于pivot。这样分之后,pivot已经在它排序后应该在的位置上,继续递归排序其左边和右边部分。
    注:当数组中的元素值全部相同的时候,时间复杂度为O(n2).
class Solution {
    int partition(vector<int>& nums,int l,int r) {
        int i = rand() % (r - l + 1) + l;//生成[l,r]的随机数 选择随机主元
        swap(nums[i],nums[r]);
        int pivot = nums[r];
        i = l - 1;
        for (int j = l;j <= r - 1;++j) {
            if (nums[j] <= pivot){
                i = i + 1;
                swap(nums[i],nums[j]);
            }
        }
        swap(nums[i+1],nums[r]);
        return i+1;
    }
    void quickSort(vector<int>& nums,int l,int r) {
        if (l >= r) return;
        // 将数据分为两部分 一部分小于pos处的值 另一部分大于pos处的值
        int pos = partition(nums,l,r);
        quickSort(nums,l,pos-1);// 递归排左半部分
        quickSort(nums,pos+1,r);// 递归排右半部分
    }
public:
    vector<int> sortArray(vector<int>& nums) {
       srand((unsigned)time(NULL));
       quickSort(nums,0,nums.size() - 1);
       return nums;
    }
};
  1. 堆排序
    从第一个非叶子节点开始调整使得孩子节点都比根节点小,建立一个大根堆。
    然后交换堆顶元素和最后一个元素,再调整堆顶保持大根堆性质,这时候最大的元素已经在自己的位置上了。再依次交换,找到次大元素,当堆中剩下一个元素的时候整体数组就有序了。
    注:交换后调整堆的时候,注意数组的长度已经改变了。
class Solution {
    void buildHeap(vector<int>& nums,int i,int n) {     
        while((2 * i + 1) <= n) {
            int l = 2 * i + 1;
            int r = 2 * i + 2;
            int large = i;
            if (l <= n && nums[l] > nums[i]) {
                large = l;
            }
            if (r <= n && nums[r] > nums[large]) {
                large = r;
            }
            if(large == i) {
                break;
            }
            swap(nums[large],nums[i]);
            i = large;
        }
    }
    void HeapSort(vector<int>& nums) {
        // 调整为一个大顶堆
        int len = nums.size() - 1;
        for (int i = len / 2;i >= 0;--i) {
            buildHeap(nums,i,len);
        }
        // 排序 将最后的值和根节点进行交换 然后调整根节点
        for (int i = len;i >= 1;--i) {
            swap(nums[i],nums[0]);
            len -= 1;
            buildHeap(nums,0,len);
        }
        
    }
public:
    vector<int> sortArray(vector<int>& nums) {
      // 堆排序
      HeapSort(nums);
      return nums;
    }
};
  1. 归并排序
    将一个元素视为有序 自底向上不断的合并相邻的有序序列 直到整个序列有序。
class Solution {
    vector<int> tmp;
    void mergeSort(vector<int>& nums,int left,int right){
        if(left >= right) return;
        int mid = (right - left) / 2 + left;
        mergeSort(nums,left,mid);
        mergeSort(nums,mid+1,right);
        // 对有序序列进行合并放到数组tmp中 类似合并有序链表的操作
        int l = left,r=mid+1,i=left;
        while(l<=mid && r<=right){
            if(nums[l] <= nums[r]){
                tmp[i++] = nums[l++];
            }else{
                tmp[i++] = nums[r++];
            }
        }
        while(l<=mid) tmp[i++] = nums[l++];
        while(r<=right) tmp[i++] = nums[r++];
        for(int i=left;i<=right;++i){
            nums[i] = tmp[i];
        }
    }
public:
    vector<int> sortArray(vector<int>& nums) {
        // 将一个元素视为有序 自底向上不断的合并相邻的有序序列 直到整个序列有序
        int n = nums.size();
        tmp.resize(n);
        mergeSort(nums,0,n-1);
        return nums;
    }
};

4. 排序链表

5. 单例模式

懒汉模式:将构造函数设置为私有的,给外界提供一个getinstance接口,该接口中创建一个局部静态变量返回给外界。由于变量是static的在程序中只有一个,从而达到了只有一个实例的目的。是线程安全的。
饿汉模式:在类中定义一个私有的静态变量,在程序开始的时候就进行初始化。外界直接通过getinstance接口获得该实例对象。

#include 
#include 
using namespace std;

class lazy_single{ //懒汉模式:用到的时候才初始化实例对象
public:
    static lazy_single& getInstance(){
        static lazy_single instance; // 调用私有构造函数
        return instance;
    }
private:
    lazy_single() {};
    ~lazy_single() {};
    lazy_single(const lazy_single&);
    lazy_single& operator=(const lazy_single&);
};
// -------------------------------------------------
class hungry_single{ // 饿汉模式  程序一开始就创建了实例对象
public:
    static hungry_single& getInstance(){
        return instance;
    }
private:
    static hungry_single instance;
private:
    hungry_single();
    ~hungry_single();
    hungry_single(const hungry_single&);
    hungry_single& operator=(const hungry_single&);
};
hungry_single hungry_single::instance;

6.vector

思路:类中三个对象,分别是size,capacity和data,data是指向数组的指针,当size等于capacity的时候新建一块数组空间把原来的数据复制过去。

template<typename T>
class MyVector {
private:
    T *data;
    size_t _capacity;
    size_t _size;

    void resize(int new_size){
        T *newdata = new T[new_size];
        for(int i = 0; i < _size; ++i)
            newdata[i] = data[i];
        delete[] data;
        data = newdata;
        _capacity = new_size;
    }

public:
    MyVector(){
        data = new T[10];
        _capacity = 10;
        _size = 0;
    }
    MyVector(size_t n){
        data = new T[n];
        _capacity = n;
        _size = 0;
    }
    ~MyVector(){
        delete[] data;
    }

    void reserve(size_t n){
        if(n > _capacity){
            T *newdata = new T[n];
            _capacity = n;
            for(int i = 0; i < _size; ++i)
                newdata[i] = data[i];
            data = newdata;
        }
    }
    
    void push_back(T e){
        if(_size == _capacity){
            resize(2 * _capacity);
        }
        data[_size++] = e;
    }

    T pop_back(T e){
        T temp = data[_size];
        --_size;
        return temp;
    }

    size_t size(){
        return _size;
    }

    bool empty(){
        return _size == 0;
    }

    T &operator[](int i){
		return *(data + i);
    }

    size_t capacity(){
        return _capacity;
    }

    void clear(){
        _size = 0;
    }
};

7.hash表

vector中存储自建单向链表链头结点的地址。

#include 
#include 
using namespace std;
template<typename T1,typename T2>
struce node{
	T1 key;
	T2 value;
	node* next;
	node(T1 k,T2 v,node* n):key(k),value(v),next(n){};
}
class Hash{
	private:
	vector<node*> h;
	int size=0;
	int capacity = 0;	
	public:
		Hash(){
			h.resize(10);
			capacity = 10;
		}
		Hash(int n){
			h.resize(n);
			capacity = n;
		}
		~Hash(){
		    delete[] h;	
		}
		void push(T1 key,T2 value){
			int index = key % capacity; // 得到key对应的index 
			node* e = new node(key,value,nullptr);
			node* t = h[index];
			if(t == nullptr){ // 如果还没有对应的链表 创建一个 
				h[index] = e;
				++size;
			}else{ // 新节点加到链表末尾 
				while(t->next){
					t=t->next;
				}
				t->next = e;
			}
		}
		T2& get(T1 key){
			int index = key % capacity;
			node* tmp = h[index];
			while(tmp){
				if(tmp->key == key){
					return tmp->value;
				}
				tmp=tmp->next;
			}
			return -1;
		}
};

8. 有序数组和为target

左右下表指针往中间扫描,如果两指针和比target小,左指针往右一步,如果比target大,右指针往左一步。

lass Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int n = numbers.size();
        int i = 0,j = n-1,sum;
        vector<int> ans;
        while(i < j) {
            sum = numbers[i] + numbers[j] ;
            if (sum == target) {
                ans.push_back(i+1);
                ans.push_back(j+1);
                break;
            } else if (sum < target) {
                ++i;
            } else if (sum > target) {
                --j;
            }
        } 
        return ans;
    }
};

9. 第k大的数

解法一:快排 找第n-k小,加入随机化后的时间复杂度是O(n)。

class Solution {
    int quickSort(vector<int>& nums,int l,int r, int k) {
        // 找枢纽元素
        int pos = l + rand() % (r - l + 1);
        // swap(nums[pos],nums[r]);
        // 将数组中的元素按照枢纽分为两部分 一部分比它大 一部分比它小
        // cout << pivot << endl;
        int x = nums[pos], i = l - 1;
        swap(nums[pos], nums[r]);
        for (int j = l; j < r; ++j) {
            if (nums[j] <= x) {
                swap(nums[++i], nums[j]);
            }
        }
        swap(nums[i + 1], nums[r]);
        if (i+1 == k) {
            return nums[i+1];
        } else {
            return i+1 < k ? quickSort(nums,i + 2,r,k) : 
            quickSort(nums,l,i,k);
        }                                    
    }
public:
    int findKthLargest(vector<int>& nums, int k) {
        srand((int)time(0));
        int n = nums.size();
        return quickSort(nums,0,n-1,n-k);
    }
};

解法二:堆

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int,vector<int>,greater<int>> pq; // 使用小根堆
        for (int i=0;i<nums.size();++i) {
            if (pq.size() < k) { // 堆中的元素
                pq.push(nums[i]);
            } else { // 堆中的元素》k个 判断当前元素和堆顶元素的大小
                int t = pq.top();
                if (t < nums[i]) {
                    pq.pop();
                    pq.push(nums[i]);
                }
            }
        }
        return pq.top();
    }
};

10. 圆圈中剩下的最后一个数字

// 数学 n-1个数字删除的下标为x,则其在n个数中的下标为x+m
// f(n,m) = (f(n-1,m) + m) % n;
// 非递归解法
class Solution {
public:
    int lastRemaining(int n, int m) {
        int res = 0;
        for (int i=2;i<=n;++i) {
            res = (res+m) % i;
        }
        return res;
    }
};
// 队列模拟
class Solution {
public:
    int lastRemaining(int n, int m) {
        queue<int> q;
        for(int i = 0; i < n; i++) q.push(i);
        int cnt = 0;
        while(q.size() > 1){
            int t = q.front();
            q.pop();
            cnt++;
            if(cnt == m){
                cnt = 0;
                continue;
            }
            q.push(t);
        }
        return q.front();
    } 
};

遇到较少:

1.洗牌算法

针对原数组随机生成一个索引下标,将该索引上的元素添加到新数组中。在原数组中将该元素交换到末尾,表示已经删除,下次生成索引下标的时候范围-1,表示忽略尾部的元素。

void shuffle(int arr[],int length) {
    if (arr == nullptr || length == 0) {
        return;
    }
    int rand_index;//每次随机生成的下标
    srand((int)time(0));
    for (int i=0;i<length;++i) {
        // 每次随机生成一个从i到length-1范围内下标,和位置i处的交换
        rand_index = i + rand() % (length - i);
        swap(arr[i],arr[rand_index]);
    }
}

你可能感兴趣的:(面试,算法,c++)