Day1 Python to CPP

看懂 LeetCode 的 C++ 代码框架;
会使用 vector、循环、条件判断和函数;
完成「移动零」和「两数之和」的基础版本。

首先,和python不同,cpp在定义变量时需要先声明类型,如:

C++
int age = 20;
long long total = 10000000000LL;
double score = 95.5;
bool passed = true;
char letter = 'A';
string name = "Kirawii";

注意true和false都是小写,每句话通常以 ; 结束;

其次,是条件判断:

C++
if (x > 0) {
    cout << "positive" << endl;
} else if (x == 0) {
    cout << "zero" << endl;
} else {
    cout << "negative" << endl;
}

用花括号代表代码块,不依赖缩进。

然后是循环,分为按下标遍历:

C++
for (int i = 0; i < nums.size(); i++) {
    cout << i << " " << nums[i] << endl;
}

初始化,循环条件,以及每轮结束后做什么

直接遍历元素:

C++
for (int x : nums) {
    cout << x << endl;
}

while循环:

C++
while (left < right) {
    left++;
}

最后是cpp的列表/数组:vector

C++
vector<int> nums = {1, 2, 3};

nums.push_back(4);  // 添加元素
nums.pop_back();    // 删除最后一个元素

int n = nums.size();

cout << nums[0] << endl;
cout << nums.back() << endl;
nums[0] = 100; //修改元素
swap(nums[0], nums[1]);//交换两个元素

练习题

数组求和:没什么好说的

C++
class Solution {
public:
    int arraySum(vector<int>& nums) {
        int sum = 0;
        // 在这里遍历 nums
        for(int x : nums){
	        sum+=x;
	      }
        return sum;
    }
};

leetcode283:移动零:

C++
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int slow = 0;
        for(int fast = 0;fast < nums.size();fast++){
            if (nums[fast]!=0){
                swap(nums[slow],nums[fast]);
                slow++;
            }
        }
    }
};

思路是双指针,因为我们需要把所有0移动到末尾,且要保持非0元素的相对顺序,我们让slow指向下一个非0元素应该在的地方,一开始也就是0,然后fast遍历整个数组,遇到非0元素,就交换fast下标的数值和slow下标的数值,然后让slow++,移动到下一个非0元素应该在的地方。至于初始状态是无所谓的,如果slow指向的是0,会把非0元素交换过来,如果slow指向的是非0元素,fast也指向的是slow,此时会自己和自己发生交换swap,无所谓。

leetcode1:两数之和

C++
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        for(int i =0;i<nums.size();i++){
            for(int j =i+1;j<nums.size();j++){
                if(nums[i]+nums[j] == target){
                    return {i,j};
                }
            }
        }
        return {};
    }
};

先用暴力熟悉一下cpp,新的知识是return {i,j},表示返回一个vector<int>,这个算法的时间复杂度是O(n^2).

练习题:返回数组中的最大值:返回数组中偶数的数量:原地交换数组的第一个和最后一个元素

C++
int findMax(vector<int>& nums) {
    int max = nums[0];
    for (int i=0;i<nums.size();i++) {//这里可以从1开始,因为0用来初始化了
        if (nums[i]>max) {
            max = nums[i];
        }
    }
    return max;
}
//命名尽量不用max,有库函数也叫max,建议叫maxValue/maximum
int countEven(vector<int>& nums) {
    int sum = 0;
    for (int i = 0;i<nums.size();i++) {
        if (nums[i]%2==0) {
            sum+=1;
        }
    }
    return sum;
}
//可以用只需要元素的循环方式
void swapEnds(vector<int>& nums) {
    int temp = nums[0];
    nums[0]=nums[nums.size()-1];
    nums[nums.size()-1]=temp;
}
//可以加一个判断,nums.empty(),防止空数组越界

几个新操作:

C++
nums.empty()  // 判断是否为空
nums.front()  // 第一个元素
nums.back()   // 最后一个元素

最后一道题:给定一个整数数组,将数组原地反转。要求使用左右双指针,不创建新的数组。

C++
void reverseArray(vector<int>& nums) {
    if (nums.empty()) {
        return;
    }
    int right = nums.size()-1;
    for (int left = 0;left <nums.size();left++) {
        if (left == right) {
            return;
        }
        swap(nums[left],nums[right]);
        right--;
        }
    }
    //此乃错误答案,只能在奇数数组的情况下运作。偶数不会出现left=right的情况,直接让left在小于right的情况下运作就行。
