校招面试中常见的算法题整理【长文】

news2024/10/6 8:23:24

⭐️我叫恒心,一名喜欢书写博客的研究生在读生。

原创不易~转载麻烦注明出处,并告知作者,谢谢!!!

这是一篇近期会不断更新的博客欧~~~ 有什么问题的小伙伴 欢迎留言提问欧。

文章目录

  • 前言
  • 一、链表问题
    • 1 合并有序的链表
    • 2 翻转链表
    • 3 重排链表
    • 4 奇偶链表
  • 二、设计模式
    • 1 单例模式
      • 1.1 饿汉模式(线程安全)
      • 1.2 :smiling_imp: 懒汉模式(线程安全)
      • 1.3 为什么要用双检测,只检测一次不行吗?
      • 1.4 优雅的单例模式
      • 1.4 单例模式的适用场景
    • 2 工厂模式
      • 2.1 简单工厂
      • 2.2 抽象工厂
    • 3 装饰者模式
    • 4 观察者模式
  • 三、排序算法
    • 1 快速排序
    • 2 归并排序
    • 3 堆排序
  • 四、:o:设计一个LRU缓存算法
  • 五、写三个线程交替打印ABC
  • 六、Top K问题
    • 6.1 利用堆
    • 快排实现
  • 七、其他
    • 洗牌算法
  • 八、图数据结构
    • 8.1 最短路径算法

前言

本文主要介绍一些面试中常见的链表算法、设计模式、排序算法、情景题与图方面的数据结构算法。

🐶 参加过今年的暑期实习面试、秋招面试,许多公司在面试的时候通常会考察一些基本的算法,主要是考察以下你的数据结构、设计模式以及代码能力的长度,这部分比较考察编程能力,但是通常也不会出的太难,问题往往都是比较经典和具有代表性的。

一、链表问题

1 合并有序的链表

力扣链接

将两个有序的链表合并为一个新链表,要求新的链表是通过拼接两个链表的节点来生成的。

输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4

#include <iostream>
using namespace std;

struct myList {
    int val;
    myList* next;
    myList(int _val) :val(_val), next(nullptr) {}// 注意这里初始化列表是,
};

myList* merge(myList* l1, myList* l2) {

    if (l1 == nullptr) return l2;
    if (l2 == nullptr) return l1;
    myList head(0);
    myList* node = &head;
    while (l1 != nullptr && l2 != nullptr) {
        if (l1->val < l2->val) {
            node->next = l1;
            l1 = l1->next;

        }
        else {
            node->next = l2;
            l2 = l2->next;
        }
        node = node->next;
    }

    if (l1 == nullptr)
        node->next = l2;
    if (l2 == nullptr)
        node->next = l1;

    return head.next;

};

int main(void) {

    myList* node0 = new myList(0);
    myList* node1 = new myList(1);
    myList* node2 = new myList(2);
    myList* node3 = new myList(3);

    myList* node4 = new myList(1);
    myList* node5 = new myList(4);
    node0->next = node1;
    node1->next = node2;
    node2->next = node3;
    node3->next = nullptr;
    node4->next = node5;
    node5->next = nullptr;

    auto node = merge(node0, node4);
    while (node != nullptr) {
        cout << node->val << endl;
        node = node->next;
    }

    return 0;
}

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1 == nullptr) return l2;
        if(l2 == nullptr) return l1;
        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;
        while(l1 && l2){
            if(l1->val <= l2->val){
                cur->next = l1;
                cur = l1;
                l1 = l1->next;
            }else if(l1->val > l2->val){
                cur->next = l2;
                cur = l2;
                l2 = l2->next;
            }
        }
        if(l1!=nullptr){
            cur->next = l1;
        }else if(l2 != nullptr){
            cur->next = l2;
        }
        return dummy->next;
    }
};

2 翻转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

#include<algorithm>
#include<unordered_map>
#include <iostream>
#include<vector>

using namespace std;

struct node {
    int  data;
    struct node* next;
    node(int _data) :data(_data), next(nullptr) {
    }
};

struct node* init() {
    node* head = new node(1);
    node* node1 = new node(2);
    node* node2 = new node(3);
    node* node3 = new node(4);
    node* node4 = new node(5);

    head->next = node1;
    node1->next = node2;
    node2->next = node3;
    node3->next = node4;
    node4->next = nullptr;

    return head;
}

struct node* reverse(node* head) {
    node* newHead = nullptr;
    while(head){
		node* nextTemp =  head->next;
        head->next = newHead;
        newHead = head;
        head = nextTemp;
    }
    return newHead;
}

int main(){
    auto head = init();
    head = reverse(head);
    while (head != nullptr) {
        cout << head->data << endl;
        head = head->next;
    }

    return 0;
}

3 重排链表

给定一个单链表 L:L0→L1→…→Ln-1→Ln , 将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:
给定链表 1->2->3->4, 重新排列为 1->4->2->3.
示例 2:
给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.

方法一:

线性法:

