C++ 笔记

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区别

  1. 参数传递方式

    • push_back() 接受一个参数,该参数是要添加到容器中的元素的副本或可移动对象。
    • emplace_back() 接受一个参数包或者一系列参数,用来构造新元素。
  2. 效率

    • 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常用函数

  1. push() 在队尾插入一个元素
  2. pop() 删除队列第一个元素
  3. size() 返回队列中元素个数
  4. empty() 如果队列空则返回true
  5. front() 返回队列中的第一个元素
  6. 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])

这两行代码的作用是将两个二进制字符串 ab 对应位置的位相加,并将进位 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::nposstring::nposstd::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。这样做的目的是在有一个链表为空的情况下,直接返回另一个链表,因为此时无需合并操作,只需返回非空的链表即可。

创建虚拟头节点链表

  1. new ListNode(0, head):这部分代码创建了一个新的 ListNode 对象,其中第一个参数是值 0,表示虚拟头节点的值为 0,第二个参数是指向原始头节点的指针 head。这样就创建了一个带有值为 0 的虚拟头节点,同时指向原始的头节点。

  2. 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;

用来求中间值,并且移位复杂度小于除法

避免在计算中间位置时产生整数溢出的情况。当 leftright 都是很大的正整数时,它们的和可能会超出整数的表示范围。

二分查找求平方根

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遍历

https://leetcode.cn/problems/binary-tree-inorder-traversal/solutions/412886/er-cha-shu-de-zhong-xu-bian-li-by-leetcode-solutio/comments/1194763

    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); 这行代码的作用:

  1. height(root->left):计算根节点左子树的高度。
  2. height(root->right):计算根节点右子树的高度。
  3. abs(height(root->left) - height(root->right)) <= 1:判断根节点的左右子树高度差是否不超过 1。
  4. isBalanced(root->left):递归地检查左子树是否是平衡二叉树。
  5. 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;
    }
};

  1. unordered_set<ListNode*> seen;:这行代码定义了一个名为 seen 的无序集合(unordered_set),其中存储的是指向 ListNode 结构的指针。在这个问题中,它用于记录遍历过的节点,以便检测是否存在环。

  2. seen.count(head):这行代码用于检查集合 seen 中是否已经包含了当前节点 headcount 函数返回指定元素在集合中出现的次数,因为在本例中集合 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() % 2rand() 函数会生成一个随机数,% 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;
            }
  • 如果 left1left2 都为 nullptr,结果为假。
  • 如果 left1left2 中有且仅有一个为 nullptr,结果为真。
  • 如果 left1left2 都不为 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;
    }
};

等差、等比求和公式

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值