C++
void reverseArray(vector<int>& nums) {
    if (nums.empty()) {
        return;
    }
    int right = nums.size()-1;
    for (int left = 0;left <right;left++) {
        swap(nums[left],nums[right]);
        right--;
        }
}
AI评价:循环终止条件还需要更敏感,变量命名可以更准确,解释代码时要区分“执行”和“效果”

Day2 Hash Map

  • 理解 unordered_map
  • 理解 unordered_set
  • 把两数之和从 O(n²) 优化到 O(n)
  • 完成一道集合练习。

哈希表的作用是:

快速判断某个值是否已经出现,以及它出现在哪个下标。

平均查询时间可以看作:O(1) 核心操作:先查找,后插入。

C++
unordered_map<int, int> mapping;

mapping[2] = 0;
mapping[7] = 1;

//判断键是否存在
mp.count(key)
mp.find(key)
if (mp.find(10)!=mp.end()){
	cout<<"exist!"<<endl;
}

这段类型unordered_map<int, int>,表示key类型int,value类型int,在两数之和中,key = 数组中的数字,value = 这个数字的下标。因为我们想要的是下标,而已知的是数值。所以反过来了。

mp.find(key)返回的是一个迭代器,而start和end就不用多说了。

Python中的集合是set(),在cpp中是:

C++
unordered_set<int> seen;

seen.insert(5);

if (seen.count(5)) {
    cout << "存在" << endl;
}
seen.insert(10);  // 添加
seen.erase(10);   // 删除
seen.count(10);   // 判断是否存在
seen.size();      // 元素数量
seen.empty();     // 是否为空

如果只需要判断“某个数字是否出现过”,使用 unordered_set

如果还需要保存它的下标、频率或其他信息,使用 unordered_map

练习题

练习1:判断是否存在重复数字

C++
bool containsDuplicate(vector<int>& nums) {
    unordered_set<int> seen;
    for (int i:nums) {
        if (seen.count(i)==1) {
            return true;
        }
        seen.insert(i);
    }
    return false;
}

练习 2:统计元素频率

C++
unordered_map<int, int> countFrequency(vector<int>& nums) {
    unorder_map<int,int>frequency;
    for(intx:nums) {
        frequency[x]++;
    }
    return frequecy;
}

练习3:使用unordered_map优化两数之和

C++
vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int,int>indexMap;
    for (int i =0;i<nums.size();i++) {
        int need = target - nums[i];
        if (indexMap.find(need)!=indexMap.end()) {
            return {indexMap[need],i};
        }
        indexMap[nums[i]]=i; //最重要的,先查询再插入,插入的时候,key是数值,value是坐标
    }
    return {};
}

练习4:字母异位词

C++
bool isAnagram(string s, string t) {
    unordered_map<char,int>times;
    unordered_map<char,int>times2;
    for (char x:s) {
        times[x]++;
    }
    for (char x:t) {
        times2[x]++;
    }
    return times==times2;
}

leetcode49. 字母异位词分组

C++
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string,vector<string>> groups;
        for (string s:strs){
            string key = s;
            sort(key.begin(),key.end());
            groups[key].push_back(s);
        }
        vector<vector<string>> result;
        for (auto& pair:groups){
            result.push_back(pair.second);
        }
        return result;

    }
};
C++
遍历哈希表时:for (auto& pair : groups)
每个pair都包含
pair.first   // key
pair.second  // value

哈希表最核心的三种用途:

  • 判断元素是否出现;
  • 保存元素对应的下标;
  • 统计元素出现次数。

Day3 栈、队列与链表指针

今日目标

  • 会使用 stackqueue
  • 看懂 ListNode*nullptr>
  • 完成有效括号、反转链表、环形链表;
  • 理解链表题为什么必须先保存 next

第一部分是栈,栈的特点就是后进先出

C++
stack<int> st;

st.push(10);
st.push(20);
st.push(30);

int x = st.top();//获取栈顶数值
st.pop();//弹出
st.empty(); // 是否为空
st.size();  // 元素数量

注意和Python不同的是,cpp的pop只会直接弹出元素,不会返回,因此,需要先读取再删除。