class Solution {
public:
    void reorderList(ListNode *head) {
        if (head == nullptr) {
            return;
        }
        vector<ListNode *> vec;
        ListNode *node = head;
        while (node != nullptr) {
            vec.emplace_back(node);
            node = node->next;
        }
        int i = 0, j = vec.size() - 1;
        while (i < j) {
            vec[i]->next = vec[j];
            i++;
            if (i == j) {
                break;
            }
            vec[j]->next = vec[i];
            j--;
        }
        vec[i]->next = nullptr;
    }
};

改进方法:

  • 找到原链表的中间节点「876. 链表的中间结点」

    • 我们可以使用快慢指针来 *O*(*N*) 地找到链表的中间节点
  • 将原链表的右半端反转(参考「206. 反转链表」)。

    • 我们可以使用迭代法实现链表的反转。
  • 将原链表的两端合并。

    • 因为两链表长度相差不超过 11,因此直接合并即可。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* searchMid(ListNode* head){
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast && fast->next){
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }

    ListNode* reverseList(ListNode* head){
        ListNode* cur = head;
        ListNode* newHead = nullptr;
        while(cur){
            ListNode* tempNext = cur->next;
            cur->next = newHead;
            newHead = cur;
            cur = tempNext;
        }
        return newHead;
    }

    void mergeList(ListNode* l1, ListNode* l2){
        while(l1 && l2){
            ListNode* temp1Next = l1->next;
            ListNode* temp2Next = l2->next;
            l1->next = l2;
            l2->next = temp1Next;
            l1 = temp1Next;
            l2 = temp2Next;
        }
    }
    void reorderList(ListNode* head) {
        ListNode* L1 = head;
        // 先找到中间节点
        // 通过中间阶段切分成两部分
        // 将后半部分逆序
        // 逆序的后半部分
        // 合并这两部分
        if(head == nullptr) return ;
        ListNode* mid = searchMid(L1);
        L1 = head;
        ListNode* L2 = mid->next;
        mid->next = nullptr;
        L2 = reverseList(L2);
        mergeList(L1,L2);
    }
};

4 奇偶链表

力扣链接

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(head == nullptr) return head;
        ListNode* first = head;
        ListNode* second = head->next;
        ListNode* cur = second;// 保存偶数链表的头
        while(second!=nullptr && second->next!=nullptr){
            //ListNode* tempNext = first->next;
            //cout<<first->val<<" ";
            first->next = second->next;
            first = first->next;// 记得移动指针
            second->next = second->next->next;
            second = second->next;
        }
        first->next = cur;
        return head;
    }
};

二、设计模式

面试的时候关于项目部分,基本上会问起数据结构,对于C++而言,学好设计模式也是非常重要的:

简单的掌握单例模式是肯定远远不够的,至少今年面试的,单例模式,面试官甚至连问得欲望都没有,但是其他集中模式倒是比较有趣,建议以下几种设计模式一定要掌握好

  • 单例模式: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 工厂模式:包括简单工厂模式、抽象工厂模式、工厂方法模式
    • 简单工厂模式: 主要用于创建对象。用一个工厂来根据输入的条件产生不同的类,然后根据不同类的虚函数得到不同的结果。
    • 抽象工厂模式:定义了一个创建一系列相关或相互依赖的接口,而无需指定他们的具体类。
  • 观察者模式:定义了一种一对多的关系,让多个观察对象同时监听一个主题对象,主题对象发生变化时, 会通知所有的观察者,使他们能够更新自己。
  • 装饰模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成派生类更为灵活。

1 单例模式

类中包含一个静态成员指针,该指针指向该类的一个对象,提供一个公有的静态成员方法,返回该对象指针,为了使得对象唯一,将构造函数等涉及到创建对象的都设为私有。

有懒汉模式和饿汉模式

1.1 饿汉模式(线程安全)

#include<iostream>
using namespace std;

class SingleInstance{
public:
	static SingleInstance* getInstance(){
		return ins;
	}
	~SingleInstance(){};
private:
	static SingleInstance* ins;
	SingleInstance(){
		cout<<"SingleInstance() 执行"<<endl;
	}
}; 
SingleInstance* SingleInstance::ins = new SingleInstance();
int main(){
	SingleInstance* instance = SingleInstance::getInstance();
	return 0;	
} 

1.2 😈 懒汉模式(线程安全)

#include<iostream>
#include<pthread.h>
#include<algorithm>
using namespace std;
class SingleInstance{
public:
	static SingleInstance* getInstance(){
		if(ins == nullptr){
			// 创建过程中注意上锁
			pthread_mutex_lock(&mutex);
			if(ins == nullptr){
				ins = new SingleInstance();
			} 
			pthread_mutex_unlock(&mutex);
		}
		return ins;
	}
	~SingleInstance(){};
	// 互斥锁
	static pthread_mutex_t mutex; 
private:
	SingleInstance(){
		cout<<"懒汉模式 "<<endl;
	}
	static SingleInstance* ins;
};
SingleInstance* SingleInstance::ins = nullptr;
pthread_mutex_t SingleInstance::mutex;
int main(){
	SingleInstance* instance = SingleInstance::getInstance();
	delete instance;
	return 0;
}

1.3 为什么要用双检测,只检测一次不行吗?

