当前位置: 首页 > news >正文

算法 for GAMES

#include <iostream>
#include <stack>int main() {std::stack<int> intStack;// 压入元素到堆栈intStack.push(5);intStack.push(10);intStack.push(15);// 查看堆栈顶部元素std::cout << "Top element: " << intStack.top() << std::endl;// 弹出堆栈顶部元素intStack.pop();// 判断堆栈是否为空if (intStack.empty()) {std::cout << "Stack is empty" << std::endl;} else {std::cout << "Stack is not empty" << std::endl;}// 打印堆栈中的元素(从栈顶到栈底)std::cout << "Stack elements: ";while (!intStack.empty()) {std::cout << intStack.top() << " ";intStack.pop();}std::cout << std::endl;return 0;
}

232. 用栈实现队列 - 力扣(LeetCode)

思路:双栈模拟队列,in2out

队列

#include <iostream>
#include <queue>int main() {// 创建一个整数队列std::queue<int> myQueue;// 向队列中添加元素myQueue.push(10);myQueue.push(20);myQueue.push(30);// 获取队列的大小std::cout << "Queue size: " << myQueue.size() << std::endl;// 访问队列的前端元素std::cout << "Front element: " << myQueue.front() << std::endl;// 访问队列的末尾元素(注意:std::queue没有直接提供访问末尾元素的接口)// 若要访问末尾元素,需要使用其他数据结构来存储队列的拷贝,然后取出拷贝的末尾元素// 弹出队列的前端元素myQueue.pop();// 检查队列是否为空if (myQueue.empty()) {std::cout << "Queue is empty." << std::endl;} else {std::cout << "Queue is not empty." << std::endl;}return 0;
}

排序

复杂度分析:

  1. 快速排序:

    • 稳定性:快速排序不是稳定排序。由于它是通过分区操作将元素分为左右两部分,相等元素可能会在分区过程中交换位置,导致相对顺序发生改变。
    • 原地性:快速排序通常是原地排序的,只需要常数级别的额外空间来保存分区点的索引。
  2. 堆排序:

    • 稳定性:堆排序不是稳定排序。在构建堆的过程中,相等元素可能会被交换,导致相对顺序改变。
    • 原地性:堆排序通常被实现为原地排序,只需要常数级别的额外空间。
  3. 归并排序:

    • 稳定性:归并排序是稳定排序。在合并两个有序子数组时,相等元素不会改变相对顺序。
    • 原地性:归并排序通常不是原地排序的,需要额外的存储空间来合并两个子数组。

C++调用:

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;int main() {std::vector<std::string> words = { "apple", "banana", "grape", "orange", "kiwi" };// 对字符串向量进行排序std::sort(words.begin(), words.end());// 打印排序后的字符串向量for (const std::string& word : words) {std::cout << word << " ";}std::cout << std::endl;std::vector<int> numbers = { 15, 6, 42, 8, 23 };// 对整数向量进行排序std::sort(numbers.begin(), numbers.end(), less<int>());// 打印排序后的整数向量for (const int& num : numbers) {std::cout << num << " ";}std::cout << std::endl;// 降序排序sort(numbers.begin(), numbers.end(), greater<int>());for (const int& num : numbers){cout << num << " ";}return 0;
}

相关题目:

56. 合并区间 - 力扣(LeetCode)

思路:先按右节点从小到大排序,再合并

215. 数组中的第K个最大元素 - 力扣(LeetCode)

思路:从大到小排序,两段数据的长度

搜索

200. 岛屿数量 - 力扣(LeetCode)

思路:找到一个1然后dfs把连起来的全赋值为0

哈希

容器对比和基础操作

(1)vector: vector支持随机访问(通过下标),时间复杂度是O(1);无序vector查找的时间复杂度是O(n),有序vector采用二分查找则是O(log n);对于插入操作,在尾部插入最快,中部次之,头部最慢;删除操作同理。由于二倍扩容机制可能会导致内存浪费,内存不足时扩容的拷贝也会造成性能开销。

(2)list: list底层是链表,不支持随机访问,只能通过扫描方式查找,时间复杂度为O(n);插入和删除速度较快,只需要调整指针指向,不会造成内存浪费。然而,频繁的内存分配和释放可能导致性能下降。

(3)deque: deque支持随机访问,但性能相对于vector较低;双端扩容机制使得头部和尾部的插入和删除元素速度很快,为O(1),但中间插入和删除较慢。

(4)set和map: 底层基于红黑树实现,增删查改的时间复杂度近似O(log n),红黑树是基于链表实现的,因此占用内存较小。

(5)unordered_set和unordered_map: 底层基于哈希表实现,是无序的。理论上增删查改的时间复杂度是O(1),但实际性能受数据分布影响较大。哈希函数的选择和解决哈希冲突的方法会影响容器的性能。

std::set:

  • 性能: std::set是基于红黑树实现的有序集合,插入、删除和查找操作的平均时间复杂度为O(log n)。
  • 底层实现: 使用红黑树实现,维护元素的有序性。
  • 适用场景: 适用于需要有序存储和快速查找元素的场景。对于数据量较小或需要保持有序性的情况,使用std::set是一个不错的选择。
#include <iostream>
#include <set>int main() {// 创建一个set容器std::set<int> mySet;// 插入元素mySet.insert(3);mySet.insert(1);mySet.insert(2);// 查找元素if (mySet.find(2) != mySet.end()) {std::cout << "Element 2 found in set" << std::endl;} else {std::cout << "Element 2 not found in set" << std::endl;}// 删除元素mySet.erase(1);// 遍历打印所有元素std::cout << "Set contents:" << std::endl;for (const auto& element : mySet) {std::cout << element << " ";}std::cout << std::endl;return 0;
}

std::unordered_set:

  • 性能: std::unordered_set是基于哈希表实现的无序集合,插入、删除和查找操作的平均时间复杂度为O(1)。
  • 底层实现: 使用哈希表实现,元素的存储位置由哈希函数决定。
  • 适用场景: 适用于对元素顺序没有要求,但需要高效地进行插入、删除和查找操作的情况。当数据量较大且没有顺序要求时,使用std::unordered_set可以获得更好的性能。
