本文最后更新于:2021年6月26日 上午
合并有序链表
参考:21 合并两个有序链表
将两个有序的链表合并为一个新链表,要求新的链表是通过拼接两个链表的节点来生成的。
输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #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; }
|
反转链表
参考:206 反转链表 、92 反转链表 II 、25 K 个一组翻转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ListNode* ReverseList(ListNode* pHead) { if (pHead == nullptr || pHead->next == nullptr) return pHead; ListNode dummyNode = ListNode(0); ListNode* pre = &(dummyNode); pre->next = pHead; ListNode* cur = pHead->next; pHead->next = nullptr; ListNode* temp = nullptr; while (cur != nullptr) { temp = cur; cur = cur->next; temp->next = pre->next; pre->next = temp; } return dummyNode->next; }
|
单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class singlePattern { private: singlePattern() {}; static singlePattern* p; public: static singlePattern* instance();
class CG { public: ~CG() { if (singlePattern::p != nullptr) { delete singlePattern::p; singlePattern::p = nullptr; } } }; };
singlePattern* singlePattern::p = new singlePattern(); singlePattern* singlePattern::instance() { return p; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class singlePattern { private: static singlePattern* p; singlePattern(){} public: static singlePattern* instance(); class CG { public: ~CG() { if (singlePattern::p != nullptr) { delete singlePattern::p; singlePattern::p = nullptr; } } }; }; singlePattern* singlePattern::p = nullptr; singlePattern* singlePattern::instance() { if (p == nullptr) { return new singlePattern(); } return p; }
|
简单工厂模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| typedef enum productType { TypeA, TypeB, TypeC } productTypeTag;
class Product {
public: virtual void show() = 0; virtual ~Product() = 0; };
class ProductA :public Product { public: void show() { cout << "ProductA" << endl; } ~ProductA() { cout << "~ProductA" << endl; } };
class ProductB :public Product { public: void show() { cout << "ProductB" << endl; } ~ProductB() { cout << "~ProductB" << endl; } };
class ProductC :public Product { public: void show() { cout << "ProductC" << endl; } ~ProductC() { cout << "~ProductC" << endl; } };
class Factory { public: Product* createProduct(productType type) { switch (type) { case TypeA: return new ProductA(); case TypeB: return new ProductB(); case TypeC: return new ProductC(); default: return nullptr; } } };
|
设计LRU缓存
参考:实现 LRU 缓存
设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。
它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class LRUCache { list<pair<int, int>> cache; unordered_map<int, list<pair<int, int>>::iterator> map; int cap; public: LRUCache(int capacity) { cap = capacity; }
int get(int key) { if (map.count(key) > 0){ auto temp = *map[key]; cache.erase(map[key]); map.erase(key); cache.push_front(temp); map[key] = cache.begin(); return temp.second; } return -1; }
void put(int key, int value) { if (map.count(key) > 0){ cache.erase(map[key]); map.erase(key); } else if (cap == cache.size()){ auto temp = cache.back(); map.erase(temp.first); cache.pop_back(); } cache.push_front(pair<int, int>(key, value)); map[key] = cache.begin(); } };
|
重排链表
参考:143 重排链表
给定一个单链表 L:L0→*L1→…→\Ln*-1→*Ln , 将其重新排列后变为: L0→Ln*→*L*1→*L*n\-1→*L*2→*L*n-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
- 寻找链表中点 + 链表逆序 + 合并链表
- 空间复杂度 O(1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class Solution { public: void reorderList(ListNode* head) { if(!head || !head->next) return; ListNode* mid = middleNode(head); ListNode* l1 = head; ListNode* l2 = mid->next; mid->next = nullptr; l2 = reverse(l2); merge(l1, l2); }
ListNode* middleNode(ListNode* head){ ListNode* slow=head, *fast=head; while(fast->next && fast->next->next){ slow = slow->next; fast = fast->next->next; } return slow; } ListNode* reverse(ListNode* head){ if(!head || !head->next) return head; ListNode* rhead = reverse(head->next); head->next->next = head; head->next = nullptr; return rhead; } void merge(ListNode* l1, ListNode* l2){ ListNode* l1_tmp, *l2_tmp; while(l1 && l2){ l1_tmp = l1->next; l2_tmp = l2->next;
l1->next = l2; l1 = l1_tmp;
l2->next = l1; l2 = l2_tmp; } } };
|
奇偶链表
参考:328 奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Solution { public: ListNode* oddEvenList(ListNode* head) { if (!(head && head->next)) return head; ListNode* head2 = head->next; ListNode* odd = head; ListNode* even = head2; while(odd->next && even->next){ odd->next = even->next; odd = odd->next; even->next = odd->next; even = even->next; } odd->next = head2; return head; } };
|
写三个线程交替打印ABC
参考:1195. 交替打印字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #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 th2(printa); thread th1(printb); thread th3(printc);
th1.join(); th2.join(); th3.join(); cout<<" main thread "<<endl; }
|
Top K问题
解决Top K问题若干种方法
- 使用最大最小堆。求最大的数用最小堆,求最小的数用最大堆。
- Quick Select算法。使用类似快排的思路,根据pivot划分数组。
- 使用排序方法,排序后再寻找top K元素。
- 使用选择排序的思想,对前K个元素部分排序。
- 将1000…..个数分成m组,每组寻找top K个数,得到m×K个数,在这m×k个数里面找top K个数。
使用最大最小堆的思路 (以top K 最大元素为例)
按顺序扫描这10000个数,先取出K个元素构建一个大小为K的最小堆。每扫描到一个元素,如果这个元素大于堆顶的元素(这个堆最小的一个数),就放入堆中,并删除堆顶的元素,同时整理堆。如果这个元素小于堆顶的元素,就直接pass。最后堆中剩下的元素就是最大的前Top K个元素,最右的叶节点就是Top 第K大的元素。
note:最小堆的插入时间复杂度为log(n),n为堆中元素个数,在这里是K。最小堆的初始化时间复杂度是nlog(n)
| struct Node { int value; int idx; Node (int v, int i): value(v), idx(i) {} friend bool operator < (const struct Node &n1, const struct Node &n2) ; };
inline bool operator < (const struct Node &n1, const struct Node &n2) { return n1.value < n2.value; }
priority_queue<Node> pq;
|
使用Quick Select的思路(以寻找第K大的元素为例)
算法的过程是这样的: 首先选取一个枢轴,然后将数组中小于该枢轴的数放到左边,大于该枢轴的数放到右边。 此时,如果左边的数组中的元素个数大于等于K,则第K大的数肯定在左边数组中,继续对左边数组执行相同操作; 如果左边的数组元素个数等于K-1,则第K大的数就是pivot; 如果左边的数组元素个数小于K,则第K大的数肯定在右边数组中,对右边数组执行相同操作。
这个算法与快排最大的区别是,每次划分后只处理左半边或者右半边,而快排在划分后对左右半边都继续排序。
使用选择排序的思想对前K个元素排序 ( 以寻找前K大个元素为例)
扫描一遍数组,选出最大的一个元素,然后再扫描一遍数组,找出第二大的元素,再扫描一遍数组,找出第三大的元素。。。以此类推,找K个元素,时间复杂度为O(N*K)
布隆过滤器
布隆过滤器是一个比特向量或者比特数组,它本质上是一种概率型数据结构,用来查找一个元素是否在集合中,支持高效插入和查询某条记录。常作为针对超大数据量下高效查找数据的一种方法。
它的具体工作过程是这样子的: 假设布隆过滤器的大小为m(比特向量的长度为m),有k个哈希函数,它对每个数据用这k个哈希函数计算哈希,得到k个哈希值,然后将向量中相应的位设为1。在查询某个数据是否存在的时候,对这个数据用k个哈希函数得到k个哈希值,再在比特向量中相应的位查找是否为1,如果某一个相应的位不为1,那这个数据就肯定不存在。但是如果全找到了,则这个数据有可能存在。
为什么说有可能存在呢? 因为不同的数据经过哈希后可能有相同的哈希值,在比特向量上某个位置查找到1也可能是由于某个另外的数据映射得到的。
支持删除操作吗 目前布隆过滤器只支持插入和查找操作,不支持删除操作,如果要支持删除,就要另外使用一个计数变量,每次将相应的位置为1则计数加一,删除则减一。
布隆过滤器中哈希函数的个数需要选择。如果太多则很快所有位都置为1,如果太少会容易误报。