如果只检测一次,在每次调用获取实例的方法时,都需要加锁,这将严重影响程序性能。双层检测可以有效避免这种情况,仅在第一次创建单例的时候加锁,其他时候都不再符合NULL == p的情况,直接返回已创建好的实例。

1.4 优雅的单例模式

#include<iostream>
using namespace std;

//单例模式
class Singleton{
private:
    Singleton(){printf("Singeleton init \n");};
    ~Singleton(){printf("delete Singeleton \n");};
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) =delete;
public:
    static Singleton* GetInstance(){
        static Singleton instance;  //函数内的静态局部变量,第一次访问才初始化,程序结束,自动释放。
        return &instance;
    }
};


int main(){
  Singleton* s = Singleton::GetInstance();
  return 0;
}

1.4 单例模式的适用场景

  • 系统只需要一个实例对象,或则考虑到资源消耗的太大而只允许创建一个对象。
  • 客户调用类的单个实例只允许使用一个公共访问点,出了该访问点之外不允许通过其他方式访问该实例(就是共有的静态方法)

2 工厂模式

工厂模式的目的是为了解耦,为了进一步解耦,可以在简单工厂模式的基础上发展成抽象工厂模式,既连工厂模式都抽象出来。

2.1 简单工厂

**建立一个工厂类,对实现同一个接口的一些类进行实例的创建。**简单工厂模式的是指是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。

#include<iostream>
using namespace std;
// 产品类
class Product{
public:
	Product(){};
	virtual void show()=0;// 纯虚函数 
}; 

class productA : public Product{
public:
	productA(){};
	void show(){
		cout<<"product A create!"<<endl;
	}
	~productA(){};
};

class productB : public Product{
public:
	productB(){};
	void show(){
		cout<<"product B create!"<<endl;
	}
	~productB(){};
};

class productC : public Product{
public:
	productC(){};
	void show(){
		cout<<"product C create!"<<endl;
	}
	~productC(){};
};

// 简单工厂类
class simpleFactory{
public:
	simpleFactory(){};
	Product* product(const string str){
		if(str == "productA")
			return new productA();
		if(str == "productB")
			return new productB();
			return NULL;
	};
}; 

  
int main(){
	simpleFactory obj;
	Product* pro;
	pro = obj.product("productA");
	pro->show();// product A create!
	delete pro;
	
	pro = obj.product("productB");
	pro->show();// product B create!
	delete pro;
	return 0;
} 

2.2 抽象工厂

工厂模式目的就是代码解耦,如果我们不采用工厂模式,如果要创建产品A、B,通常做法采用用swith..case
句,那么想一想后期添加更多的产品进来,我们不是要添加更多的switch..case吗?这样就很麻烦,而且也不符合设计模式中的开放封闭原则。
为了进一步解耦,在简单工厂的基础上发展出了抽象工厂模式,即连工厂都抽象出来,实现了进一步代码解耦。

#include<iostream>
using namespace std;
// 产品类
class Product{
public:
	Product(){};
	virtual void show()=0;// 纯虚函数 
}; 

class ProductA : public Product{
public:
	productA(){};
	void show(){
		cout<<"product A create!"<<endl;
	}
	~ProductA(){};
};

class ProductB : public Product{
public:
	productB(){};
	void show(){
		cout<<"product B create!"<<endl;
	}
	~ProductB(){};
};

class ProductC : public Product{
public:
	productC(){};
	void show(){
		cout<<"product C create!"<<endl;
	}
	~ProductC(){};
};

// 简单工厂类
class Factor{
public:
	Factor(){};
	virtual Product* CreateProduct() = 0;// 纯虚函数  
}; 
// 工厂类A,只生产A产品
class FactorA:public Factor{
public:
	Product* CreateProduct(){
		Product* product_  = nullptr;
		product_ = new ProductA();
		return product_;
	}
}; 

class FactorB:public Factor{
public:
	Product* CreateProduct(){
		Product* product_  = nullptr;
		product_ = new ProductB();
		return product_;
	}
};

  
int main(){
	Product* product = nullptr;
	auto myFactorA = new FactorA();
	product = myFactorA->CreateProduct();
	product->show();
	delete product;

	auto myFactorB = new FactorB();
	product = myFactorB->CreateProduct();
	product->show();
	delete product;
	return 0;
} 

3 装饰者模式

借鉴一本书上的例子!这真是我见过写得漂亮的装饰者模式的句子

从中你可以学习到

  • 虚函数设计意义
  • 智能指针的使用
#include<iostream>
#include<memory>
using namespace std;

class Transform{
public:
	virtual void move() = 0; 
};

class Car : public Transform{
public:
	Car(){
		cout<<"我是变形金刚"<<endl; 
	}
	void move(){
		cout<<"陆地移动"<<endl;
	}
};

class Change : public Transform{
public:
	Change(shared_ptr<Transform> tran){
		this->transform = tran;
	}
	void move(){
		this->transform->move();
	}
private:
	shared_ptr<Transform> transform;
};

