KMP
KMP算法讲解
unordered_map和map的区别
unordered_map也就是不排序的map的意思
使用的头文件不一样:
#include<unordered_map>
#include<map>
class Solution {
private:
unordered_map<char, int> symbolValues = {
{'I', 1},
{'V', 5},
{'X', 10},
{'L', 50},
{'C', 100},
{'D', 500},
{'M', 1000},
};
public:
int romanToInt(string s) {
int ans = 0; // 存储结果
int n = s.length(); // 字符串长度
for (int i = 0; i < n; ++i) {
int value = symbolValues[s[i]]; // 当前字符对应的值
// 如果当前字符表示的值小于下一个字符表示的值,则减去当前值
if (i < n - 1 && value < symbolValues[s[i + 1]]) {
ans -= value;
} else { // 否则加上当前值
ans += value;
}
}
return ans; // 返回结果
}
};
vector
进行vector
操作前应添加头文件#include <vector>
有序容器
在 C++ 的 set
容器中,insert
函数会将元素插入容器,并且会自动对容器进行排序。set
是一个有序的容器,它会根据元素的值进行排序,因此插入元素后,set
会自动按照元素的大小进行排序。
set<int> s;
返回最后一个元素
s.rbegin();
vector常用定义
//定义具有10个整型元素的向量(尖括号为元素类型名,它可以是任何合法的数据类型),不具有初值,其值不确定
vector<int>a(10);
//定义具有10个整型元素的向量,且给出的每个元素初值为1
vector<int>a(10,1);
//用向量b给向量a赋值,a的值完全等价于b的值
vector<int>a(b);
//将向量b中从0-2(共三个)的元素赋值给a,a的类型为int型
vector<int>a(b.begin(),b.begin+3);
//将向量b中从0-2(共三个)的元素赋值给a,a的类型为int型
vector<int>a(b.begin(),b.begin+3);
//从数组中获得初值
int b[7]={1,2,3,4,5,6,7};
vector<int> a(b,b+7);
vector用法
#include<vector>
vector<int> a,b;
//b为向量,将b的0-2个元素赋值给向量a
a.assign(b.begin(),b.begin()+3);
//a含有4个值为2的元素
a.assign(4,2);
//返回a的最后一个元素
a.back();
//返回a的第一个元素
a.front();
//返回a的第i元素,当且仅当a存在
a[i];
//清空a中的元素
a.clear();
//判断a是否为空,空则返回true,非空则返回false
a.empty();
//删除a向量的最后一个元素
a.pop_back();
//删除a中第一个(从第0个算起)到第二个元素,也就是说删除的元素从a.begin()+1算起(包括它)一直到a.begin()+3(不包括它)结束
a.erase(a.begin()+1,a.begin()+3);
//在a的最后一个向量后插入一个元素,其值为5
a.push_back(5);
//在a的第一个元素(从第0个算起)位置插入数值5,
a.insert(a.begin()+1,5);
//在a的第一个元素(从第0个算起)位置插入3个数,其值都为5
a.insert(a.begin()+1,3,5);
//b为数组,在a的第一个元素(从第0个元素算起)的位置插入b的第三个元素到第5个元素(不包括b+6)
a.insert(a.begin()+1,b+3,b+6);
//返回a中元素的个数
a.size();
//返回a在内存中总共可以容纳的元素个数
a.capacity();
//将a的现有元素个数调整至10个,多则删,少则补,其值随机
a.resize(10);
//将a的现有元素个数调整至10个,多则删,少则补,其值为2
a.resize(10,2);
//将a的容量扩充至100,
a.reserve(100);
//b为向量,将a中的元素和b中的元素整体交换
a.swap(b);
//b为向量,向量的比较操作还有 != >= > <= <
a==b;
queue<pair<TreeNode *, int> > que;
que.emplace(root, 1);
push_back与emplace_back区别
-
参数传递方式:
push_back()
接受一个参数,该参数是要添加到容器中的元素的副本或可移动对象。emplace_back()
接受一个参数包或者一系列参数,用来构造新元素。
-
效率:
push_back()
添加元素时,会将参数拷贝或者移动构造到容器中,这可能涉及到额外的拷贝或移动操作,尤其是如果元素是一个对象,而不是简单的内置类型。emplace_back()
直接在容器内部构造新元素,省去了额外的拷贝或移动操作,因此在某些情况下更高效。
Queue用法
queue初始化
queue<Type, Container> (<数据类型,容器类型>)
初始化时必须要有数据类型,容器可省略,省略时则默认为deque 类型
初始化示例
queue<int>q1;
queue<double>q2;
queue<char>q3;
//默认为用deque容器实现的queue;
queue<char, list<char>>q1;
//用list容器实现的queue
queue<int, deque<int>>q2;
//用deque容器实现的queue
注意:不能用vector容器初始化queue
因为queue转换器要求容器支持front()、back()、push_back()及 pop_front(),说明queue的数据从容器后端入栈而从前端出栈。所以可以使用deque(double-ended queue,双端队列)和list对queue初始化,而vector因其缺少pop_front(),不能用于queue。
queue常用函数
- push() 在队尾插入一个元素
- pop() 删除队列第一个元素
- size() 返回队列中元素个数
- empty() 如果队列空则返回true
- front() 返回队列中的第一个元素
- back() 返回队列中最后一个元素
字符串
截取字符串0-5的数据
str1.substr(0, 5);
遍历字符串
bool valid(const string& str) {
int balance = 0;
for (char c : str) {
if (c == '(') {
++balance;
} else {
--balance;
}
if (balance < 0) {
return false;
}
}
return balance == 0;
}
字符串反转
第一种方法是构造了一个新的字符串,该字符串是原字符串的逆序;而第二种方法是直接修改了原字符串,将其原地反转。
reverse(a.begin(), a.end());
string sgood_rev(sgood.rbegin(), sgood.rend());
carry += i < a.size() ? (a.at(i) == '1') : 0;
carry += i < b.size() ? (b.at(i) == '1') : 0;
字符大小写转化
#include <cctype>
a=tolower(a) //大写变小写
toupper()
字母判断
isalnum
函数接受一个字符作为参数,如果该字符是字母或数字,则返回非零值(通常是 1),否则返回零。
isalnum(s[left])
这两行代码的作用是将两个二进制字符串 a
和 b
对应位置的位相加,并将进位 carry 加到下一位的计算中。
-
carry += i < a.size() ? (a.at(i) == '1') : 0;
: 这行代码首先检查当前的索引i
是否小于字符串a
的长度,如果是,则表示字符串a
还有位需要参与运算。然后,通过a.at(i) == '1'
来判断字符串a
在当前位是否为 '1',如果是,则将carry
加上 1,否则不加。这一步是将字符串a
在当前位的值(0 或 1)加到进位carry
上。 -
carry += i < b.size() ? (b.at(i) == '1') : 0;
: 类似地,这行代码首先检查当前的索引i
是否小于字符串b
的长度,如果是,则表示字符串b
还有位需要参与运算。然后,通过b.at(i) == '1'
来判断字符串b
在当前位是否为 '1',如果是,则将carry
再加上 1,否则不加。这一步是将字符串b
在当前位的值(0 或 1)加到进位carry
上。
这样,carry
就保存了当前位相加的结果和进位值。在下一次迭代中,会将这个进位值加到下一位的计算中。
字符串转化与添加
class Solution {
public:
vector<string> summaryRanges(vector<int>& nums) {
vector<string> ret;
int i = 0;
int n = nums.size();
while (i < n) {
int low = i;
i++;
while (i < n && nums[i] == nums[i - 1] + 1) {
i++;
}
int high = i - 1;
string temp = to_string(nums[low]);
if (low < high) {
temp.append("->");
temp.append(to_string(nums[high]));
}
ret.push_back(move(temp));
}
return ret;
}
};
ret.push_back(move(temp));
使用 std::move
可以在一些情况下提高程序的性能,尤其是对于大型对象或者需要频繁操作的对象。移动操作比复制操作更高效,因为它只涉及指针或资源所有权的转移,而不需要进行实际的复制。
在这个特定的代码段中,temp
是一个临时字符串,用来存储范围的字符串表示,例如 "low->high"。当我们将 temp
添加到 ret
向量中时,使用 move
函数可以避免不必要的复制操作,提高了性能并节省了内存。因为 temp
在这之后不再需要,所以可以安全地将其移动到 ret
向量中,而不是进行复制。
字符判断
auto isVowel = [vowels = "aeiouAEIOU"s](char ch) {
return vowels.find(ch) != string::npos;
};
这行代码 return vowels.find(ch) != string::npos;
的意思是在字符串 vowels
中查找字符 ch
,如果找到则返回找到的位置(非空字符串的位置),否则返回 string::npos
。string::npos
是 std::string
类的静态成员常量,它表示未找到指定字符或子字符串时的特殊值。
tack
堆栈(stack)最大的特点就是先进后出(后进先出)(可用于逆序输出)
#include<stack>
定义
//stack的定义
stack<int>s1; //定义一个储存数据类型为int的stack容器s1
stack<double>s2; //定义一个储存数据类型为double的stack容器s2
stack<string>s3; //定义一个储存数据类型为string的stack容器s3
stack<结构体类型>s4; //定义一个储存数据类型为结构体类型的stack容器s4
stack<int> s5[N]; //定义一个储存数据类型为int的stack容器数组,N为大小
stack<int> s6[N]; //定义一个储存数据类型为int的stack容器数组,N为大小
常用成员函数
empty() //判断堆栈是否为空
pop() //弹出堆栈顶部的元素
push() //向堆栈顶部添加元素
size() //返回堆栈中元素的个数
top() //返回堆栈顶部的元素
示例
#include<iostream>
#include<stack>
using namespace std;
int main()
{
stack<int> s; //定义一个数据类型为int的stack
s.push(1); //向堆栈中压入元素1
s.push(2); //向堆栈中压入元素2
s.push(3); //向堆栈中压入元素3
s.push(4); //向堆栈中压入元素4
cout<<"将元素1、2、3、4一一压入堆栈中后,堆栈中现在的元素为:1、2、3、4"<<endl;
cout<<"堆栈中的元素个数为:"<<s.size()<<endl;
//判断堆栈是否为空
if(s.empty())
{
cout<<"堆栈为空"<<endl;
}
else
{
cout<<"堆栈不为空"<<endl;
}
cout<<"堆栈的最顶部元素为:"<<s.top()<<endl;
//弹出堆栈最顶部的那个元素
s.pop();
cout<<"将堆栈最顶部元素弹出后,现在堆栈中的元素为1、2、3"<<endl;
}
堆栈中的数据是不允许随机访问的,也就是说不能通过下标访问,且堆栈内的元素是无法遍历的。
遍历方法:
while(!s.empty())
{
cout<<s.top()<<" ";
s.pop();
}
class Solution {
public:
bool isValid(string s) {
stack<int> st;
for (int i = 0; i < s.size(); i++) {
if (s[i] == '(' || s[i] == '[' || s[i] == '{') st.push(i);
else {
if (st.empty()) return false;
if (s[i] == ')' && s[st.top()] != '(') return false;
if (s[i] == '}' && s[st.top()] != '{') return false;
if (s[i] == ']' && s[st.top()] != '[') return false;
st.pop();
}
}
return st.empty();
}
};
ListNode链表
ListNode的结构
struct ListNode {
int val; //当前结点的值
ListNode *next; //指向下一个结点的指针
ListNode(int x) : val(x), next(NULL) {} //初始化当前结点值为x,指针为空
};
在节点ListNode定义中,定义为节点为结构变量
节点存储了两个变量:value 和 next。value 是这个节点的值,next 是指向下一节点的指针,当 next 为空指针时,这个节点是链表的最后一个节点。
注意val只代表当前指针的值,比如p->val表示p指针的指向的值;而p->next表示链表下一个节点,也是一个指针。
构造函数包含两个参数 _value 和 _next ,分别用来给节点赋值和指定下一节点
单链表的创建和打印
#include <iostream>
using namespace std;
//定义结构体
struct ListNode{
int val;
ListNode* next;
};
class operateList
{
public:
/*创建单链表*/
void createList(ListNode *head)
{
int i;
ListNode* phead=head; //不破坏头指针
for(i=1;i<10;i++){
ListNode* node=new ListNode;
node->val=i;
node->next=NULL;
phead->next=node;
phead=node;
}
cout<<"创建成功\n";
}
/*打印链表*/
void printList(ListNode* head)
{
ListNode* phead=head;
while(phead->next!=NULL)
{
cout<<phead->val<<" ";
phead=phead->next;
}
cout<<phead->val;
}
};
int main()
{
ListNode* head=new ListNode;
operateList ll;
head->val=0;
head->next=NULL;
ll.createList(head);
ll.printList(head);
return 0;
}
逆序打印单链表的方式
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
//定义结构体
struct ListNode{
int val;
ListNode* next;
};
class operateList
{
public:
/*创建单链表*/
void createList(ListNode *head)
{
int i;
ListNode* phead=head; //不破坏头指针
for(i=1;i<10;i++){
ListNode* node=new ListNode;
node->val=i;
node->next=NULL;
phead->next=node;
phead=node;
}
cout<<"链表创建成功!\n";
}
/*打印链表*/
void printList(ListNode* head)
{
ListNode* phead=head;
while(phead->next!=NULL)
{
cout<<phead->val<<" ";
phead=phead->next;
}
cout<<phead->val<<"\n";
}
/*利用栈先进后出的思想*/
vector<int> printListInverseByStack(ListNode* head){
vector<int> result;
stack<int> arr;
int i;
ListNode* phead=head;
while(phead->next!=NULL)
{
arr.push(phead->val);
phead=phead->next;
}
arr.push(phead->val); //最后一个元素
while(!arr.empty())
{
result.push_back(arr.top());
arr.pop();
}
return result;
}
void printVector(vector<int> result)
{
int i;
for(i=0;i<result.size();i++)
cout<<result[i]<<" ";
cout<<"\n";
}
};
int main()
{
ListNode* head=new ListNode;
vector<int> result;
operateList ll;
head->val=0;
head->next=NULL;
ll.createList(head);
ll.printList(head);
//利用栈逆序
result=ll.printListInverseByStack(head);
cout<<"利用栈逆序的结果为:\n";
ll.printVector(result);
return 0;
}
采用递归对两个链表进行排序
#include <iostream>
using namespace std;
// 定义 ListNode 结构体
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == NULL) {
return l2;
}
if (l2 == NULL) {
return l1;
}
if (l1->val <= l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
};
// 打印链表
void printList(ListNode* head) {
while (head != NULL) {
cout << head->val << " ";
head = head->next;
}
cout << endl;
}
int main() {
// 创建两个链表
ListNode* l1 = new ListNode(1);
l1->next = new ListNode(2);
l1->next->next = new ListNode(4);
ListNode* l2 = new ListNode(1);
l2->next = new ListNode(3);
l2->next->next = new ListNode(4);
// 创建 Solution 对象
Solution solution;
// 合并两个链表
ListNode* mergedList = solution.mergeTwoLists(l1, l2);
// 打印合并后的链表
cout << "Merged List: ";
printList(mergedList);
return 0;
}
例题
ListNode* mergeTwoLists(ListNode *a, ListNode *b)
if ((!a) || (!b)) return a ? a : b;
- 如果链表
a
为空,或者链表b
为空,则直接返回其中非空的链表。如果两个链表都为空,则返回其中一个(在这里没有要求返回哪一个)。
这句代码使用了逻辑运算符 ||
表示逻辑或,!
表示逻辑非。它首先检查链表 a
是否为空,如果为空,则返回链表 b
;如果链表 a
不为空,则返回链表 a
。这样做的目的是在有一个链表为空的情况下,直接返回另一个链表,因为此时无需合并操作,只需返回非空的链表即可。
创建虚拟头节点链表
-
new ListNode(0, head)
:这部分代码创建了一个新的 ListNode 对象,其中第一个参数是值0
,表示虚拟头节点的值为0
,第二个参数是指向原始头节点的指针head
。这样就创建了一个带有值为0
的虚拟头节点,同时指向原始的头节点。 -
struct ListNode* dummyHead = ...
:这部分代码将创建的虚拟头节点赋值给名为dummyHead
的指针变量。因为虚拟头节点的存在,我们可以在删除元素时更方便地处理头节点的情况,而不需要单独处理头节点和非头节点的删除逻辑。
struct ListNode* dummyHead = new ListNode(0, head);
链表翻转
/**
* 以链表1->2->3->4->5举例
* @param head
* @return
*/
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
/*
直到当前节点的下一个节点为空时返回当前节点
由于5没有下一个节点了,所以此处返回节点5
*/
return head;
}
//递归传入下一个节点,目的是为了到达最后一个节点
ListNode newHead = reverseList(head.next);
/*
第一轮出栈,head为5,head.next为空,返回5
第二轮出栈,head为4,head.next为5,执行head.next.next=head也就是5.next=4,
把当前节点的子节点的子节点指向当前节点
此时链表为1->2->3->4<->5,由于4与5互相指向,所以此处要断开4.next=null
此时链表为1->2->3->4<-5
返回节点5
第三轮出栈,head为3,head.next为4,执行head.next.next=head也就是4.next=3,
此时链表为1->2->3<->4<-5,由于3与4互相指向,所以此处要断开3.next=null
此时链表为1->2->3<-4<-5
返回节点5
第四轮出栈,head为2,head.next为3,执行head.next.next=head也就是3.next=2,
此时链表为1->2<->3<-4<-5,由于2与3互相指向,所以此处要断开2.next=null
此时链表为1->2<-3<-4<-5
返回节点5
第五轮出栈,head为1,head.next为2,执行head.next.next=head也就是2.next=1,
此时链表为1<->2<-3<-4<-5,由于1与2互相指向,所以此处要断开1.next=null
此时链表为1<-2<-3<-4<-5
返回节点5
出栈完成,最终头节点5->4->3->2->1
*/
head.next.next = head;
head.next = null;
return newHead;
}
回溯法
往下走后也要往回走(回溯)
用来遍历所有情况(包含递归思想),也可以有选择的遍历所有情况,常用来寻找组合数
示例代码
class Solution {
void backtrack(vector<string>& ans, string& cur, int open, int close, int n) {
if (cur.size() == n * 2) {
ans.push_back(cur);
return;
}
if (open < n) {
cur.push_back('(');
backtrack(ans, cur, open + 1, close, n);
cur.pop_back();
}
if (close < open) {
cur.push_back(')');
backtrack(ans, cur, open, close + 1, n);
cur.pop_back();
}
}
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
string current;
backtrack(result, current, 0, 0, n);
return result;
}
};
KMP算法
KMP算法实际上解决的是一个字符串匹配的问题,即从一个目标字符串(通常非常长)中找到与给定字符串(也称为模式串)相匹配的字串的位置
讲解视频
KMP算法讲解
示例
class Solution {
public:
int strStr(string haystack, string needle) {
int n = haystack.size(), m = needle.size();
if (m == 0) {
return 0;
}
vector<int> pi(m); //相当于NEXT数组
//计算出NEXT数组 j代表前缀 i代表后缀
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && needle[i] != needle[j]) {
j = pi[j - 1];//前缀数字往后退 直到和后缀一致
}
if (needle[i] == needle[j]) {
j++;
}
pi[i] = j;
}
//遍历寻找匹配的字符串
for (int i = 0, j = 0; i < n; i++) {
while (j > 0 && haystack[i] != needle[j]) {
j = pi[j - 1];//一旦遇到不相等的少比较pi[j - 1]位
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == m) {
return i - m + 1;
}
}
return -1;
}
};
二分查找
示例
中间值求法
int mid = ((right - left) >> 1) + left;
用来求中间值,并且移位复杂度小于除法
避免在计算中间位置时产生整数溢出的情况。当 left
和 right
都是很大的正整数时,它们的和可能会超出整数的表示范围。
二分查找求平方根
class Solution {
public:
int mySqrt(int x) {
int l = 0, r = x, ans = -1;
while (l <= r) {
int mid = l + (r - l) / 2;
if ((long long)mid * mid <= x) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
return ans;
}
};
二叉树
前序遍历
树的前序遍历指的是对树按照根、左、右的规律进行访问
递归代码实现
//存储遍历结果的数组
vector<int> v;
//前序遍历函数
vector<int> preorderTraversal(TreeNode* root) {
if(root==nullptr) return v;
v.emplace_back(root->val); //输入数组语句
preorderTraversal(root->left);
preorderTraversal(root->right);
return v;
}
迭代代码实现(使用一个栈,速度比递归更快)
vector<int> preorderTraversal(TreeNode* root)
{
vector<int> v;//存储遍历结果的数组
stack<TreeNode*> s; //栈,模拟搜索
TreeNode* temp = root;
//循环条件,只有当遍历完所有节点并且栈为空的时候才终止
while(temp || !s.empty())
{
//当指向不为空节点时
if(temp != nullptr)
{
v.emplace_back(temp->val);
s.push(temp); //将该结点入栈
temp = temp->left; //根据前序遍历的要求,指向它的左子节点
}else
{
//当左边已经完全搜完时,弹出栈顶节点并找到它的右子节点继续进行搜索
temp = s.top()->right;
s.pop();
}
}
return v;
}
例题
中序遍历
树的中序遍历指的是对树按照左、根、右的规律进行访问
递归代码
//存储遍历结果的数组
vector<int>v;
//中序遍历函数
vector<int> inorderTraversal(TreeNode* root) {
if(root==nullptr) return v;
inorderTraversal(root->left);
v.emplace_back(root->val); //输入数组语句
inorderTraversal(root->right);
return v;
}
迭代代码
流程图解
先将根的左孩子依次入栈,直到为空
栈顶元素出栈并访问,如果其右孩子为空,继续执行2
若右孩子不空,则执行1
vector<int> inorderTraversal(TreeNode* root)
{
vector<int>v; //存储结果语句
stack<TreeNode*> s;
TreeNode* temp = root;
while(1)
{
//先找到最左边的节点,并将经过的节点入队
while(temp)
{
s.push(temp);
temp = temp->left;
}
//当栈为空时则退出循环
if(s.empty()) break;
//加入栈顶节点的值,即最左边的节点的值
v.emplace_back(s.top()->val);
//查找该节点的右子节点
temp = s.top()->right;
s.pop();
}
return v;
}
后序遍历
树的后序遍历指的是对树按照左、右、根的规律进行访问
递归代码
//存储结果数组
vector<int> v;
//后序遍历函数
vector<int> postorderTraversal(TreeNode* root) {
if(root == nullptr) return v;
postorderTraversal(root->left);
postorderTraversal(root->right);
v.emplace_back(root->val); //输入语句
return v;
}
迭代代码
先将根的左孩子依次入栈,直到为空
读栈顶元素,若其右孩子不空且未被访问过,则将右子树执行1
若其右子树为空或者已被访问过,栈顶元素出栈并访问
vector<int> postorderTraversal(TreeNode* root) {
vector<int>res;
stack<TreeNode*>s;
TreeNode* temp=root;
TreeNode* r=nullptr;
while(temp || !s.empty())
{
if(temp)
{
s.push(temp);
temp=temp->left;
}else
{
temp=s.top();
if(temp->right && temp->right!=r)
temp=temp->right;
else
{
res.emplace_back(temp->val);
s.pop();
r=temp;
temp=nullptr;
}
}
}
return res;
}
Morris遍历
public List<Integer> method3(TreeNode root) {
List<Integer> ans=new LinkedList<>();
while(root!=null){
//没有左子树,直接访问该节点,再访问右子树
if(root.left==null){
ans.add(root.val);
root=root.right;
}else{
//有左子树,找前驱节点,判断是第一次访问还是第二次访问
TreeNode pre=root.left;
while(pre.right!=null&&pre.right!=root)
pre=pre.right;
//是第一次访问,访问左子树
if(pre.right==null){
pre.right=root;
root=root.left;
}
//第二次访问了,那么应当消除链接
//该节点访问完了,接下来应该访问其右子树
else{
pre.right=null;
ans.add(root.val);
root=root.right;
}
}
}
return ans;
}
层序遍历(广度优先搜索)
层序遍历就是从上到下、从左到右输出节点
迭代代码
把每一层放在一个数组中,最后将它们再放入一个总的数组中
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> v;
if(root == NULL) return v; //特判
queue<TreeNode*> q; //队列
TreeNode* temp = NULL;
q.push(root);
while(!q.empty()) //队列为空跳出循环
{
vector<int> ans; //存放每一层的数字
int n = q.size(); //每一层的个数
for (int i=0;i<n;i++)
{
temp = q.front();
q.pop();
ans.emplace_back(temp->val);
if(temp->left != NULL) q.push(temp->left);
if(temp->right != NULL) q.push(temp->right);
}
v.emplace_back(ans);
}
return v;
}
深度优先搜索
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) return 0;
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
};
平衡二叉树
平衡二叉树的定义是:二叉树的每个节点的左右子树的高度差的绝对值不超过 111,则二叉树是平衡二叉树。
判断是不是平衡二叉树
class Solution {
public:
int height(TreeNode* root) {
if (root == NULL) {
return 0;
} else {
return max(height(root->left), height(root->right)) + 1;
}
}
bool isBalanced(TreeNode* root) {
if (root == NULL) {
return true;
} else {
return abs(height(root->left) - height(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
}
}
};
这段代码是用来检查一棵二叉树是否是平衡二叉树(Balanced Binary Tree)的。平衡二叉树是一种二叉树,其中每个节点的左右子树的高度差不超过 1。
让我们逐步解释 return abs(height(root->left) - height(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
这行代码的作用:
height(root->left)
:计算根节点左子树的高度。height(root->right)
:计算根节点右子树的高度。abs(height(root->left) - height(root->right)) <= 1
:判断根节点的左右子树高度差是否不超过 1。isBalanced(root->left)
:递归地检查左子树是否是平衡二叉树。isBalanced(root->right)
:递归地检查右子树是否是平衡二叉树。
综上,这行代码的作用是判断当前节点的左右子树的高度差是否不超过 1,并且递归地检查左右子树是否都是平衡二叉树。如果满足这些条件,则返回 true
,表示当前节点及其子树是平衡二叉树;否则返回 false
,表示当前节点及其子树不是平衡二叉树。
因为
return abs(height(root->left) - height(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);是&& 的关系,一旦一个过程出现false 则会一直向上传递到最终结果为false
哈希表
集合
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode*> seen;
while (head != nullptr) {
if (seen.count(head)) {
return true;
}
seen.insert(head);
head = head->next;
}
return false;
}
};
-
unordered_set<ListNode*> seen;
:这行代码定义了一个名为seen
的无序集合(unordered_set),其中存储的是指向 ListNode 结构的指针。在这个问题中,它用于记录遍历过的节点,以便检测是否存在环。 -
seen.count(head)
:这行代码用于检查集合seen
中是否已经包含了当前节点head
。count
函数返回指定元素在集合中出现的次数,因为在本例中集合seen
中存储的是指针,所以这里的count
函数会检查集合中是否已经包含了指向当前节点的指。
图
class Solution {
public:
int majorityElement(vector<int>& nums) {
unordered_map<int, int> counts;
int majority = 0, cnt = 0;
for (int num: nums) {
++counts[num];
if (counts[num] > cnt) {
majority = num;
cnt = counts[num];
}
}
return majority;
}
};
同构字符串
s2t.count(x)是否包含键(key)为x
class Solution {
public:
bool isIsomorphic(string s, string t) {
unordered_map<char, char> s2t;
unordered_map<char, char> t2s;
int len = s.length();
for (int i = 0; i < len; ++i) {
char x = s[i], y = t[i];
if ((s2t.count(x) && s2t[x] != y) || (t2s.count(y) && t2s[y] != x)) {
return false;
}
s2t[x] = y;
t2s[y] = x;
}
return true;
}
};
杂碎知识
auto node1 = queue1.front();
auto
关键字用于自动推断变量的类型。具体来说,auto
会让编译器根据变量的初始化表达式来推断其类型,而不需要显式地指定类型。
rand() % N
来生成一个 0 到 N-1
之间的随机数,其中 N
是你想要的范围
rand() % 2
:rand()
函数会生成一个随机数,% 2
的操作会将其限制在 0 和 1 之间。
叶子节点
叶子节点是指没有子节点的节点。
FLASH
FLASH属于广义上的ROM,和EEPROM的最大区别是FLASH按扇区操作,相对于EEPROM的改进就是擦除时不再以字节为单位,而是以块为单位,一次简化了电路,数据密度更高,降低了成本。上M的ROM一般都是闪存。而EEPROM则按字节操作。目前Flash主要有两种OR Flash和NA DN Flash。
位运算优化
二进制1的个数
class Solution {
public:
int hammingWeight(uint32_t n) {
int ret = 0;
while (n) {
n &= n - 1;
ret++;
}
return ret;
}
};
验证是否为2的幂
异或运算符
if ((left1 == nullptr) ^ (left2 == nullptr)) {
return false;
}
- 如果
left1
和left2
都为nullptr
,结果为假。 - 如果
left1
和left2
中有且仅有一个为nullptr
,结果为真。 - 如果
left1
和left2
都不为nullptr
,结果为假。
异或求只出现一次的数字
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for (auto e: nums) ret ^= e;
return ret;
}
};
class Solution {
public:
int missingNumber(vector<int>& nums) {
int res = 0;
int n = nums.size();
for (int i = 0; i < n; i++) {
res ^= nums[i];
}
for (int i = 0; i <= n; i++) {
res ^= i;
}
return res;
}
};
异或删除最右侧的1
class Solution {
public:
int hammingDistance(int x, int y) {
int s = x ^ y, ret = 0;
while (s) {
s &= s - 1;
ret++;
}
return ret;
}
};
for的遍历
vector<int> table(26, 0);
for (auto& ch: s) {
table[ch - 'a']++;
}
各个位上的数字相加,直到结果为一位数
3的幂
class Solution {
public:
bool isPowerOfThree(int n) {
while (n && n % 3 == 0) {
n /= 3;
}
return n == 1;
}
};
4的幂
class Solution {
public:
bool isPowerOfFour(int n) {
return n > 0 && (n & (n - 1)) == 0 && (n & 0xaaaaaaaa) == 0;
}
};