#include <iostream>
#include <unordered_set>
#include <unordered_map>
using namespace std;int main() {// unordered_set 常用操作示例std::unordered_set<int> intSet;// 插入元素intSet.insert(5);intSet.insert(10);intSet.insert(15);// 使用 emplace 插入元素intSet.emplace(20);// 查找元素if (intSet.find(10) != intSet.end()) {std::cout << "10 存在于集合中" << std::endl;}// 删除元素intSet.erase(5);// 遍历集合for (const int& num : intSet) {std::cout << num << " ";}std::cout << std::endl;// unordered_map 常用操作示例std::unordered_map<std::string, int> scoreMap;// 插入键值对scoreMap.emplace("Alice", 100);scoreMap.emplace("Bob", 85);scoreMap.emplace("Charlie", 92);// 查找键if (scoreMap.find("Alice") != scoreMap.end()) {std::cout << "Alice 的分数是 " << scoreMap["Alice"] << std::endl;}// 删除键值对scoreMap.erase("Bob");// 遍历键值对for (const auto& pair : scoreMap) {std::cout << pair.first << ": " << pair.second << std::endl;}unordered_map<string, vector<string>> mp;mp["Alice"].emplace_back("tall");mp["Alice"].emplace_back("thin");for (auto it = mp.begin(); it != mp.end(); ++it){cout << "key: " << it->first << ", Values: ";for (const string& value : it->second){cout << value << " ";}cout << endl;}return 0;
}

std::map:

  • 性能: std::map是基于红黑树实现的有序映射,插入、删除和查找操作的平均时间复杂度为O(log n)。
  • 底层实现: 使用红黑树实现,维护键的有序性。
  • 适用场景: 适用于需要根据键进行有序存储和查找操作的情况。当需要对键值对进行排序和查找时,使用std::map是一个合适的选择。
#include <iostream>
#include <map>int main() {// 创建一个map容器,键是整数,值是字符串std::map<int, std::string> myMap;// 插入元素myMap[1] = "One";myMap[2] = "Two";myMap[3] = "Three";// 查找元素std::map<int, std::string>::iterator it = myMap.find(2);if (it != myMap.end()) {std::cout << "Key 2 found, Value: " << it->second << std::endl;} else {std::cout << "Key 2 not found" << std::endl;}// 删除元素myMap.erase(1);// 遍历打印所有元素std::cout << "Map contents:" << std::endl;for (const auto& pair : myMap) {std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;}return 0;
}

std::unordered_map:

  • 性能: std::unordered_map是基于哈希表实现的无序映射,插入、删除和查找操作的平均时间复杂度为O(1)。
  • 底层实现: 使用哈希表实现,键的存储位置由哈希函数决定。
  • 适用场景: 适用于对键值对顺序没有要求,但需要高效地进行插入、删除和查找操作的情况。当需要在键值对间进行快速查找时,使用std::unordered_map可以获得更好的性能。
#include <iostream>
#include <unordered_map>
#include <string>int main() {// 创建一个unordered_map容器,键是整数,值是字符串std::unordered_map<int, std::string> myUnorderedMap;// 插入元素myUnorderedMap[1] = "One";myUnorderedMap[2] = "Two";myUnorderedMap[3] = "Three";// 查找元素auto it = myUnorderedMap.find(2);if (it != myUnorderedMap.end()) {std::cout << "Key 2 found, Value: " << it->second << std::endl;} else {std::cout << "Key 2 not found" << std::endl;}// 删除元素myUnorderedMap.erase(1);// 遍历打印所有元素std::cout << "Unordered Map contents:" << std::endl;for (const auto& pair : myUnorderedMap) {std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;}return 0;
}

哈希表

在 C++ 中,std::unordered_set 使用哈希表(Hash Table)作为底层数据结构来存储元素,处理冲突的主要原理是链地址法(Separate Chaining)。下面我会详细介绍冲突处理的原理以及元素删除的原理。

处理冲突的原理(链地址法):

  • 哈希表将元素通过哈希函数映射到特定的桶(bucket)。
  • 每个桶内部维护一个链表,如果多个元素映射到了同一个桶,它们将被添加到该桶对应的链表中。
  • 当插入元素时,哈希表会计算元素的哈希值,找到对应的桶,然后将元素添加到桶的链表中。
  • 当查找元素时,哈希表会根据元素的哈希值找到对应的桶,然后在桶的链表中查找。

删除元素的原理:

  • 当要删除元素时,哈希表会计算元素的哈希值,找到对应的桶。
  • 在桶的链表中查找要删除的元素,如果找到了,将该元素从链表中移除。

红黑树

树(Tree)

  • 树是一种层次结构,由节点和边组成,每个节点可以有零个或多个子节点,一个节点称为另一个节点的父节点,它们之间的连接称为边。
  • 树中的一个节点可以有多个子节点,但每个节点最多只有一个父节点,不存在环状结构。
  • 树用于表示分层结构,例如文件系统、组织架构等。

二叉树(Binary Tree)

  • 二叉树是一种特殊的树,每个节点最多有两个子节点,分别称为左子节点和右子节点。
  • 二叉树可以为空,或者由一个根节点和左右两个子树构成。

二叉查找树(Binary Search Tree,BST)

  • 二叉查找树是一种特殊的二叉树,对于每个节点,其左子树中的所有节点的值都小于它的值,右子树中的所有节点的值都大于它的值。
  • 这个性质使得在二叉查找树中进行搜索、插入和删除操作的平均时间复杂度为 O(log n),其中 n 是树中的节点数量。

红黑树(Red-Black Tree)

  • 红黑树是一种自平衡的二叉查找树,保持了在最坏情况下基本的平衡性,因此其查找、插入和删除操作的平均时间复杂度都是 O(log n)
  • 每个节点都有一个颜色属性,可以是红色或黑色。
  • 红黑树具有以下性质:
    • 顾名思义,红黑树中的节点,一类被标记为黑色,一类被标记为红色。
    • 根节点是黑色。
    • 每个**叶子节点都是黑色的空节点**(NIL),也就是说,叶子节点不存储数据;
    • 任何**相邻的节点都不能同时为红色**,也就是说,红色节点是被黑色节点隔开的;
    • 每个节点,**从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点;**
  • 这些性质保证了红黑树的平衡,避免了退化为链表的情况。