class Robot : public Change{
public:
	Robot(shared_ptr<Transform> tran):Change(tran) {
		cout<<"变身Robot"<<endl;
	}
	void say(){
		cout<<"我是Robot"<<endl;
	}
};

class AirFly : public Change{
public: 
	AirFly(shared_ptr<Transform> tran):Change(tran) {
		cout<<"变身AirFly"<<endl;
	}
	void say(){
		cout<<"我在天上飞"<<endl;
	}
};


int main(){
	shared_ptr<Transform> camaro = make_shared<Car>();
	camaro->move();
	cout<<"-------------"<<endl;
	shared_ptr<Robot> rbb = make_shared<Robot>(camaro);
	rbb->move();
	rbb->say();
	cout<<"-------------"<<endl;
	shared_ptr<AirFly> air = make_shared<AirFly>(camaro);
	air->move();
	air->say();
	return 0;	
}

运行结果:

image-20221211203221903

4 观察者模式

相比于前面几种设计模式的代码,观察者模式可能会稍微长一点,但是核心的思想也是比较简单的。

1、我们希望观察一个对象的行为,监听者数量是不确定的,但是被监听者往往是少数。

2、通过列表的形式添加监听者对象

3、 被监听者每次行为都会被发送给监听者进行回调。

如果你接触过QT,那这个设计模式你必须要东,因为信号槽机制的设计原理就是基于此设计而出的,相对于其他图形化桌面设计,这种设计模式也是非常有借鉴意义。

#include <iostream>
#include <string>
#include <list>

// 被观察者基类
class Subject;
// 观察者基类
class Observer
{
protected:
    std::string name;
    Subject *sub;

public:
    Observer(std::string name, Subject *sub)
    {
        this->name = name;
        this->sub = sub;
    }
    virtual void update() = 0;
};

class StockObserver : public Observer
{
public:
    StockObserver(std::string name, Subject *sub) : Observer(name, sub) {}
    void update();
};

class NBAObserve : public Observer
{
public:
    NBAObserve(std::string name, Subject *sub) : Observer(name, sub) {}
    void update();
};

class Subject
{
protected:
    std::list<Observer *> observers;

public:
    std::string action;
    virtual void attach(Observer *) = 0;
    virtual void detach(Observer *) = 0;
    virtual void notify() = 0;
};

class Secretary : public Subject
{
    void attach(Observer *observer)
    {
        observers.push_back(observer);
    }
    void detach(Observer *observer)
    {
        std::list<Observer *>::iterator iter = observers.begin();
        while ((*iter) == observer)
        {
            observers.erase(iter);
            return;
        }
        ++iter;
    }
    void notify()
    {
        std::list<Observer *>::iterator iter = observers.begin();
        while (iter != observers.end())
        {
            (*iter)->update();
            ++iter;
        }
    }
};

void StockObserver::update()
{
    std::cout << name << "收到消息: " << sub->action << std::endl;
    if (sub->action == "老板来了!")
    {
        std::cout << "立马关闭股票,认真工作!" << std::endl;
    }
}
void NBAObserve::update()
{
    std::cout << name << "收到消息: " << sub->action << std::endl;
    if (sub->action == "老板来了!")
    {
        std::cout << "立马关闭NBA节目,认真工作!" << std::endl;
    }
}

int main()
{
    Subject *BOSS = new Secretary();
    Observer *xa = new NBAObserve("xa", BOSS);
    Observer *xb = new NBAObserve("xb", BOSS);
    Observer *xc = new StockObserver("xc", BOSS);

    BOSS->attach(xa);
    BOSS->attach(xb);
    BOSS->attach(xc);

    BOSS->action = "去吃饭了!";
    BOSS->notify();
    std::cout << std::endl;
    BOSS->action = "老板来了!";
    BOSS->notify();
    return 0;
}

运行结果:

image-20221211203133171

三、排序算法

不单单要会写代码 还需要会分析时间复杂度。

image-20220512181345286

1 快速排序

void quickSort(vector<int> &nums, int begin, int end){
	// 判断是否是有序的 如果有序的话,直接退出 
	if(begin >= end) return ;
    // 优化部分
    int p = round(1.0*rand()/RAND_MAX*(end - begin) + begin);
    swap(nums[begin],nums[p]);
	int low = begin, high = end, key = nums[begin];
	while(low < high){
		while(low < high && nums[high] >= key){
			high--;
		}
		if(low < high) nums[low++] = nums[high];
		while(low < high && nums[low] <=key){
			low++;
		}
		if(low < high) nums[high--] = nums[low];
	
	}
	nums[low] = key;
	print_nums(nums);
	
	quickSort(nums,begin, low - 1);
	quickSort(nums, low + 1, end);
}

2 归并排序

又称为二路排序。

#include<iostream>
#include<vector>
using namespace std;
void print_nums(vector<int> nums){
	for(auto num : nums){
		cout<<num<<" "; 
	}
	cout<<endl;
}

void mergeSort(vector<int>& data, vector<int>& temp, int begin, int end){
	if(begin >= end) return ; // 注意当begin >= end 表示不需要排序了
	// 分成两部分 
	int mid = begin + (end - begin) / 2;// 防止溢出 
	int low1 = begin, high1 = mid;
	int low2 = mid + 1, high2 = end;
	// 因为到最后一步的时候copy数组是排序好的,因此如下写法视为省去交换
	// num.assign(copyNums.begin(), copyNums.end()) 
	mergeSort(temp, data, low1, high1);
	mergeSort(temp, data, low2, high2);
	
	int index = low1;//  此处不能写成0 而应该以每次的左边界
	// 针对左部分或者右边部分进行切分后的排序 
	while(low1 <= high1 && low2 <= high2){
		temp[index++] = data[low1] < data[low2] ? data[low1++] :data[low2++];
	}
	
	// 左边部分赋值给temp 
	while(low1 <= high1){
		temp[index++] = data[low1++];
	}
	
	// 右边部分赋值给temp 
	while(low2 <= high2){
		temp[index++] = data[low2++];
	}
	
	
	print_nums(temp);
}

// 归并排序 
int main(){
	vector<int> nums{8,9,1,4,2,3,6,7,5,5} ;
	vector<int> temp(nums);// 注意temp
	mergeSort(nums, temp , 0 , nums.size() - 1);
	nums.assign(temp.begin(), temp.end());
	print_nums(nums);
	return 0;
}

3 堆排序

思路:基于堆结构,每次将最后一个节点和第一个节点交换,移除掉最后一个节点

堆:

  1. 完全二叉树(要学会辨识二叉树)
    1. 从上往下
    2. 从左往右
  2. 父节点的值大于子节点

image-20220511154716786

#include<iostream>
#include<vector>
using namespace std;
void heapyfy(vector<int>& nums, int n, int i){
	//对有一定顺序的堆,
	//当前第i个结点取根左右的最大值(这个操作称heapfiy)
	// 从上而下 
	if(i>=n) return; 
    int c1 = 2*i + 1;// 左子节点
    int c2 = 2*i + 2;// 右子节点
    int max = i;
    if(c1 < n && nums[c1] > nums[max]) max = c1;
    if(c2 < n && nums[c2] > nums[max]) max = c2;
    if(max !=i){
        // 如果当前节点(父节点)不等于i
        swap(nums[i],nums[max]);
        heapyfy(nums, n, max);
    }
}

void build_heap(vector<int>& nums, int n){
	//建立大根堆,从树的倒数第二层第一个结点开始,
	//从最后一个节点的父节点开始做heapfy,直到根节点,就可以将无序数组组织成堆的形式
	int last_node = n - 1;
	int parent_node = (last_node - 1) / 2;
	for(int i = parent_node; i >= 0; i--){
		heapyfy(nums,n, i);
	}
}

void sort_heap(vector<int>& nums, int n){
	/*排序将问题分解成了2个步骤:
先建立一个堆,在依据堆顶为最大值的性质,循环抽出堆顶最大值交换到堆的末尾位置,
有序序列,由于抽出最大值破坏了堆的性质,因此要重新heapify
而在建立堆的过程中,首先面临的是一堆无序的数,
需要从最后一个节点的父节点开始heapify,
才能使得整个数组变成堆,其中heapify的过程就是递归将调整父节点子节点的过程*/

	build_heap(nums,n);
	for(int i = n-1; i >= 0; i--){
		// 跟最后一个节点交换 
		swap(nums[0],nums[i]);
		// heapy一下当前的节点
		heapyfy(nums, i, 0); 
	}
}

int main(){
	vector<int> nums = {2,5,1,3,10,4};
	sort_heap(nums, nums.size());
	for(auto num:nums) cout<<num<<endl; 
	return 0;
} 

四、⭕️设计一个LRU缓存算法

力扣链接

设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。

它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

#include<iostream>
#include<unordered_map> 
#include<algorithm>
using namespace std;

//这里放你的代码
struct DKLinkNode{
    int key,value;
    DKLinkNode* next;
    DKLinkNode* pre;
    DKLinkNode():key(0),value(0),pre(nullptr),next(nullptr){}
    DKLinkNode(int _key, int _val):key(_key),value(_val),pre(nullptr),next(nullptr){}
};

class LRUCache {
private:
    // 借助哈希表来查询存储的位置,key value(链表节点)
    unordered_map<int, DKLinkNode*> cache;
    int capacity;
    int size;
    DKLinkNode* head;
    DKLinkNode* tail;
public:
    LRUCache(int _capacity):capacity(_capacity),size(0) {
        // 创建头尾 伪节点 便于定位
        head = new DKLinkNode();
        tail = new DKLinkNode();
        head->next = tail;
        tail->pre = head;
    }
    
    ~LRUCache(){
    	if(head != nullptr){
    		delete head;
    		head = nullptr;
		}
		if(tail != nullptr){
			delete tail;
			tail = nullptr;
		}
		for(auto& c : cache){
			if(c.second != nullptr){
				delete c.second;
				c.second = nullptr;
			}
		}
	}
    
    int get(int key) {
        // 如果双向链表中没有这个节点则直接返回
        if(!cache.count(key)){return -1;}
        // 如果有节点 则通过哈希表获取这个节点的地址,将这个节点移到前面
        DKLinkNode* node = cache[key];
        moveToHead(node);
        return node->value;
    }
    
    void put(int key, int value) {
        // 如果哈希表查找不到这个key 则插入新的值到哈希表中
            // 将新的值插入双向链表的头部
        if(!cache.count(key)){
            DKLinkNode* node = new DKLinkNode(key, value);
            cache[key] = node;
            addHeadNode(node);
            ++size;
            // 如果当前的容量大于缓存的最大容量,则移除某段节点
            if(size > capacity){
                DKLinkNode* rNode = removeTail();
                cache.erase(rNode->key);
                delete rNode;
                --size;
            }
        }else{
            // 如果查找得到key,则将该节点移动到头部
            DKLinkNode* moveNode = cache[key];
            // 更新当前key对应的value 并移动链表
            moveNode->value = value;
            moveToHead(moveNode);
        }
    }

    void addHeadNode(DKLinkNode* node){
        node->pre = head;
        node->next = head->next;
        head->next->pre = node;
        head->next  = node;
    }

    void removeNode(DKLinkNode* rNode){
        rNode->pre->next = rNode->next;
        rNode->next->pre = rNode->pre;
    }

    DKLinkNode* removeTail(){
        DKLinkNode* rNode = tail->pre;
        removeNode(rNode);
        return rNode; 
    }

    void moveToHead(DKLinkNode* node){
        // 删除当前节点
        removeNode(node);
        // 在头结点处添加进去
        addHeadNode(node);
    }
};

int main(){
	LRUCache* cache = new LRUCache(2);
	cache->put(1, 1);
	cache->put(2, 2);
	int res = cache->get(1);       // 返回  1
	cout<<res<<endl;
	cache->put(3, 3);    // 该操作会使得密钥 2 作废
	res = cache->get(2);       // 返回 -1 (未找到)
	cache->put(4, 4);    // 该操作会使得密钥 1 作废
	res = cache->get(1);       // 返回 -1 (未找到)
	cout<<res<<endl;
	res = cache->get(3);       // 返回  3
	cout<<res<<endl;
	res = cache->get(4);       // 返回  4
	cout<<res<<endl;
	return 0;
} 

五、写三个线程交替打印ABC

#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
using namespace std;

mutex mymutex;
condition_variable cv;
int flag=0;

void printa(){
    unique_lock<mutex> lk(mymutex);
    int count=0;
    while(count<10){
        while(flag!=0) cv.wait(lk);
        cout<<"thread 1: a"<<endl;
        flag=1;
        cv.notify_all();
        count++;
    }
    cout<<"my thread 1 finish"<<endl;
}
void printb(){
    unique_lock<mutex> lk(mymutex);
    for(int i=0;i<10;i++){
        while(flag!=1) cv.wait(lk);
        cout<<"thread 2: b"<<endl;
        flag=2;
        cv.notify_all();
    }
    cout<<"my thread 2 finish"<<endl;
}
void printc(){
    unique_lock<mutex> lk(mymutex);
    for(int i=0;i<10;i++){
        while(flag!=2) cv.wait(lk);
        cout<<"thread 3: c"<<endl;
        flag=0;
        cv.notify_all();
    }
    cout<<"my thread 3 finish"<<endl;
}
int main(){
    thread th1(printa);
    thread th2(printb);
    thread th3(printc);

    th1.join();
    th2.join();
    th3.join();
    cout<<" main thread "<<endl;


}

六、Top K问题

常见的形式 :

给定10000个整数,找第K大(第K小)的数
给定10000个整数,找出最大(最小)的前K个数
给定100000个单词,求前K词频的单词

有效的解决办法:

解决Top K问题若干种方法

  • 使用最大最小堆。求最大的数用最小堆,求最小的数用最大堆。
  • Quick Select算法。使用类似快排的思路,根据pivot划分数组。
  • 将1000…个数分成m组,每组寻找top K个数,得到m×K个数,在这m×k个数里面找top K个数。的

按顺序扫描这10000个数,先取出K个元素构建一个大小为K的最小堆。每扫描到一个元素,如果这个元素大于堆顶的元素(这个堆最小的一个数),就放入堆中,并删除堆顶的元素,同时整理堆。如果这个元素小于堆顶的元素,就直接pass。最后堆中剩下的元素就是最大的前Top K个元素,最右的叶节点就是Top 第K大的元素。

6.1 利用堆

头文件是queue

最小k个数 应该用大根堆来做!!!

因为这样才能减少存储空间,而不是将全部数据存储下来!!!

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> ret;
        if (k==0 || k > input.size()) return ret;
        priority_queue<int, vector<int>> pq;
        for (const int val : input) {
            if (pq.size() < k) {
                pq.push(val);
            }
            else {
                if (val < pq.top()) {
                    pq.pop();
                    pq.push(val);
                }

            }
        }

        while (!pq.empty()) {
            ret.push_back(pq.top());
            pq.pop();
        }
        return ret;
    }
};

自己写一个堆:

class Solution {
public:
    void heapify(vector<int>& nums, int i, int n){
        if(i >= n) return ;
        // 取得左右两个节点,判断这两个节点和根节点哪个最大
        int c1 = 2*i + 1;
        int c2 = 2*i + 2;
        int max = i;
        if(c1 < n && nums[c1] > nums[max]) max = c1;
        if(c2 < n && nums[c2] > nums[max]) max = c2;
        if(max !=i){
            // 调整
            swap(nums[i],nums[max]);
            heapify(nums, max, n);
        }
    }

    void build_heapy(vector<int>& nums, int n){
        int last_node = n-1;
        int parent_node = (last_node - 1)/2;
        // 根据父节点进行调整堆
        for(int i = parent_node; i >=0; i--){
            heapify(nums, i ,n);
        }
    }

    int findKthLargest(vector<int>& nums, int k) {
        int n = nums.size();
        // 创建大根堆
        build_heapy(nums,n);
        for(int i = nums.size()-1; i >= nums.size() - k + 1; i--){
            // 将最后一个节点与第0个节点交换
            swap(nums[0],nums[i]);
            heapify(nums, 0, i);
        }
        return nums[0];
    }
};

快排实现

class Solution {
public:
    int quickSort(vector<int>& nums, int left, int right){
        int pivot = nums[left];
        int idx = left;
        for(int i = left + 1; i <=right; ++i){
            if(nums[i] >= pivot){
                // 比中轴大才交换。
                idx++;
                swap(nums[idx], nums[i]);
            }
        }
        swap(nums[idx], nums[left]);// 最左边的位置和idx 交换
        return idx;
    }

    int findKthLargest(vector<int>& nums, int k) {
        int left = 0;
        int right = nums.size() - 1;
        int idx = 0;
        while(true){
            idx = quickSort(nums, left, right);
            if(idx == k - 1){
                // 注意这里是第k个。
                return nums[idx];
            }else if(idx < k -1){
                left = idx + 1;
            }else{
                right = idx - 1;
            }
        }
        return 0;
    }
};

七、其他

洗牌算法

所谓洗牌算法,就是给你一个1到n的序列,让你随机打乱,保证每个数出现在任意一个位置的概率相同,也就是说在n!个的排列中,每一个排列出现的概率相同。