另外,栈为空时不能调用top和pop。

练习题1 leetcode 20. 有效的括号

C++
class Solution {
public:
    bool isValid(string s) {
        stack<char> st;
        for(char c:s){
            if(c=='(' || c == '{' || c == '['){
                st.push(c);
            }else{
                if(st.empty()){
                    return false;
                }
                if((c == ')' && st.top()=='(')||(c == ']' && st.top()=='[')||(c == '}' && st.top()=='{')){
                    st.pop();
                    continue;
                }else{
                    return false;
                }
            }
    }
    if (st.empty()){
        return true;
    }else{
        return false;
    }
    }
};

主要是要考虑几个边界条件,时刻注意栈是不是为空。另外读取栈顶后要记得pop。

第二部分队列,FIFO,先进先出。

C++
queue<int> q;

q.push(10);
q.push(20);
q.push(30);
q.front(); //访问队首
q.back(); //访问队尾
q.pop(); //删除队首
q.empty();
q.size();


和栈一样,
q.pop() 不返回元素:

第三部分,链表。

数组中的元素通常连续存储,可以通过下标访问:

而链表由一个个节点组成,每个节点保存:

  1. 当前值;
  2. 下一个节点的位置。

1 → 2 → 3 → nullptr

leetcode中通常已经定义好了。

C++
struct ListNode {
    int val;
    ListNode* next;

    ListNode(int x) : val(x), next(nullptr) {}
};

所以先不研究构造函数,主要看 int val和ListNode* next

C++
ListNode* head; //可以理解为head保存某个链表节点的位置

他不是节点本身,而是指向节点的指针。

访问当前节点 head→val

访问下一个节点 head→next

nullptr表示不指向任何节点,类似Python的None。

链表最后一个节点满足 node→next == nullptr;

空链表满足 head == nullptr

简写成

C++
if (!head) {
    return;
}

如何遍历列表?

C++
ListNode* cur = head;

while (cur != nullptr) {
    cout << cur->val << endl;
    cur = cur->next;
}

小练习:计算链表长度。

C++
int getLength(ListNode* head) {
    ListNode* cur = head;
    int sum = 0;
    while(cur!= nullptr){
        sum++;
        cur=cur->next;
    }
    return sum;
}

leetcode.206 反转列表

C++
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr;
        ListNode* cur = head;
        while(cur!=nullptr){
            ListNode* nextNode = cur->next;
            cur->next = prev;
            prev = cur;
            cur = nextNode;
        }
        return prev;
    }
};

注意最后返回的不是cur,而是prev,prev在循环结束后是指向原链表最后一个节点的。所以新的头节点是prev。

三指针是比较常见的O(1)做法,还有复制链表/维护一个栈,空间复杂度高一点。

leetcode.141 环形链表

C++
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast!=nullptr && fast->next != nullptr){
            slow=slow->next;
            fast=fast->next->next;
            if(slow==fast){
                return true;
            }
        }
        return false;
    }
};

快慢双指针。进阶问题:如果存在环,如何判断环的长度呢?方法是,快慢指针相遇后继续移动,直到第二次相遇。两次相遇间的移动次数即为环的长度。

DAY4 双指针与滑动窗口

双指针是一种遍历方式,常见形式有三种

C++
左右相向:数组反转,有序数组查找,最多水的容器,三数之和
int left = 0;
int right = nums.size() - 1;

while (left < right) {
    // 根据条件移动 left 或 right
}
快慢指针:移动0,删除重复元素,原地修改数组
int slow = 0;

for (int fast = 0; fast < nums.size(); fast++) {
    // 根据 fast 的内容更新 slow
}
滑动窗口:连续子数组,连续子字符串,最长最短满足某种条件的区间。
int left = 0;

for (int right = 0; right < nums.size(); right++) {
    // 加入 nums[right]

    while (窗口不合法) {
        // 移除 nums[left]
        left++;
    }
}

练习题

leetcode.11 盛最多水的容器

C++
class Solution {
public:
    int maxArea(vector<int>& height) {
        int left = 0;
        int right = height.size()-1;
        int answer = 0;
        int area = min(height[left],height[right])*(right-left);
        int temp = 0;
        while(left<right){
            if (height[left]<height[right]){
                left++;
            }else{
                right--;
            }
            temp = min(height[left],height[right])*(right-left);
            if(temp>area){
                area = temp;
            }
        }
        return area;
    }
};

