下面是我学习的一些知识点和做的题、每个题都要分析和解释
知识点:
C++的类:(public、protected、private)
1.封装(public、private): 用户代码(类外)可以访问public成员而不能访问private成员;private成员只能由类成员(类内)和友元访问,不能被派生类访问;protected成员可以被派生类对象访问,不能被用户代码(类外)访问。
2.类的另一个特征就是继承,例如:class B : protected A、class B : private A和class B : public A public继承,子类继承原来的父类的成员等级不变。 protected继承,除了public降为protected之外、其余的成员等级不变。 private继承,所有成员的等级都为private。
这里只是简单了解一下;
unordered_map函数、map函数
头文件#include < unordered_map >、#include < map >
特点:
map: map内部实现了一个红黑树,该结构具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素,因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行这样的操作,故红黑树的效率决定了map的效率,效率高、但是占用内存大。
unordered_map: unordered_map内部实现了一个哈希表,因此其元素的排列顺序是杂乱的,无序的。耗时间 unordered_map: 首先unordered_map是一个将key和value关联起来的容器,它可以高效的根据单个key值查找对应的value. key值应该是唯一的,key和value的数据类型可以不相同。
一、map函数会通过红黑树实现键的从小到大排列
1.定义:
map
mp; 2.访问:map
mp; mp['c'] = 20;
例子:map可以通过定义map
::iterator it来查看key和value; int main()
{
map
mp;
mp['m'] = 20;
mp['r'] = 30;
mp['a'] = 40;
//it -> first 是当前映射的键
//it -> second是当前映射的值
for(map
::iterator it = mp.begin(); it != mp.end(); it++) {
cout << it -> first << " " << it -> second << endl;
}
return 0;
}
map除了begin()、end()函数,还有:
1.find(key)返回键为key的映射的迭代器,时间复杂度为O(logN),N为map中映射的个数。
例子:map
::iterator it = mp.find('b'); 2.erase()有两种用法:删除单个元素,删除一个区间内的所有元素
2.1例如:删除key为‘b’的:
map
::iterator it = mp.find('b'); mp.erase(it);
2.2例如:mp.erase(first,last),其中first为需要删除的区间的起始迭代器,而last则为需要删除的区间的末尾迭代器的下一个地址,也即为删除左闭右开的区间[first,last)
map
::iterator it = mp.find('b'); //令it指向键为b的映射 mp.erase(it,mp.end());//删除从key为b的到最后的所有map映射
3.size()用来获取map中映射的对数,时间复杂度为O(1)
4.clear()用来清空map中的所有元素,复杂度为O(N),其中N为map中元素的个数
二、unordered_map存储元素时没有进行排序,只是根据key的哈希值,将元素存在指定位置,所以根据key查找单个value时非常高效,平均可以在常数时间内完成。
unordered_map查询单个key的时候效率比map高,但是要查询某一范围内的key值时比map效率低。
可以使用[]操作符来访问key值对应的value值。
例如:
1.定义
string key="123";
int value=4;
unordered_map
unomap;//创建一个key为string类型,value为int类型的unordered_map unomap.emplace(key, value);//使用变量方式,插入一个元素
unomap.emplace("456", 7);//也可以直接写上key和value的值
cout<
2.遍历
for(auto x:unomap)//遍历整个map,输出key及其对应的value值
cout<
for(auto x:unomap)//遍历整个map,并根据其key值,查看对应的value值
cout<
函数:
除了begin()、end()、empty()、size()、find(key)、erase()、clear()。
还有:
1.count(key),在容器中查找以key为键的键值对的个数
2.insert()容器中添加新键值对。
3.swap(),交换2个容器存储的键值对,前提是必须保证这2个容器的类型完全向相等,例如: m1.swap(m2);交换m1和m2
下面是我做的一些题:
原题:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
一、暴力解法
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);//建立一个新的节点
ListNode prev = prehead;//定义一个prev指向该节点
while (l1 != null && l2 != null) {//只要l1和l2链表的值存在就继续循环、最后一个
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;//上面给prev指向了下一位后、pre移动到下一位
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev.next = l1 == null ? l2 : l1;//l1 == null ? l2 : l1;为判断语句,返回l1或者l2;
return prehead.next;//返回prehead节点的下一位,也就是我们合并后的链表
}
}
//思路:新建立一个prehead节点、一个prev指针。指针指向节点,在用while循环用l1和l2遍历两个链表、直到一个链表为空、同时在这个过程中把两个链表的最小的元素当做prev指针的下一位(哪个链表被遍历了,它对应的指针就前进一位),然后prev在前面得到了值,移动到下一位不断前进。最后我们一定会遍历完一个链表、然后此时prev的下一位指向没有遍历完的链表,这样两个链表就合并了;
二、递归
class Solution {
public:
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;
}
}
};
//特点:没有移动指针,只是给出当前链表的值与另外一个链表的值,就可以合并链表,最后通过递归得到整个合并的链表
//思路:把小的链表的下一位地址作为与另外一个链表的值进行下一次比较、通过依次的返回最小的链表的值的地址,我们用返回的链表的地址作为当前的下一位;最后递归回来,我们就自然合并了链表。
原题: 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
1.数组
class Solution {
public:
ListNode* middleNode(ListNode* head) {
vector
A = {head};//定义一个数组用来存放结构体指针 while (A.back()->next != NULL)//A.back()表示当前数组的最后一位,由于现在A的只有一个元素,while循环的条件是最后一位也就是现在的第一位的下一位地址不为空,则在while循环里:我们不断向while循环放入元素,while的条件语句也会改变,最后整个链表的地址都被放入了数组A。
A.push_back(A.back()->next);
return A[A.size() / 2];//返回数组的中间元素,也就是链表的中间元素
}
};
//思路:定义一个数组A,用于存放链表的地址信息,最后取数组的中间元素作为返回值,就返回了链表的中间元素。
//核心代码(把链表放入数组):
while (A.back()->next != NULL)//A.back()表示当前数组的最后一位,由于现在A的只有一个元素,while循环的条件是最后一位也就是现在的第一位的下一位地址不为空,则在while循环里:我们不断向while循环放入元素,while的条件语句也会改变,最后整个链表的地址都被放入了数组A。
A.push_back(A.back()->next);
二、迭代
class Solution {
public:
ListNode* middleNode(ListNode* head) {
int n = 0;//定义一个n用来记录链表的大小
ListNode* cur = head;//定义一个指针,用于遍历数组
while (cur != nullptr) {//遍历数组
++n;
cur = cur->next;
}
int k = 0;//定义一个k用于记录我们遍历到链表中间时,返回链表的元素
cur = head;
while (k < n / 2) {
++k;
cur = cur->next;
}
return cur;//返回cur指针指向的中间元素
}
};
//思路:通过一个指针,遍历同一个链表,得到该链表的大小后,在遍历到链表的中间值返回就可以了
//核心代码:
while (cur != nullptr) {//遍历数组
++n;
cur = cur->next;
}
int k = 0;//定义一个k用于记录我们遍历到链表中间时,返回链表的元素
cur = head;
while (k < n / 2) {
++k;
cur = cur->next;
}
三、快慢指针
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;//定义快慢指针:fast、slow
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
//定义快慢指针,由于快的速度是慢的两倍,因此慢指针的路程是快指针的一半,当快指针指向末尾时,慢指针指向链表的中间元素
//核心代码:
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
原题:
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
一、栈
class Solution {
public:
vector
reverseBookList(ListNode* head) { stack
stk;//定义一个栈stk,用于存放链表的值 while(head != nullptr) {
stk.push(head->val);
head = head->next;
}//向栈中放入链表的元素
vector
res;//定义一个数组作为返回值 while(!stk.empty()) {//把栈里面的元素全部放入数组
res.push_back(stk.top());//依次取出栈的值放入数组中
stk.pop();
}
return res;
}
};
//利用栈的性质,做到反转链表的值,并用数组返回。(还可以反转链表的、反转数组、字符串等等)
二、递归
class Solution {
public:
vector
reverseBookList(ListNode* head) { recur(head);//取head作为链表的头结点,把链表的值反转放入数组中
return res;
}
vector
res;//定义一个数组、作为返回值 void recur(ListNode* head) {//定义一个修改res数组的值的没有返回值的数组,作用是通过递归放入链表的值到数组。
if(head == nullptr) return ;
recur(head->next);//取链表的下一个地址开始作为头结点
res.push_back(head->val);//取链表的值放入res数组中
}
};
//定义一个全局的数组、一个函数、最后主程序调用函数解决问题。
//函数recur:通过递归,从最后面把链表的值放入数组中。
//上面很好的解释了递归的本质是栈
原题:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
class Solution {
public:
bool isValid(string s) {
int n = s.size();
if (n % 2 == 1) {
return false;
}//如果只有一个字符,那么肯定是错误的
unordered_map
pairs = {//定义key和value都为char类型,方便把它们放入栈时进行比较判断 {')', '('},
{']', '['},
{'}', '{'}//根据栈的性质,我们把后括号作为key、前括号作为value
};
stack
stk; for (char ch: s) {//for的增强循环、作用是把字符串前括号放入栈中,如果检测到unordered_map有对应的后括号,就判断现在栈的top的值是否是对应的前括号
if (pairs.count(ch)) {//判断当前字符是否存在于栈中
if (stk.empty() || stk.top() != pairs[ch]) {//判断栈为空、或者栈顶端的value与当前后括号是否相等、相等就对应,则说明则对括号是正确的
return false;
}
stk.pop();
}
else {
stk.push(ch);//把字符串的值依次放入栈,但是只放入了前括号
}
}
return stk.empty();//如果给的是空就返回True、括号全部对应,则栈为空
}
};
//思路:建立一个映射(unordered_map)pairs,来当做我们对括号是否对应的标准,根据栈的性质、key为后括号、value为前括号,我们通过循环把字符串放入栈中、判断是否是正确括号:在这个过程中、我们只放入前括号、一旦检测到后括号、我们就拿映射去做对比,因为根据括号的正确顺序和栈的性质、如果检测到后括号,那么此时栈顶的字符一定是对应的前括号。最后在栈中,不断放入、删除字符。最后栈为空,就说明括号的顺序正确。
//核心代码:
for (char ch: s) {//for的增强循环、作用是把字符串前括号放入栈中,如果检测到unordered_map有对应的后括号,就判断现在栈的top的值是否是对应的前括号
if (pairs.count(ch)) {//判断当前字符是否存在于栈中
if (stk.empty() || stk.top() != pairs[ch]) {//判断栈为空、或者栈顶端的value与当前后括号是否相等、相等就对应,则说明则对括号是正确的
return false;
}
stk.pop();
}
else {
stk.push(ch);//把字符串的值依次放入栈,但是只放入了前括号
}
路很漫长、愿我坚持不懈。