#include<stdio.h>
#include<time.h>
#include <stdlib.h>
//注意传引用
void swap(int &a,int &b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
void RandomShuffle(int a[], int n)
{
	if(a == NULL)	return;
	
	for(int i = 0;i < n;i++)
	{
		int index = i + rand()%(n-i);
		swap(a[index],a[i]);
	}
}

int main()
{
	srand((unsigned)time(0));
	int a[54];
	for(int i = 0;i < 54;i++)
	{
		a[i] = i+1;
	}
	RandomShuffle(a,54);
	return 0;
}

八、图数据结构

8.1 最短路径算法

1.Dijkstra算法是计算图中的一个点到其它点的最小路径.
  算法思路: 贪心算法.
    将图中所有点分成 S(已求出解)U(未求出解)2个点集.dist[i]表示v0到v[i]当前已求得得最短路径.A[n][n]为边集
    1.从剩下的边集合中选出dist最短的边并将边的另一顶点vi从U中加入S.
    2.更新与vi连接的所有且并未在S中的点的dist矩阵值,dist[vk]=min(dist[vk],dist[vi]+A(i,k)).
    3.重复上述操作直到U中无与S中的点相连的点.
2.Floyd算法计算图中任意一对点的最短路径.
  算法思路:  T(n)=O(n^3).
   动态规划法: Dis(i,j) =min(Dis(i,j), Dis(i,k) + Dis(k,j)).

下一篇文章更新:

常见的海量数据算法(带实现例子)

最后 🐶狗头保命

一名喜欢书写博客的研究生在读生

如果觉得有用,麻烦三连支持一下欧,希望这篇文章可以帮到你,你的点赞是我持续更新的动力

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/81511.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Anaconda安装详细教程

一、Anaconda下载 &#xff08;1&#xff09;方式一&#xff1a;Anaconda官网 不推荐使用官网下载&#xff1a; &#xff08;1&#xff09;官网下载速度非常慢&#xff0c;需要使用国内源下载 &#xff08;2&#xff09;官网下载的是最新版本&#xff0c;可能使用时会出现意料…

基于java+springboot+mybatis+vue+mysql的地方废物回收机构管理系统

项目介绍 地方废物回收机构管理系统能够通过互联网得到广泛的、全面的宣传&#xff0c;让尽可能多的用户了解和熟知地方废物回收机构管理系统的便捷高效&#xff0c;不仅为用户提供了服务&#xff0c;而且也推广了自己&#xff0c;让更多的用户了解自己。对于地方废物回收机构…

【 SQLite3移植到ARM Linux教程】

SQLite3移植到ARM Linux教程1 下载 SQLite3源码2 复制并解压源码包3 配置编译选项4 编译5 去除调试信息6 复制文件7 运行测试sqlite3SQLite 是一款轻型的数据库&#xff0c;是遵守ACID的关联式数据库管理系统&#xff0c;它的设计目标是嵌入式的&#xff0c;而且目前已经在很多…

MyBatis如何处理表关联

实体类 学生表 添加对应对象 - 教师 private Teacher teacher; 2. Mapper添加对应结果集映射 collection 一对多 学科表 对 学生表 1. 实体类 学科 添加对应集合 – 学生 private List<Student> stuList; 2. Mapper添加对应结果集映射 使用ResultType实现结果多表映…

[附源码]Python计算机毕业设计Django云南美食管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Redis配置、优化以及相命令

目录 一、关系数据库和非关系型数据库 1、关系型数据库 2、非关系型数据库 二、关系型数据库和非关系型数据库区别 1、数据存储方式不同 1.1 关系型数据 1.2 非关系型数据库 2、扩展方式不同 2.1 SQL数据库 2.2 NoSQL数据库 3、对事务性的支持不同 3.1 SQL数据库 …

全网最详细的HTTP协议学习笔记

目录 一、HTTP简介 相关词语 HTTP请求过程 二、HTTP详解 1.在TCP/IP协议中的位置 2.Request(请求消息) 3.Response(响应消息) 4.HTTP状态码 5.HTTP请求方法 6.其他 三、练习自测 四、总结 五、重点&#xff1a;配套学习资料和视频教学 一、HTTP简介 全称&#…

[附源码]Python计算机毕业设计大学生兼职管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

[附源码]Python计算机毕业设计宠物短期寄养平台Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

R语言无监督学习:PCA主成分分析可视化

总览 在监督学习中&#xff0c;我们通常可以访问n个 观测值的p个 特征 集 &#xff0c;并 在相同观测值上测得的 Y。 无监督学习是一组没有相关的变量 Y的方法。在这里&#xff0c;我们重点介绍两种技术… 主成分分析&#xff1a;用于数据可视化或在其他监督学习方法之…

面试官:你说说Springboot的启动过程吧(5.4万字分析启动过程)

文章目录前言一、Springboot是什么二、启动流程2.1 构建Spring Boot项目2.2 启动的日志2.3 启动流程分析说明2.3.1 第一部分&#xff1a;SpringApplication的构造函数A、webApplicationType&#xff08;web应用类型&#xff09;B、引导注册初始化器C、设置初始化器D、设置监听器…

[附源码]Python计算机毕业设计大学生二手物品交易网站Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

锁存器和触发器

大多数数字系统中,除了需要具有逻辑运算和算术运算功能的组合逻辑电路外,还需要具有存储功能的电路。组合电路与存储电路结合构成时序逻辑电路,简称时序电路。 本文将讨论实现存储功能的两种逻辑单元电路∶锁存器和触发器,着重讨论其工作原理与电路结构,以及所实现的不同逻…

手写js——继承

原型链继承 所谓 函数 也就是 函数 Father其本身&#xff0c;也叫作构造函数 &#xff0c;当一个函数被创建的同时&#xff0c;也会为其创建一个 prototype 属性&#xff0c;而这个属性&#xff0c;就是用来指向 函数原型&#xff0c;的我们可以把 prototype 理解为 Father的一…

用 Numba 加速 Python 代码,变得像 C++ 一样快

1. 介绍 Numba 是 python 的即时&#xff08;Just-in-time&#xff09;编译器&#xff0c;即当你调用 python 函数时&#xff0c;你的全部或部分代码就会被转换为“即时”执行的机器码&#xff0c;它将以你的本地机器码速度运行&#xff01;它由 Anaconda 公司赞助&#xff0c…

SpringMVC初配置解析?

在springMVC-servlet中 在web.xml中 SpringMVC常用注解 Controller 负责注册一个bean 到spring 上下文中 RequestMapping 注解为控制器指定可以处理哪些 URL 请求 RequestBody 该注解用于读取Request请求的body部分数据&#xff0c;使用系统默认配置的HttpMessageConverter进行…

CentOS7安装Cockpit网页版图像化服务管理工具

不经意间看到CentOS8说是默认集成了Cockpit——网页版图像化服务管理工具&#xff0c;出于为了更好的管理自己的服务器&#xff0c;于是参考一些资料在自己的服务器CentOS7上也安装了一个。 一、Cockpit是什么 github 地址&#xff1a; https://github.com/cockpit-project/c…

[附源码]Python计算机毕业设计大数据与智能工程系教师档案管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

基于node.js+vue的社区团购网站 毕业设计

在当今社会的高速发展过程中&#xff0c;产生的劳动力越来越大&#xff0c;提高人们的生活水平和质量&#xff0c;尤其计算机科技的进步&#xff0c;数据和信息以人兴化为本的目的&#xff0c;给人们提供优质的服务&#xff0c;其中网上购买团购商品尤其突出&#xff0c;使我们…

移植知识点整理(续,后期持续添加)

一&#xff1a; uboot源码移植准备工作 1.在家目录下创建一个<demo>文件夹 2.将en.SOURCES-stm32mp1-openstlinux-5.10-dunfell-mp1-21-11-17_tar_v3.1.0.xz文件夹拷贝到demo目录下 3.对en.SOURCES-stm32mp1-openstlinux-5.10-dunfell-mp1-21-11-17_tar_v3.1.0.xz进行解压…