一次过,主要是要考虑面积的计算,因为接水的时候主要受短边影响。所以我们只需要一直移动短边,然后遍历结束后保留一个面积的最大值即可。

leetcode.15 三数之和

C++
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<vector<int>> result;
        for(int i = 0;i <nums.size();i++){
            if(i>0 && nums[i]==nums[i-1]){
                continue;
            }
            int left = i+1;
            int right = nums.size()-1;
            while(left<right){
                if (nums[left]+nums[right]>-nums[i]){
                    right--;
                }else if(nums[left]+nums[right]<-nums[i]){
                    left++;
                }else{
                    result.push_back({nums[i],nums[left],nums[right]});

                    while(left < right && nums[left]==nums[left+1]){
                        left++;
                    }
                    while(left < right && nums[right]==nums[right-1]){
                        right--;
                    }
                    left++;
                    right--;
            }
           
        }
    }
        return result;    
}
};

最重要的是注意三个去重,
第一处是对固定下标
i 去重:若 nums[i] == nums[i - 1],说明以这个数作为第一个数的组合已经搜索过,应直接跳过。
第二处和第三处是在找到答案后,对
leftright 去重:相同的第二个数和第三个数会生成相同的三元组,因此应先跳过重复值,再分别执行 left++right--,继续寻找新的组合。这里先去重再移动,是因为当前的 nums[left]nums[right] 正是刚刚加入答案的值。

剩下的其实三数之和就是多了一个循环的两数之和。

leetcode.3 无重复字符的最长子串

C++
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_set<char> window;
        int left = 0;
        int answer = 0;
        for (int right =0;right<s.size();right++){
            while(window.count(s[right])){
                window.erase(s[left]);
                left++;
            }
            window.insert(s[right]);
            int length = right - left +1;
            answer = max(answer,length);
        }
        return answer;
        }
};

三个判断信号:

  • 数组两端/两个元素构成答案
  • 有序数组中找两个数的和
  • 最长或最短连续子串

DAY5 前缀和、最大子数组、前后缀面积

什么是前缀和,就是数组中的prefix[i] 表示前 i 个元素的总和。

本质是先保存从开头到各位置的累计结果,以后可以快速计算任意连续区间的和。

练习题

leetcode.560 和为K的子数组

C++
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int,int> prefixCount;
        prefixCount[0]=1;
        int prefix = 0;
        int answer = 0;
        for (int x:nums){
            prefix += x;
            int need = prefix -k;
            if(prefixCount.count(need)){
                answer+=prefixCount[need];
            }
            prefixCount[prefix]++;
        }
        return answer;
    }
};

假设当前累计和为:

Plain text
prefix

我们想找到一段子数组的和等于 k

如果某个更早的前缀和是:

Plain text
previousPrefix

那么:

Plain text
prefix - previousPrefix = k

移项:

Plain text
previousPrefix = prefix - k

所以遍历到当前元素时,只需要问:

之前出现过多少次 prefix - k

出现几次,就说明存在几个以当前位置结尾、和为 k 的子数组。

哈希表存什么?

Plain text
unordered_map<int,int>prefixCount;

这里:

  • key:某个前缀和;
  • value:这个前缀和出现的次数。

例如:

Plain text
0 → 1
1 → 2
3 → 1

表示:

  • 前缀和 0 出现过一次;
  • 前缀和 1 出现过两次;
  • 前缀和 3 出现过一次。

注意要初始化状态,prefixCount[0]=1;否则会漏掉从下标 0 开始的合法子数组。
不用滑动窗口的原因是滑动窗口依赖某种单调性,但是负数会破坏单调性,因此使用前缀和。

leetcode.53 最大子数组和

C++
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int current = nums[0];
        int answer = nums[0];
        for(int i = 0;i<nums.size();i++){
            current = max(current+nums[i],nums[i]);
            answer = max(answer,current);
        }
        return answer;
    }
};

如果前面的累计和有帮助,就继续连接;如果前面的累计和是负担,就从当前位置重新开始。因为我们只需要记录最大值,而不是记录最大值对应的数组/区间。

leetcode.283 除自身以外的数组的乘积