在关系上:

  • 二叉查找树是特殊的二叉树。
  • 红黑树是一种特殊的自平衡二叉查找树。

二叉查找树

二叉查找树(Binary Search Tree,BST)具有以下好的性质:

  1. 有序性: 在二叉查找树中,每个节点的值都大于其左子树中的所有节点的值,同时小于其右子树中的所有节点的值,这保证了树的有序性。

  2. 快速查找: 由于有序性的特点,通过比较节点值,可以快速定位目标节点,从而实现高效的查找操作。

  3. 高效的插入和删除: 二叉查找树的插入和删除操作平均情况下的时间复杂度为O(log n),其中n为树中节点的数量。这使得动态地维护有序数据集变得高效。

然而,二叉查找树并不是完美的数据结构,它在某些情况下可能会退化成不平衡的树,导致操作的时间复杂度恶化。为了解决这个问题,衍生出了自平衡二叉查找树,如红黑树和AVL树。

  • 增加(Insertion): 在平衡的二叉查找树中,插入操作的平均时间复杂度为O(log n)。但在不平衡的情况下,可能需要O(n)的时间,因为树可能变得过于倾斜。

  • 删除(Deletion): 与插入操作类似,在平衡的情况下,删除操作的平均时间复杂度为O(log n)。不过,删除操作可能会涉及到调整树的平衡,具体复杂度取决于树的结构。

#include <iostream>
#include <vector>
using namespace std;// 二叉树的节点结构体
struct node {int data;	  // 数据域 node* lchild; // 指针域:左孩子node* rchild; // 指针域:右孩子
};// 创建新节点
node* newNode(int v) {node* Node = new node;Node->data = v;Node->lchild = Node->rchild = nullptr;return Node;
}// 二叉查找树的查找操作
bool search(node* root, const int& val) {if (root == nullptr)   return false;if (root->data == val) return true;else if (root->data > val) {return search(root->lchild, val); // 修正:返回递归结果}else {return search(root->rchild, val); // 修正:返回递归结果}
}// 二叉查找树的插入操作
// 可以使插入操作中修改指针的值能够影响到原始指针,从而在调用者函数内正确更新树的结构。
void insert(node*& root, const int& val) { // 修正:传入引用if (root == nullptr) {root = newNode(val);return;}if (root->data == val) {return; // 已经有相同的值}else if (root->data > val) {insert(root->lchild, val);}else {insert(root->rchild, val);}
}// 二叉查找树的建立
node* create(vector<int>& data) {node* root = nullptr;for (auto& iter : data) {insert(root, iter);}return root;
}// 二叉查找树的删除
/*为保证删除某一个节点之后仍然为一个二叉查找树,一种方法是,找到删除节点的左子树中的最大值,替换掉删除的节点另一种方法是,找到删除节点的右子树中的最小值,替换掉删除的节点替换的方法是进行删除节点的递归操作
*/// 传入的是左孩子节点,找到左子树中的最大值,
node * GetLeftMax(node * root) {while (root != nullptr) {root = root->rchild;}return root;
}// 传入的是右孩子节点,找到右子树中的最小值,
node* GetRightMin(node* root) {while (root != nullptr) {root = root->lchild;}return root;
}// 二叉查找树的删除操作
void deleteNode(node*& root, int& val) {if (root == nullptr) return;if (root->data > val) {deleteNode(root->lchild, val);}else if (root->data < val) {deleteNode(root->rchild, val);}else {if (root->lchild == nullptr && root->rchild == nullptr) {delete root;	// 释放内存root = nullptr; // 指针置空}else if (root->lchild != nullptr) {node* pre = GetLeftMax(root->lchild);root->data = pre->data; // 使用前驱节点替换要删除的节点deleteNode(root->lchild, pre->data); // 递归删除掉替换的节点}else if (root->rchild != nullptr) {node* post = GetRightMin(root->rchild);root->data = post->data; // 使用后继节点替换要删除的节点deleteNode(root->rchild, post->data); // 递归删除掉替换的节点}}
}int main() {vector<int> data = { 5, 3, 8, 2, 4, 7, 9 };node* root = create(data);// 测试搜索操作cout << "Search 7: " << (search(root, 7) ? "Found" : "Not found") << endl;cout << "Search 6: " << (search(root, 6) ? "Found" : "Not found") << endl;// 释放树的内存//destroy(root);return 0;
}

工程上为什么偏好红黑树?


在红黑树中,红色节点不能相邻,也就是说,有一个红色节点就要至少有一个黑色节点,将它跟其他红色节点隔开。红黑树中包含最多黑色节点的路径不会超过 log2n,所以加入红色节点之后,最长路径不会超过 2log2n,也就是说,红黑树的高度近似 2log2n。

在工程中使用红黑树而不是其他平衡二叉树(如AVL树、Splay树等)的原因有多个,主要与红黑树在平衡性、性能和实现复杂度等方面的优势有关。

  1. 平衡性和性能: 红黑树是一种相对平衡的二叉查找树,它保持了在最坏情况下基本的平衡,这意味着它的查找、插入和删除操作的平均时间复杂度都是 O(log n)。虽然在某些特定情况下,其他平衡二叉树(如AVL树)可能会更加平衡,但红黑树在大多数情况下仍然提供了很好的性能,同时在插入和删除操作上更具灵活性。

  2. 实现复杂度: 红黑树相对于某些其他平衡树来说,其实现相对简单一些。红黑树的平衡性质相对较弱,允许一些不完全平衡的情况出现,因此在操作过程中的平衡调整相对较少,实现也相对较简单。

左旋与右旋

红黑树的插入

必须插入红色点

  1. 如果插入节点的父节点是黑色的,那我们什么都不用做,它仍然满足红黑树的定义。
  2. 如果插入的节点是根节点,那我们直接改变它的颜色,把它变成黑色就可以了。

如果插入的节点的父节点是红色

核心思路:

红 -- 红 变 红 -- 黑 or 黑 -- 黑

红色在左边

如果叔叔节点是红色,那我要想办法把它变成黑色。(插入,不要相邻,染成黑色,染成红色)

如果叔叔节点是黑色,那么条件一已经满足。

将插入的红色点放置到父节点的左子树。

如果不在左子树,则以b为原点左旋。

CASE 1:如果关注节点是 a,它的叔叔节点 d 是红色,我们就依次执行下面的操作:

将关注节点 a 的父节点 b、叔叔节点 d 的颜色都设置成黑色;

将关注节点 a 的祖父节点 c 的颜色设置成红色;

关注节点变成 a 的祖父节点 c

跳到 CASE 2 或者 CASE 3。

CASE 2:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的右子节点,我们就依次执行下面的操作:统一把红色节点调整到左侧

关注节点变成节点 a 的父节点 b

围绕新的关注节点 b 左旋;

跳到 CASE 3。 

 CASE 3:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的左子节点,我们就依次执行下面的操作:

围绕关注节点 a 的祖父节点 c 右旋;

将关注节点 a 的父节点 b、兄弟节点 c 的颜色互换。调整结束。

 红黑树的删除

CASE 1:如果要删除的节点是 a,它只有一个子节点 b,那我们就依次进行下面的操作:

删除节点 a,并且把节点 b 替换到节点 a 的位置,这一部分操作跟普通的二叉查找树的删除操作一样;

节点 a 只能是黑色,节点 b 也只能是红色,其他情况均不符合红黑树的定义。

这种情况下,我们把节点 b 改为黑色;

调整结束,不需要进行二次调整。

CASE 2:如果要删除的节点 a 有两个非空子节点,并且它的后继节点就是节点 a 的右子节点 c。我们就依次进行下面的操作:

如果节点 a 的后继节点就是右子节点 c,那右子节点 c 肯定没有左子树。我们把节点 a 删除,并且将节点 c 替换到节点 a 的位置。这一部分操作跟普通的二叉查找树的删除操作无异;

然后把节点 c 的颜色设置为跟节点 a 相同的颜色;

如果节点 c 是黑色,为了不违反红黑树的最后一条定义,我们给节点 c 的右子节点 d 多加一个黑色,这个时候节点 d 就成了“红 - 黑”或者“黑 - 黑”;

这个时候,关注节点变成了节点 d,第二步的调整操作就会针对关注节点来做。

 CASE 3:如果要删除的是节点 a,它有两个非空子节点,并且节点 a 的后继节点不是右子节点,我们就依次进行下面的操作:

  1. 找到后继节点 d,并将它删除,删除后继节点 d 的过程参照 CASE 1;
  2. 将节点 a 替换成后继节点 d;
  3. 把节点 d 的颜色设置为跟节点 a 相同的颜色;
  4. 如果节点 d 是黑色,为了不违反红黑树的最后一条定义,我们给节点 d 的右子节点 c 多加一个黑色,这个时候节点 c 就成了“红 - 黑”或者“黑 - 黑”;
  5. 这个时候,关注节点变成了节点 c,第二步的调整操作就会针对关注节点来做。

CASE 1:如果关注节点是 a,它的兄弟节点 c 是红色的,我们就依次进行下面的操作:

(红-黑-黑 左-换)

围绕关注节点 a 的父节点 b 左旋

关注节点 a 的父节点 b 和祖父节点 c 交换颜色

关注节点不变;

继续从四种情况中选择适合的规则来调整。

CASE 2:如果关注节点是 a,它的兄弟节点 c 是黑色的,并且节点 c 的左右子节点 d、e 都是黑色的,我们就依次进行下面的操作:

(黑-黑-黑 减一个加一个)

将关注节点 a 的兄弟节点 c 的颜色变成红色;

从关注节点 a 中去掉一个黑色,这个时候节点 a 就是单纯的红色或者黑色;

给关注节点 a 的父节点 b 添加一个黑色,这个时候节点 b 就变成了“红 - 黑”或者“黑 - 黑”;

关注节点从 a 变成其父节点 b;

继续从四种情况中选择符合的规则来调整。

 CASE 3:如果关注节点是 a,它的兄弟节点 c 是黑色,c 的左子节点 d 是红色,c 的右子节点 e 是黑色,我们就依次进行下面的操作:

(黑-红-黑 右-换)

围绕关注节点 a 的兄弟节点 c 右旋;

节点 c 和节点 d 交换颜色;

关注节点不变;

跳转到 CASE 4,继续调整。

CASE 4:如果关注节点 a 的兄弟节点 c 是黑色的,并且 c 的右子节点是红色的,我们就依次进行下面的操作:

(黑-x-红 父左-去-换-染)

围绕关注节点 a 的父节点 b 左旋;

将关注节点 a 的兄弟节点 c 的颜色,跟关注节点 a 的父节点 b 设置成相同的颜色

将关注节点 a 的父节点 b 的颜色设置为黑色;

从关注节点 a 中去掉一个黑色,节点 a 就变成了单纯的红色或者黑色;

关注节点 a 的叔叔节点 e 设置为黑色;调整结束 



1. 两数之和 - 力扣(LeetCode)

思想:空间换时间。N^-->N

49. 字母异位词分组 - 力扣(LeetCode)

思想:不同中的相同(排序后,字符串一样)。

128. 最长连续序列 - 力扣(LeetCode)

思想:暴力 -- 哈希(空间换时间)-- 裁剪

双指针

283. 移动零 - 力扣(LeetCode)

思路:拷贝非0,再补0

11. 盛最多水的容器 - 力扣(LeetCode)

思路:求容量、移动最差的那一边

15. 三数之和 - 力扣(LeetCode)

思路:当我们需要枚举两个元素,一个递增,一个递减 -- 双指针

27. 移除元素 - 力扣(LeetCode)

思路:一个指针操作要被赋值的数,一个指针遍历数组

滑动窗口

3. 无重复字符的最长子串 - 力扣(LeetCode)

数据结构:哈希集合
思路:移动右指针直到不能移动,再移动左指针

438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

数据结构:哈希map
思路:窗口 + 频率表

560. 和为 K 的子数组 - 力扣(LeetCode)

数据结构:哈希map

思路:以 i 结尾 和为 k 的子问题 是 以 j - 1 结尾 和为 pre[i] - k

链表

常见考法:

1.找中点:快慢指针

2.翻转:pre cur

3.合并:

4.找环

206. 反转链表 - 力扣(LeetCode)

思路:prev储存head前的节点,next 储存head后面的节点

141. 环形链表 - 力扣(LeetCode)

思路:快慢指针。链表长度、快指针何时移动?

142. 环形链表 II - 力扣(LeetCode)

思路:哈希表统计是否遍历过

23. 合并 K 个升序链表 - 力扣(LeetCode)

思路:归并

876. 链表的中间结点 - 力扣(LeetCode)

143. 重排链表 - 力扣(LeetCode)lls

146. LRU 缓存 - 力扣(LeetCode)

// 定义双向链表节点结构
struct DLinkedNode {int value;int key;DLinkedNode* prev;  // 指向前一个节点DLinkedNode* next;  // 指向后一个节点DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {};DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr)  {};
};// 实现LRU缓存类
class LRUCache {
private:unordered_map<int, DLinkedNode*> cache;  // 存储键值对和对应的双向链表节点int capacity;  // 缓存容量int size;  // 当前缓存大小DLinkedNode* head;  // 链表头节点,不存储实际数据DLinkedNode* tail;  // 链表尾节点,不存储实际数据public:// 构造函数LRUCache(int _capacity): capacity(_capacity), size(0) {head = new DLinkedNode();  // 初始化链表头tail = new DLinkedNode();  // 初始化链表尾head->next = tail;  // 头节点指向尾节点tail->prev = head;  // 尾节点指向头节点}// 获取缓存中键对应的值int get(int key) {if (!cache.count(key)) {  // 若缓存中没有该键,返回 -1return -1;}DLinkedNode* node = cache[key];  // 获取对应的链表节点moveToHead(node);  // 将该节点移动到链表头部,表示最近使用return node->value;  // 返回节点值}// 向缓存中插入键值对void put(int key, int value) {if (!cache.count(key)) {  // 如果缓存中没有该键DLinkedNode* node = new DLinkedNode(key, value);  // 创建新的链表节点cache[key] = node;  // 在缓存中存储该节点addToHead(node);  // 将节点插入到链表头部size++;  // 增加缓存大小if (size > capacity) {  // 若缓存大小超过容量DLinkedNode* removed = removeTail();  // 移除链表尾部的节点,即最久未使用的cache.erase(removed->key);  // 从缓存中移除对应的键delete removed;  // 释放被移除的节点内存size--;  // 减少缓存大小}} else {  // 如果缓存中已经有该键DLinkedNode* node = cache[key];  // 获取对应的链表节点node->value = value;  // 更新节点值moveToHead(node);  // 将节点移动到链表头部,表示最近使用}}// 将节点插入到链表头部void addToHead(DLinkedNode* node) {node->prev = head;  // 新节点的前一个节点是头节点node->next = head->next;  // 新节点的后一个节点是原头节点的后一个节点head->next->prev = node;  // 原头节点的前一个节点是新节点head->next = node;  // 头节点的后一个节点是新节点}// 移除链表中的某个节点void removeNode(DLinkedNode* node) {node->prev->next = node->next;  // 调整前一个节点的 next 指针node->next->prev = node->prev;  // 调整后一个节点的 prev 指针}// 将节点移动到链表头部void moveToHead(DLinkedNode* node) {removeNode(node);  // 先移除节点addToHead(node);  // 然后将节点插入到链表头部}// 移除链表尾部的节点,并返回该节点DLinkedNode* removeTail() {DLinkedNode* node = tail->prev;  // 获取尾节点的前一个节点removeNode(node);  // 移除该节点return node;  // 返回被移除的节点}};

动态规划

1049. 最后一块石头的重量 II - 力扣(LeetCode)lls

70. 爬楼梯 - 力扣(LeetCode)

二叉树

二叉搜索树

二叉查找树最大的特点就是,支持动态数据集合的快速插入、删除、查找操作。

散列表也是支持这些操作的,并且散列表的这些操作比二叉查找树更高效,时间复杂度是 O(1)。

既然有了这么高效的散列表,**使用二叉树的地方是不是都可以替换成散列表呢?有没有哪些地方是散列表做不了,必须要用二叉树来做的呢?**

Def:
二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。

如果需要支持重复数据,则统一存储在右节点。

平衡二叉查找树的高度接近 logn,所以插入、删除、查找操作的时间复杂度也比较稳定,是 O(logn)。

#include <iostream>
#include <vector>
using namespace std;// 二叉树的节点结构体
struct node {int data;	  // 数据域 node* lchild; // 指针域:左孩子node* rchild; // 指针域:右孩子
};// 创建新节点
node* newNode(int v) {node* Node = new node;Node->data = v;Node->lchild = Node->rchild = nullptr;return Node;
}// 二叉查找树的查找操作
bool search(node* root, const int& val) {if (root == nullptr)   return false;if (root->data == val) return true;else if (root->data > val) {return search(root->lchild, val); // 修正:返回递归结果}else {return search(root->rchild, val); // 修正:返回递归结果}
}// 二叉查找树的插入操作
void insert(node*& root, const int& val) { // 修正:传入引用if (root == nullptr) {root = newNode(val);return;}if (root->data == val) {return; // 已经有相同的值}else if (root->data > val) {insert(root->lchild, val);}else {insert(root->rchild, val);}
}// 二叉查找树的建立
node* create(vector<int>& data) {node* root = nullptr;for (auto& iter : data) {insert(root, iter);}return root;
}// 二叉查找树的删除
/*为保证删除某一个节点之后仍然为一个二叉查找树,一种方法是,找到删除节点的左子树中的最大值,替换掉删除的节点另一种方法是,找到删除节点的右子树中的最小值,替换掉删除的节点替换的方法是进行删除节点的递归操作
*/// 传入的是左孩子节点,找到左子树中的最大值,
node * GetLeftMax(node * root) {while (root != nullptr) {root = root->rchild;}return root;
}// 传入的是右孩子节点,找到右子树中的最小值,
node* GetRightMin(node* root) {while (root != nullptr) {root = root->lchild;}return root;
}// 二叉查找树的删除操作
void deleteNode(node*& root, int& val) {if (root == nullptr) return;if (root->data > val) {deleteNode(root->lchild, val);}else if (root->data < val) {deleteNode(root->rchild, val);}else {if (root->lchild == nullptr && root->rchild == nullptr) {delete root;	// 释放内存root = nullptr; // 指针置空}else if (root->lchild != nullptr) {node* pre = GetLeftMax(root->lchild);root->data = pre->data; // 使用前驱节点替换要删除的节点deleteNode(root->lchild, pre->data); // 递归删除掉替换的节点}else if (root->rchild != nullptr) {node* post = GetRightMin(root->rchild);root->data = post->data; // 使用后继节点替换要删除的节点deleteNode(root->rchild, post->data); // 递归删除掉替换的节点}}
}int main() {vector<int> data = { 5, 3, 8, 2, 4, 7, 9 };node* root = create(data);// 测试搜索操作cout << "Search 7: " << (search(root, 7) ? "Found" : "Not found") << endl;cout << "Search 6: " << (search(root, 6) ? "Found" : "Not found") << endl;// 释放树的内存//destroy(root);return 0;
}

手写最大堆,说明插入、弹出操作 

#include <iostream>
#include <vector>
#include <algorithm> // 包含算法头文件
using namespace std;class MaxHeap {
private:vector<int> heapArr;
public:// 构造函数,用传入的数组创建最大堆MaxHeap(int elements[], int number) {heapArr.push_back(0);  // 在堆数组开头添加一个哑节点for (int i = 1; i < number + 1; i++) {heapArr.push_back(elements[i - 1]);  // 将传入的数据存入堆数组中}// 从最后一个非叶节点开始,逐步将数组调整为最大堆for (int i = heapArr.size() / 2; i >= 1; i--) {down(i);}}// 向最大堆中添加元素void push(int p) {heapArr.push_back(p);up(heapArr.size() - 1);}// 弹出最大元素并重新调整堆int pop() {int res = heapArr[1];  // 最大元素在堆顶swap(heapArr[1], heapArr[heapArr.size() - 1]);  // 将堆顶与最后一个元素交换heapArr.pop_back();  // 弹出最后一个元素down(1);  // 调整堆return res;}// 向下调整,维持最大堆的性质void down(int i) {while (true) {int son1 = 2 * i, son2 = 2 * i + 1;int maxPos = i;if (son1 <= heapArr.size() - 1 && heapArr[son1] > heapArr[i]) {maxPos = son1;}if (son2 <= heapArr.size() - 1 && heapArr[son2] > heapArr[maxPos]) {maxPos = son2;  // 将 heapArr[son1] 修改为 heapArr[son2]}if (maxPos == i) break;  // 如果当前位置是最大位置,停止调整swap(heapArr[i], heapArr[maxPos]);  // 交换当前位置与最大位置的元素i = maxPos;  // 继续向下调整}}// 向上调整,维持最大堆的性质void up(int i) {int fa = i / 2;if (i > 1 && heapArr[i] > heapArr[fa]) {swap(heapArr[i], heapArr[fa]);  // 如果当前位置的元素大于父节点,交换它们up(fa);  // 继续向上调整}}// 显示最大堆中的元素void show() {for (int i = 1; i < heapArr.size(); i++) {cout << " " << heapArr[i];}cout << endl;}
};int main() {int a[] = { 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 };MaxHeap* m = new MaxHeap(a, int(sizeof(a) / sizeof(int)));  // 创建最大堆对象并初始化cout << "初始最大堆:" << endl;m->show();cout << "弹出最大元素:" << m->pop() << endl;cout << "调整后的最大堆:" << endl;m->show();m->push(90);  // 向最大堆中插入元素cout << "插入元素后的最大堆:" << endl;m->show();return 0;
}

https://blog.csdn.net/weixin_42653023/article/details/123502436
 

相关文章:

算法 for GAMES

栈 #include <iostream> #include <stack>int main() {std::stack<int> intStack;// 压入元素到堆栈intStack.push(5);intStack.push(10);intStack.push(15);// 查看堆栈顶部元素std::cout << "Top element: " << intStack.top() <…...

自研分布式IM-HubuIM RFC草案

HubuIM RFC草案 消息协议设计 基本协议 评估标准 【性能】协议传输效率&#xff0c;尽可能降低端到端的延迟&#xff0c;延迟高于200ms用户侧就会有所感知 【兼容】既要向前兼容也要向后兼容 【存储】减少消息包的大小&#xff0c;降低空间占用率&#xff0c;一个字节在亿…...

tableau基础学习1:数据源与绘图

文章目录 读取数据常用绘图方法1. 柱状图2. 饼图3. 散点图4. 热力图 第一部分是一些较容易上手的内容&#xff0c;以及比较常见的可视化内容&#xff0c;包括&#xff1a;柱状图、饼图、散点图与热力图 读取数据 打开界面后&#xff0c;选择数据源之后就可以导入数据&#xf…...

探索经典算法问题与解决方案

探索经典算法问题与解决方案 在计算机科学领域&#xff0c;有许多经典算法问题需要我们思考和解决。本文将深入介绍一些著名的经典算法问题&#xff0c;包括旅行商问题、背包问题的变种、N皇后问题、钢条切割问题、最大子数组和问题、最长公共子串问题以及矩阵连乘问题&#x…...

【Linux】DNS系统,ICMP协议,NAPT技术

遏制自己内心的知识优越感&#xff0c;才能让你发自内心的去尊重他人&#xff0c;避免狂妄自大&#xff0c;才能让你不断的丰富自己的内心。 文章目录 一、DNS系统1.DNS服务器返回域名对应的ip2.使用dig工具分析DNS过程3.浏览器中输入url后发生的事情&#xff1f; 二、ICMP协议…...

BI技巧丨Window应用之同环比

白茶曾介绍过OFFSET可以用来解决同环比的问题&#xff0c;其实微软最近推出的开窗函数WINDOW也可以用来解决同环比。 WINDOW函数基础语法 WINDOW ( from[, from_type], to[, to_type][, <relation>][, <orderBy>][, <blanks>][, <partitionBy>][, &l…...

【Mac】编译Spring 源码和Idea导入

今天我们开始Spring源码的阅读之旅。阅读Spring的源码的第一步当然是编译Spring源码。首先我们要去GitHub上将spring源码给clone下来。 笔者编译环境如下&#xff1a; Spring版本&#xff1a;5.28 https://github.com/spring-projects/spring-framework/tree/v5.2.8.RELEASE …...

手把手教你用 ANSYS workbench

ANSYS Workbench ANSYS Workbench是一款基于有限元分析&#xff08;FEA&#xff09;的工程仿真软件。其基本概念包括&#xff1a; 工作区&#xff08;Workspace&#xff09;&#xff1a;工程仿真模块都在此区域内&#xff0c;包括几何建模、网格划分、边界条件设置、分析求解等…...

Kotlin开发笔记:协程基础

Kotlin开发笔记&#xff1a;协程基础 导语 本章内容与书的第十五章相关&#xff0c;主要介绍与协程相关的知识。总的来说&#xff0c;本文将会介绍Kotlin中关于异步编程的内容&#xff0c;主要就是与协程有关。在Kotlin中协程是利用continuations数据结构构建的&#xff0c;用…...

自学设计模式(简单工厂模式、工厂模式、抽象工厂模式)

使用工厂模式来生产某类对象&#xff08;代码简化且容易维护&#xff0c;类之间有血缘关系&#xff0c;可以通过工厂类进行生产&#xff09;&#xff1b; 简单工厂模式&#xff08;用于创建简单对象&#xff09; 对于简单工厂模式&#xff0c;需要的工厂类只有一个&#xff1…...

NFS:使⽤ NFS 为远程客户端提供共享文件系统

写在前面 分享一些 nfs 搭建的笔记考试顺便整理内容涉及 nfs 服务端客户端的搭建配置理解不足小伙伴帮忙指正 对每个人而言&#xff0c;真正的职责只有一个&#xff1a;找到自我。然后在心中坚守其一生&#xff0c;全心全意&#xff0c;永不停息。所有其它的路都是不完整的&…...

2022-kaggle-nlp赛事:Feedback Prize - English Language Learning(超多注释讲解)

2022-kaggle-nlp赛事&#xff1a;Feedback Prize - English Language Learning 零、比赛介绍 比赛地址Feedback Prize - English Language Learning | Kaggle 0.1 比赛目标 写作是一项基本技能。可惜很少学生能够磨练&#xff0c;因为学校很少布置写作任务。学习英语作为第…...

第十三课 宾语从句

文章目录 前言一、宾语从句1、主语及物动词宾语从句2、主语双宾动词间接宾语直接宾语3、主语特定及物动词宾语从句&#xff08;作宾语&#xff09;宾补4、主语be某些形容词宾语从句5、动词不定式后面的宾语从句6、动名词后面的宾语从句7、介词后面的宾语从句9、间接引语 前言 一…...

Docker容器与虚拟化技术:GitHub账户注册

目录 一、实验 1.GitHub 一、实验 1.GitHub &#xff08;1&#xff09;GitHub是一个面向开源及私有软件项目的托管平台&#xff0c;因为只支持Git作为唯一的版本库格式进行托管&#xff0c;故名GitHub。 &#xff08;2&#xff09;官网 GitHub: Let’s build from here …...

thinkphp安装workman

需要加版本&#xff0c;版本太高了不行 composer require topthink/think-worker1.0.*...

L1-036 A乘以B(Python实现) 测试点全过

题目 看我没骗你吧 —— 这是一道你可以在 10 秒内完成的题&#xff1a;给定两个绝对值不超过 100 的整数 A 和 B&#xff0c;输出 A 乘以 B 的值。 输入格式 输入在第一行给出两个整数 A 和 B &#xff08; − 100 ≤ A , B ≤ 100 &#xff09; A 和 B&#xff08;−100≤…...

代码随想录第五十三天

代码随想录第五十三天 Leetcode 1143. 最长公共子序列Leetcode 1035. 不相交的线Leetcode 53. 最大子数组和 Leetcode 1143. 最长公共子序列 题目链接: 最长公共子序列 自己的思路:没想出来&#xff01;&#xff01;&#xff01; 正确思路:首先这道题由于是涉及到了两个数组&…...

cmd - 如何在不重启的情况下让修改后的hosts生效

cmd - 如何在不重启的情况下让修改后的hosts生效 亲测有效 一般在修改了hosts文件后&#xff0c;需要重启电脑才能生效&#xff1b;其实可以不通过重启电脑也可以令其生效&#xff0c;方法如下&#xff1a; 打开cmd窗口输入​​ipconfig /flushdns​​&#xff0c;然后回车。…...

echarts实现双x轴并且分组滚动效果

var myChart echarts.init(document.getElementById(allOutPut1));var option {legend: {itemHeight: 10, // 图例icon高度itemWidth: 16, // 图例icon宽度icon:rect,//设置为矩形top:2%,right:10%,},tooltip: {trigger: axis,axisPointer: {type: shadow},textStyle: {fontS…...

UE4 地形编辑基础知识 学习笔记

之前自己写过这样的功能&#xff0c;今天看到一个UE现成的 点击地形&#xff0c;选择样条 按住CTRL键点击屏幕中某一个点会在场景内生成一个这样的图标 再点两次&#xff0c;会生成B样条的绿线条 点击号再选择一个模型&#xff0c;会生成对应的链条状的mesh 拉高最远处的一个图…...

AcWing算法提高课-5.5.2最大公约数

宣传一下 算法提高课整理 CSDN个人主页&#xff1a;更好的阅读体验 原题链接 题目描述 给定整数 N N N&#xff0c;求 1 ≤ x , y ≤ N 1 \le x,y \le N 1≤x,y≤N 且 gcd ⁡ ( x , y ) \gcd(x,y) gcd(x,y) 为素数的数对 ( x , y ) (x,y) (x,y) 有多少对。 输入格式 输…...

Kubernetes-CKA考题详解

Kubernetes-CKA考题详解 考前须知:考试环境说明第一题:RBAC(4%)第二题:指定node设置为不可用(4%)第三题:升级kubernetes节点(7%)第四题:etcd备份还原(7%)第五题:创建NetworkPolicy(7%)第六题:创建svc(7%)第七题:创建ingress资源(7%)第八题:扩展deployme…...

不同版本.net引用同一个项目

项目文件.csproj文件内容如下&#xff1a; 重点是&#xff1a;不能有其他的 netstandard2;net40;net45;net46;net6 <Project Sdk"Microsoft.NET.Sdk"><PropertyGroup><TargetFrameworks>netstandard2;net40;net45;net46;net6</TargetFrame…...

软件开发企业SDL安全培训案例

1.背景 随着计算机技术的发展、internet及mobile应用的普遍使用,软件安全像功能、性能、稳定性一样是计算机系统的一个非常重要部分。没有安全的软件,任何美好的功能都是徒劳的,没有安全的软件,公司的机密数据、客户隐私、系统的可靠性都得不到保障.如何有效评估、开发安全、可…...

ide-eval-resetter jar包下载、源码、使用介绍

如果你在找ide-eval-resetter插件&#xff0c;这里告诉你&#xff0c;2021.3版本开始该插件正式失效。 如果你安装的JB产品版本低于2021.3版本&#xff0c;你确定要找ide-eval-resetter&#xff0c;下面提供相关链接希望对你有帮助。 ide-eval-resetter源码&#xff1a; Githu…...

数据压缩算法一览

文章首发地址 Huffman编码&#xff1a; Huffman编码是一种基于字符频率的无损压缩算法。它将出现频率较高的字符用较短的编码表示&#xff0c;出现频率较低的字符用较长的编码表示&#xff0c;从而实现压缩。Lempel-Ziv-Welch (LZW)&#xff1a; LZW是一种基于字典的无损压缩算…...

使用Rust开发命令行工具

生成二进制文件&#xff0c;将其扔到环境变量的path下即可~ 用rust打造实时天气命令行工具[1] 找到合适的API 使用该api[2] 如请求 api.openweathermap.org/data/2.5/weather?qBeijing&appidyour_key: { "coord": { "lon": 116.3972, "lat&quo…...

CentOS中Oracle11g进程有哪些

最近遇到Oracle数据库运行过程实例进程由于某种原因导致中止的问题&#xff0c;专门看了下正常Oracle数据库启动后的进程有哪些&#xff0c;查阅资料了解了下各进程的作用&#xff0c;记录如下。 oracle 3032 1 0 07:36 ? 00:00:00 ora_pmon_orcl oracle …...

WebRTC之FEC前向纠错协议

FEC前向纠错用于丢包恢复&#xff0c;对媒体包进行异或或其他算法生成冗余包进行发送。如果接收端出现丢包&#xff0c;可以通过冗余包恢复出原始的媒体包。FEC的代价是增加码率带宽&#xff0c;所以一般会根据网络状况、丢包率来动态调整FEC冗余系数&#xff0c;也会结合NACK/…...

软件测试技术分享丨使用Postman搞定各种接口token实战

现在许多项目都使用jwt来实现用户登录和数据权限&#xff0c;校验过用户的用户名和密码后&#xff0c;会向用户响应一段经过加密的token&#xff0c;在这段token中可能储存了数据权限等&#xff0c;在后期的访问中&#xff0c;需要携带这段token&#xff0c;后台解析这段token才…...

网站流量宝/stp营销战略

Sutdent表的定义字段名字段描述数据类型主键外键非空唯一自增Id学号INT(10)是否是是是sName姓名VARCHAR(20)否否是否否Sex性别VARCHAR(4)否否否否否Birth出生年份YEAR否否否否否Department院系VARCHAR(20)否否是否否Address家庭住址VARCHAR(50)否否否否否Score表的定义字段名字…...

织梦做的网站后台怎么进/实体店怎么引流推广

最近做城市天气相关的项目&#xff0c;找到一些资源给大家分享一下。 <?xml version"1.0" encoding"UTF-8"?><China><province id"01" name"北京"><city id"0101" name"北京"><cou…...

浦东建设交通委网站/企业营销型网站建设

为什么80%的码农都做不了架构师&#xff1f;>>> Oracle数据库查询高效分页 由于网页渲染速度的影响&#xff0c;在C/S程序中那种一个Grid包含几千、上万行的数据基本上在网页是无法展现的&#xff0c;因此一般采用分页的形式显示&#xff08;也可能采用Visual Srol…...

别人做的网站怎么打开吗/西安seo网络优化公司

一.描述 1.QDateTimeEdit是一个用来编辑日期和时间的单行文本框&#xff0c;由于大继承自QAbstractSpinBox类&#xff0c;可以用箭头按钮来调节文本内容&#xff0c;也可以用键盘输入。在用按钮修改内容时可以单独修改某个部分&#xff08;年、月、日、小时、分&#xff09; …...

公司网站友情链接怎么做副链/谷歌浏览器下载安装2022最新版

文章目录1. 简介2. 示例2.1 文件内容修改2.2 在某一行前面插入一行2.3 在某一行后面插入一行2.4 删除某一行2.5 末尾加入一行2.6 替换或添加某一行– ansible 快速学习手册 1. 简介 lineinfile:文件内容修改、在某行前面添加一行、在某行后面添加一行、删除某一行、末尾加入一…...

网站开发备案/长沙好的seo外包公司

1、反射 为何要关注元数据&#xff1f;------反射服务 在.net中&#xff0c;通过反射可以得到一个给定的.dll或.exe程序集所包含的所有类型的列表&#xff0c;列表包括给定类型定义的方法、字段、属性和事件&#xff0c;反射即反向获得。 如何使用System.Reflection命名空间编程…...