C++
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        vector<int> answer(nums.size(),1);
        int leftProduct = 1;
        for(int i = 0;i<nums.size();i++){
            answer[i] = leftProduct;
            leftProduct *= nums[i];
        }
        int rightProduct = 1;
        for(int i = nums.size()-1;i>=0;i--){
            answer[i]*=rightProduct;
            rightProduct *= nums[i];
        }
        return answer;
    }
};

DAY6 矩阵遍历与原地修改

二维矩阵在cpp中通常表示为

vector<vector<int>> matrix;

访问第i行第j列就是matrix[i][j];

行数和列数 matrix.size(),matrix[0].size(),注意矩阵不为空.

二维数组的遍历:

C++
for (int i = 0; i < matrix.size(); i++) {
    for (int j = 0; j < matrix[0].size(); j++) {
        cout << matrix[i][j] << endl;
    }
}

leetcode.73 矩阵置0

C++
class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        unordered_set<int> zeroRows;
        unordered_set<int> zeroCols;
        for(int i = 0;i<matrix.size();i++){
            for(int j = 0;j<matrix[0].size();j++){
                if(matrix[i][j]==0){
                    zeroCols.insert(j);
                    zeroRows.insert(i);
                }
            }
        }
        for(int i = 0;i<matrix.size();i++){
            for(int j = 0;j<matrix[0].size();j++){
                if(zeroCols.count(j) || zeroRows.count(i)){
                    matrix[i][j]=0;
                }
            }
        }
    }
};

直接狠狠暴力,用哈希表记录一下存在0的行和列。特别注意不能读取到0就全置0,会影响判断,不知道到底是原来的0还是被修改后的0,导致扩大范围。

leetcode.54 螺旋矩阵

C++
class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        int top = 0;
        int bottom = matrix.size()-1;
        int left = 0;
        int right = matrix[0].size()-1;
        vector<int> result;
        while(top<=bottom && left<=right){
            for(int i = left;i<=right;i++){
                result.push_back(matrix[top][i]);
            }
            top++;
            for(int j = top;j<=bottom;j++){
                result.push_back(matrix[j][right]);
            }
            right--;
            if(top<=bottom){
                for(int k = right;k>=left;k--){
                    result.push_back(matrix[bottom][k]);
                }
                bottom--;
            }
            if(left<=right){
                for(int l = bottom;l>=top;l--){
                    result.push_back(matrix[l][left]);
                }
                left++;
            }
        }
        return result;
    }
};

其实本质就是把一个大矩阵拆成不同的边界,关键的是控制边界然后触发不同的行为,就四种。另外边界本身也需要访问。所以是≤,还要检查边界和收缩对应边。一边想一边写就行。

遍历部分为什么通常不用/要检查
从左向右外层 while 已保证当前上边界合法
从上向下即使行边界失效,正序 for 会自然不执行
从右向左可能重复访问已经走过的单行,必须检查 top <= bottom
从下向上可能重复访问已经走过的单列,必须检查 left <= right

leetcode.48 旋转图像

顺时针 90° = 转置 + 每行反转

逆时针 90° = 转置 + 每列反转

C++
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        for(int i = 0;i<matrix.size();i++){
            for(int j = i+1;j<matrix[0].size();j++){
                swap(matrix[i][j],matrix[j][i]);
            }
        }
        for(int i = 0;i<matrix.size();i++){
            reverse(matrix[i].begin(),matrix[i].end());
        }
    }
};

第一次遍历给矩阵转置,第二次遍历给每行反转,正好是顺时针旋转90度。

DAY7 链表进阶

为什么链表题经常需要虚拟头结点?因为如果不设置虚拟头结点,我们往往要单独处理head,而设置虚拟头以后,就可以直接把head当做普通节点处理了。

C++
ListNode dummy(0);
dummy.next = head;

注意这里用 . 是访问节点对象的成员,创建节点指针访问其成员用箭头。

练习题

leetcode.21 合并两个有序链表

C++
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode dummy(0);
        ListNode* tail = &dummy;
        while(list1 != nullptr && list2 != nullptr){
            if(list1->val < list2->val){
                tail->next = list1;
                list1 = list1->next;
            }else{
                tail->next = list2;
                list2 = list2->next;
            }
            tail = tail->next;
        }
        if(list1){
            tail->next = list1;
        }else{
            tail->next = list2;
        }
        return dummy.next;
    }
};

其实就是新创建一个链表,用虚拟头结点。比较大小然后连接。主要是要知道最后返回的是dummy.next。另一个就是一条链表比完以后,另一条剩下的直接连上去就行。

leetcode.19 删除链表倒数第N个节点

C++
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode dummy(0);
        dummy.next = head;
        ListNode* fast = &dummy;
        ListNode* slow = &dummy;
        for(int i = 0;i<=n;i++){
            fast = fast->next;
        }
        while(fast!=nullptr){
            fast= fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return dummy.next;
    }
};

用快慢双指针来找倒数第几个节点。

leetcode.160 相交链表

C++
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* pA = headA;
        ListNode* pB = headB;
        while(pA != pB){
            pA = (pA == nullptr) ? headB:pA->next;
            pB = (pB == nullptr)? headA:pB->next;
        }
        return pA;
    }
};

指针换路法,这个明天再看看。

DAY8 二叉树基础-递归DFS与层序BFS

今天开始二叉树。目标是掌握两套模板:

  1. 递归 DFS:向左右子树继续处理;
  2. 队列 BFS:一层一层遍历。

一、二叉树节点怎么表示

LeetCode 通常已经定义:

C++
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;

    TreeNode(int x)
        : val(x), left(nullptr), right(nullptr) {}
};

每个节点有三个成员:root→val root→left root→right

空树和空子树用nullptr表示

常见的递归出口:

C++
if (root == nullptr) {
    return ...;
}

二、理解树的递归

链表只有一条后路:

C++
cur =cur->next;

二叉树有两条后路:

C++
root->left;root->right;

递归处理二叉树通常写成:

C++
返回类型dfs(TreeNode*root) {
if (root==nullptr) {
	return 空节点对应的结果;
    }

    左子树结果 =dfs(root->left);
    右子树结果 =dfs(root->right);
    return 根据当前节点和左右结果计算答案;
}

你可以把它理解为:

我只负责当前节点;左子树交给递归处理,右子树也交给递归处理。

练习题:

leetcode.104 二叉树的最大深度

C++
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(root == nullptr){
            return 0;
        }
        int leftD = maxDepth(root->left);
        int rightD = maxDepth(root->right);
        return max(leftD,rightD)+1;
    }
};

leetcode.226 翻转二叉树

C++
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(root == nullptr){
            return nullptr;
        }
        swap(root->left,root->right);
        invertTree(root->left);
        invertTree(root->right);
        return root;
    }
};

三、DFS 的三种遍历顺序

对于当前节点、左子树、右子树,有三种常见顺序。

前序遍历

Plain text
当前节点 → 左子树 → 右子树
C++
void preorder(TreeNode* root) {
    if (root == nullptr) {
        return;
    }

    cout << root->val;
    preorder(root->left);
    preorder(root->right);
}

中序遍历

Plain text
左子树 → 当前节点 → 右子树
C++
void inorder(TreeNode* root) {
    if (root == nullptr) {
        return;
    }

    inorder(root->left);
    cout << root->val;
    inorder(root->right);
}

后序遍历

Plain text
左子树 → 右子树 → 当前节点
C++
void postorder(TreeNode* root) {
    if (root == nullptr) {
        return;
    }

    postorder(root->left);
    postorder(root->right);
    cout << root->val;
}

leetcode.102 二叉树的层序遍历

C++
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> q;
        q.push(root);
        vector<vector<int>> result;
        if (root == nullptr){
            return result;
        }
        while(!q.empty()){
            int levelSize = q.size();
            vector<int> level;
            for(int i =0;i<levelSize;i++){
                TreeNode* node = q.front();
                q.pop();
                level.push_back(node -> val);
                if(node->left != nullptr){
                    q.push(node->left);
                }
                if(node->right != nullptr){
                    q.push(node->right);
                }
            }
            result.push_back(level);
        }
        return result;
    }
};

看到递归关系

例如:

  • 树的高度;
  • 是否对称;
  • 路径和;
  • 翻转树;
  • 子树信息影响父节点。

优先考虑 DFS:

看到“每一层”

例如:

  • 层序遍历;
  • 每层最大值;
  • 右视图;
  • 最小深度。

优先考虑 BFS: