文章目录
- 数组
- [350. 两个数组的交集 II](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/)
- [566. 重塑矩阵](https://leetcode-cn.com/problems/reshape-the-matrix/)
- [118. 杨辉三角](https://leetcode-cn.com/problems/pascals-triangle/)
- [36. 有效的数独](https://leetcode-cn.com/problems/valid-sudoku/)
- [73. 矩阵置零](https://leetcode-cn.com/problems/set-matrix-zeroes/)
- [300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/)
- 字符串
- 链表
- [141. 环形链表](https://leetcode-cn.com/problems/linked-list-cycle/)
- [21. 合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/)
- [203. 移除链表元素](https://leetcode-cn.com/problems/remove-linked-list-elements/)
- [206. 反转链表](https://leetcode-cn.com/problems/reverse-linked-list/)
- [83. 删除排序链表中的重复元素](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/)
- [143. 重排链表](https://leetcode-cn.com/problems/reorder-list/)
- 栈/队列
- 树
- [144. 二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/)
- [94. 二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/)
- [145. 二叉树的后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/)
- [102. 二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/)
- [104. 二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/)
- [101. 对称二叉树](https://leetcode-cn.com/problems/symmetric-tree/)
- [226. 翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/)
- [112. 路径总和](https://leetcode-cn.com/problems/path-sum/)
- [700. 二叉搜索树中的搜索](https://leetcode-cn.com/problems/search-in-a-binary-search-tree/)
- [701. 二叉搜索树中的插入操作](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/)
- [98. 验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree/)
- [653. 两数之和 IV - 输入 BST](https://leetcode-cn.com/problems/two-sum-iv-input-is-a-bst/)
- [235. 二叉搜索树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/)
数组
350. 两个数组的交集 II
给你两个整数数组
nums1
和nums2
,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
*进阶*:
- 如果给定的数组已经排好序呢?你将如何优化你的算法?
- 如果
nums1
的大小比nums2
小,哪种方法更优? - 如果
nums2
的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
实现:
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
// 先对两个数组进行排序
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
// 定义返回结果
vector<int> result;
// i表示nums1下标,j表示nums2下标
int i = 0, j = 0;
// 遍历,查找nums[i] == nums[j]的数字
while (i < nums1.size() && j < nums2.size()) {
if (nums1[i] < nums2[j]) { //没找到,谁小,则移动谁的下标
++i;
} else if (nums1[i] > nums2[j]) {
++j;
} else { //找到了,直接加入到结果列表中
result.emplace_back(nums1[i]);
++i;
++j;
}
}
return result;
}
};
566. 重塑矩阵
在 MATLAB 中,有一个非常有用的函数
reshape
,它可以将一个m x n
矩阵重塑为另一个大小不同(r x c
)的新矩阵,但保留其原始数据。
给你一个由二维数组
mat
表示的m x n
矩阵,以及两个正整数r
和c
,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。
如果具有给定参数的
reshape
操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。
示例 1:
输入:mat = [[1,2],[3,4]], r = 1, c = 4
输出:[[1,2,3,4]]
示例 2:
输入:mat = [[1,2],[3,4]], r = 2, c = 4
输出:[[1,2],[3,4]]
提示:
m == mat.length
n == mat[i].length
1 <= m, n <= 100
-1000 <= mat[i][j] <= 1000
1 <= r, c <= 300
实现:
class Solution {
public:
vector<vector<int>> matrixReshape(vector<vector<int>>& mat, int r, int c) {
int m = mat.size(), n = mat[0].size();
// 若矩阵存储元素数量不一致,返回原数组
if (m * n != r * c) {
return mat;
}
vector<vector<int>> result(r, vector<int>(c));
// i为矩阵第几个数(按行计数),i / c表示在第几行, i % c表示在行的第几列
for (int i = 0; i != m * n; ++i) {
result[i / c][i % c] = mat[i / n][i % n];
}
// for (int i = 0, j = 0, x = 0, y = 0; i < m && x < r;) {
// while (j < n && y < c ) {
// result[x][y++] = mat[i][j++];
// }
// if (j == n) {
// j = 0;
// ++i;
// }
// if (y == c) {
// y = 0;
// ++x;
// }
// }
return result;
}
};
118. 杨辉三角
给定一个非负整数 *
numRows
,*生成「杨辉三角」的前numRows
行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
示例 2:
输入: numRows = 1
输出: [[1]]
提示:
1 <= numRows <= 30
实现:
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> result(numRows);
for (int i = 0; i != numRows; ++i) {
// 初始化每一行的数列
vector<int> tmp(i + 1, 1);
// 从第二列开始计算,到倒数第二列停止
for (int j = 1; j < i; ++j) {
// 计算该列数字
tmp[j] = result[i - 1][j] + result[i - 1][j - 1];
}
result[i] = tmp;
}
return result;
}
};
36. 有效的数独
请你判断一个
9 x 9
的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
- 数字
1-9
在每一行只能出现一次。- 数字
1-9
在每一列只能出现一次。- 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)
注意:
- 一个有效的数独(部分已被填充)不一定是可解的。
- 只需要根据以上规则,验证已经填入的数字是否有效即可。
- 空白格用
'.'
表示。
示例 1:
输入:board =
[["5","3",".",".","7",".",".",".","."]
,["6",".",".","1","9","5",".",".","."]
,[".","9","8",".",".",".",".","6","."]
,["8",".",".",".","6",".",".",".","3"]
,["4",".",".","8",".","3",".",".","1"]
,["7",".",".",".","2",".",".",".","6"]
,[".","6",".",".",".",".","2","8","."]
,[".",".",".","4","1","9",".",".","5"]
,[".",".",".",".","8",".",".","7","9"]]
输出:true
示例 2:
输入:board =
[["8","3",".",".","7",".",".",".","."]
,["6",".",".","1","9","5",".",".","."]
,[".","9","8",".",".",".",".","6","."]
,["8",".",".",".","6",".",".",".","3"]
,["4",".",".","8",".","3",".",".","1"]
,["7",".",".",".","2",".",".",".","6"]
,[".","6",".",".",".",".","2","8","."]
,[".",".",".","4","1","9",".",".","5"]
,[".",".",".",".","8",".",".","7","9"]]
输出:false
解释:除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
提示:
board.length == 9
board[i].length == 9
board[i][j]
是一位数字(1-9
)或者'.'
实现:
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
// 记录每行数字出现次数
vector<vector<int>> row(9, vector<int>(9));
// 记录每行数字出现次数
vector<vector<int>> col(9, vector<int>(9));
// 记录每个3x3区域数字出现次数
vector<vector<vector<int>>> subboxes(3, vector<vector<int>>(3, vector<int>(9)));
for (int i = 0; i != 9; ++i) {
for (int j = 0; j != 9; ++j) {
char c = board[i][j];
if (c != '.') {
int num = board[i][j] - '1';
++row[i][num];
++col[j][num];
++subboxes[i / 3][j / 3][num];
// 判断是否出现重复数字
if (row[i][num] > 1 || col[j][num] > 1 || subboxes[i / 3][j / 3][num] > 1) {
return false;
}
}
}
}
return true;
}
};
73. 矩阵置零
给定一个
*m* x *n*
的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法**。**
示例 1:
输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]
示例 2:
输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]
提示:
m == matrix.length
n == matrix[0].length
1 <= m, n <= 200
-231 <= matrix[i][j] <= 231 - 1
进阶:
- 一个直观的解决方案是使用
O(*m**n*)
的额外空间,但这并不是一个好的解决方案。 - 一个简单的改进方案是使用
O(*m* + *n*)
的额外空间,但这仍然不是最好的解决方案。 - 你能想出一个仅使用常量空间的解决方案吗?
实现:
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
// 使用两个数组记录改行或该列是否应该全置为0
vector<int> row(m, 0);
vector<int> col(n, 0);
for (int i = 0; i != m; ++i) {
for (int j = 0; j != n; ++j) {
// 若二维数组中出现0,则将改行和该列标记为需要置为0
if (matrix[i][j] == 0) {
row[i] = 1;
col[j] = 1;
}
}
}
// 根据记录,将二维数组中对应位置置为0
for (int i = 0; i != m; ++i) {
for (int j = 0; j != n; ++j) {
if (col[j] == 1 || row[i] == 1) {
matrix[i][j] = 0;
}
}
}
}
};
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
// 使用两个bool变量记录第一行或第一列是否该置为0,然后使用第一行和第一列记录改行或该列是否该置为0
bool flagRow = false, falgCol = false;
// 判断第一列是否该置为0
for (int i = 0; i != m; ++i) {
if (!matrix[i][0]) {
cout << "i: " << i << endl;
falgCol = true;
}
}
// 判断第一行是否该置为0
for (int i = 0; i != n; ++i) {
if (!matrix[0][i]) {
flagRow = true;
}
}
// cout << falgCol << " " << flagRow << endl;
for (int i = 1; i != m; ++i) {
for (int j = 1; j != n; ++j) {
// 如果数组中存在0,则将数组的第一行和第一列置为0
if (!matrix[i][j]) {
matrix[i][0] = matrix[0][j] = 0;
}
}
}
// 判断数组的第一行和第一列对应值是否为0,确定是否将对应位置的值置为0
for (int i = 1; i != m; ++i) {
for (int j = 1; j != n; ++j) {
if (!matrix[i][0] || !matrix[0][j]) {
matrix[i][j] = 0;
}
}
}
// 根据记录判断是否将第一列全置为0
for (int i = 0; i != m; ++i) {
if (falgCol) {
matrix[i][0] = 0;
}
}
// 根据记录判断是否将第一行全置为0
for (int i = 0; i != n; ++i) {
if (flagRow) {
matrix[0][i] = 0;
}
}
}
};
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
// 使用一个bool变量记录第一列是否该置为0
bool flagCol = false;
for (int i = 0; i != m; ++i) {
// 判断第一列是否该置为0
if (!matrix[i][0]) {
flagCol = true;
}
// 从第二列开始
for (int j = 1; j != n; ++j) {
// 使用第一行和第一列记录改行和该列是否该置为0
if (!matrix[i][j]) {
matrix[i][0] = matrix [0][j] = 0;
}
}
}
// 从最后一列开始遍历,避免修改到第一行的记录
// 判断数组的第一行和第一列对应值是否为0,确定是否将对应位置的值置为0
for (int i = m - 1; i >= 0; --i) {
for (int j = 1; j != n; ++j) {
if (!matrix[i][0] || !matrix [0][j]) {
matrix[i][j] = 0;
}
}
}
// 根据记录判断是否将第一列全置为0
if (flagCol) {
for (int i = 0; i != m; ++i) {
matrix[i][0] = 0;
}
}
}
};
300. 最长递增子序列
给你一个整数数组
nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,
[3,6,2,7]
是数组[0,3,1,6,2,2,7]
的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104
进阶:
- 你能将算法的时间复杂度降低到
O(n log(n))
吗?
实现:
动态规划:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
int count = 1;
// 使用数组记录每一个位置的最长序列,初始化都为1
vector<int> dp(n, 1);
// 下标从1开始遍历
for (int i = 1; i < n; ++i) {
// 遍历之前的每一个位置
for (int j = i - 1; j >= 0; --j) {
// 若当前下标数比之前下标数大,则为一个升序子序列,记录当前位置最大长度子序列长度
if (nums[i] > nums[j]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
// 记录最终的最大子序列长度
count = max(dp[i], count);
}
return count;
}
};
二分法:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
// 使用dp维护一个当前最长的升序子序列
vector<int> dp(n, 0);
int count = 1;
dp[0] = nums[0];
for (int i = 1; i != n; ++i) {
// 如果当前数字大于当前升序子序列的最后一个值,则直接插入
if (nums[i] > dp[count - 1]) {
dp[count++] = nums[i];
} else { // 如果不是则找到与该数字相近的数字进行替换, 如当前为2,要插入1,则将2替换为1, 如当前为2、5,要插入3,则将5替换为3
int left = 0, right = count - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[i] > dp[mid]) {
left = mid + 1;
} else {
right = mid;
}
}
dp[left] = nums[i];
}
}
return count;
}
};
字符串
387. 字符串中的第一个唯一字符
给定一个字符串
s
,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回-1
。
示例 1:
输入: s = "leetcode"
输出: 0
示例 2:
输入: s = "loveleetcode"
输出: 2
示例 3:
输入: s = "aabb"
输出: -1
提示:
1 <= s.length <= 105
s
只包含小写字母
class Solution {
public:
int firstUniqChar(string s) {
// 使用数组记录s中所有字母出现次数
vector<int> tmp(26, 0);
int n = s.size();
// 记录出现次数
for (int i = 0; i != n; ++i) {
tmp[s[i] - 'a']++;
}
// 返回第一个出现一次的字母的下标
for (int i = 0; i != n; ++i) {
if (tmp[s[i] - 'a'] == 1) {
return i;
}
}
return -1;
}
};
383. 赎金信
给你两个字符串:
ransomNote
和magazine
,判断ransomNote
能不能由magazine
里面的字符构成。
如果可以,返回
true
;否则返回false
。
magazine
中的每个字符只能在ransomNote
中使用一次。
示例 1:
输入:ransomNote = "a", magazine = "b"
输出:false
示例 2:
输入:ransomNote = "aa", magazine = "ab"
输出:false
示例 3:
输入:ransomNote = "aa", magazine = "aab"
输出:true
提示:
1 <= ransomNote.length, magazine.length <= 105
ransomNote
和magazine
由小写英文字母组成
实现:
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
// 使用tmp记录magazine中每个字母出现次数
vector<int> tmp(26);
for (int i = 0; i != magazine.size(); ++i) {
++tmp[magazine[i] - 'a'];
}
// 从tmp中减去ransomNote中出现的字母,若tmp中某个字母出现次数为-1,则说明不能使用magazine中的字母构成ransomNote
for (int i = 0; i != ransomNote.size(); ++i) {
if (--tmp[ransomNote[i] - 'a'] < 0) {
return false;
}
}
return true;
}
};
242. 有效的字母异位词
给定两个字符串
*s*
和*t*
,编写一个函数来判断*t*
是否是*s*
的字母异位词。
**注意:**若
*s*
和*t*
中每个字符出现的次数都相同,则称*s*
和*t*
互为字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
提示:
1 <= s.length, t.length <= 5 * 104
s
和t
仅包含小写字母
进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
实现:
class Solution {
public:
bool isAnagram(string s, string t) {
// 若大小不相等,则不可能是异位词
if (s.size() != t.size()) {
return false;
}
// 使用异或判断,若s、t包含相同数量的字母,则最后结果为0
int result = 0;
for (int i = 0; i != s.size(); ++i) {
result ^= s[i];
result ^= t[i];
}
return result == 0;
}
};
另一种思路:排序然后判断是否相等
链表
141. 环形链表
给你一个链表的头节点
head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪
next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回
true
。 否则,返回false
。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
- 链表中节点的数目范围是
[0, 104]
-105 <= Node.val <= 105
pos
为-1
或者链表中的一个 有效索引 。
**进阶:**你能用 O(1)
(即,常量)内存解决此问题吗?
实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if (head) {
// 定义快慢指针,慢指针每次走一步,快指针每次走两步
ListNode *slow = head;
ListNode *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
// 如果慢指针赶上快指针则说明有环
if (slow == fast) {
// 返回环的第一个元素 head,从head出发,知道与fast相遇则为环的第一个元素
while (head != fast) {
head = head->next;
fast = fast->next;
}
return true;
}
}
}
return false;
}
};
21. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
提示:
- 两个链表的节点数目范围是
[0, 50]
-100 <= Node.val <= 100
l1
和l2
均按 非递减顺序 排列
实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
// 生成新节点
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* newList = new ListNode();
ListNode* list1Head = list1;
ListNode* list2Head = list2;
ListNode* newListHead = newList;
while (list1Head || list2Head) {
int list1Val = list1Head ? list1Head->val : 101;
int list2Val = list2Head ? list2Head->val : 101;
if (list1Val < list2Val) {
list1Head = list1Head->next;
newListHead->next = new ListNode(list1Val);
} else {
list2Head = list2Head->next;
newListHead->next = new ListNode(list2Val);
}
newListHead = newListHead->next;
}
return newList->next;
}
};
class Solution {
public:
// 不生成,直接使用list1和list2的节点
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* newList = new ListNode();
ListNode* newListHead = newList;
while (list1 && list2) {
if (list1->val < list2->val) {
newListHead->next = list1;
list1 = list1->next;
} else {
newListHead->next = list2;
list2 = list2->next;
}
}
newListHead->next = list1 ? list1 : list2;
return newList->next;
}
};
203. 移除链表元素
给你一个链表的头节点
head
和一个整数val
,请你删除链表中所有满足Node.val == val
的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1
输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]
提示:
- 列表中的节点数目在范围
[0, 104]
内 1 <= Node.val <= 50
0 <= val <= 50
实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 定义一个新的虚拟结点,令其指向头结点
ListNode* newHead = new ListNode();
newHead->next = head;
ListNode* tmp = newHead;
// 遍历,并将节点值等于val的节点都删除
while (tmp->next) {
if (tmp->next->val = val) {
tmp->next = tmp->next->next;
} else {
tmp = tmp->next;
}
}
// 返回虚拟节点所指向的头结点
return newHead->next;
}
};
206. 反转链表
给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000]
-5000 <= Node.val <= 5000
**进阶:**链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head) {
return head;
}
// 定义当前节点的前一个节点
ListNode* preNode = nullptr;
while (head->next) {
// 保存当前节点的下一节点
ListNode* tmpNode = head->next;
// 将当前节点的next指向钱一个节点
head->next = preNode;
// 将前一节点赋值为当前节点,将当前节点赋值为保存的下一节点
preNode = head;
head = tmpNode;
}
head->next = preNode;
return head;
}
};
class Solution {
public:
// 递归反转
ListNode* reverseList(ListNode* head) {
// 到达最后一个节点或者链表为空
if (!head || !head->next) {
return head;
}
// 递归返回最后一个节点,即反转后的头结点
ListNode* newHead = reverseList(head->next);
// 将当前节点的下一个节点的next赋值为当前节点,即反转
head->next->next = head;
// 将当前节点的next赋值为nullptr,即最终为头结点指向nullptr
head->next = nullptr;
return newHead;
}
};
83. 删除排序链表中的重复元素
给定一个已排序的链表的头
head
, 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
示例 1:
输入:head = [1,1,2]
输出:[1,2]
示例 2:
输入:head = [1,1,2,3,3]
输出:[1,2,3]
提示:
- 链表中节点数目在范围
[0, 300]
内 -100 <= Node.val <= 100
- 题目数据保证链表已经按升序 排列
实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) {
return head;
}
ListNode* newHead = head;
while (head->next) {
// 判断当前节点是否与下一节点值相等,若是,则删除下一节点,不是的话将当前节点复制为下一节点
if (head->next->val == head->val) {
head->next = head->next->next;
} else {
head = head->next;
}
}
return newHead;
}
};
143. 重排链表
给定一个单链表 L
的头节点 head
,单链表 L
表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
输入:head = [1,2,3,4]
输出:[1,4,2,3]
示例 2:
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]
提示:
- 链表的长度范围为
[1, 5 * 104]
1 <= node.val <= 1000
实现:
数组:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
// 使用一个vecotor保存所有节点,对数组进行重排
void reorderList(ListNode* head) {
vector<ListNode*> vec;
ListNode* tmp = head;
while (tmp->next) {
vec.push_back(tmp->next);
tmp = tmp->next;
}
tmp = head;
int n = vec.size() - 1;
int i = 0, j = n;
for (; i < j; ++i, --j) {
tmp->next = vec[j];
vec[j]->next = vec[i];
tmp = vec[i];
}
if (i == j) {
tmp->next = vec[i];
tmp->next->next = nullptr;
} else {
tmp->next = nullptr;
}
}
};
寻找中间点 + 后续节点逆序 + 合并:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
void reorderList(ListNode* head) {
// 快慢指针寻找中间节点
ListNode* mid = head;
ListNode* fast = head;
while(fast && fast->next) {
mid = mid->next;
fast = fast->next->next;
}
// 后半段逆序
ListNode* lastHead = nullptr;
while (mid) {
ListNode* tmp = mid->next;
mid->next = lastHead;
lastHead = mid;
mid = tmp;
}
// 重排
ListNode* node = head;
while (lastHead && lastHead->next) {
ListNode* tmp = lastHead->next;
lastHead->next = node->next;
node->next = lastHead;
node = lastHead->next;
lastHead = tmp;
}
}
};
栈/队列
20. 有效的括号
难度简单3005
给定一个只包括
'('
,')'
,'{'
,'}'
,'['
,']'
的字符串s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
示例 4:
输入:s = "([)]"
输出:false
示例 5:
输入:s = "{[]}"
输出:true
提示:
1 <= s.length <= 104
s
仅由括号'()[]{}'
组成
实现:
class Solution {
public:
bool isCorrespond(char c1, char c2) {
switch (c1) {
case ')':
return c2 == '(';
case ']':
return c2 == '[';
case '{':
return c2 == '[';
default:
return false;
}
return false;
}
bool isValid(string s) {
int n = s.size();
// 入栈
stack<char> st;
st.push(s[n - 1]);
for (int i = n - 2; i >= 0; --i) {
// 碰到对应的符号则出栈
if (!st.empty() && isCorrespond(st.top(), s[i])) {
st.pop();
} else {
st.push(s[i]);
}
}
// 栈为空则满足要求
return st.empty();
}
};
232. 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(
push
、pop
、peek
、empty
):
实现
MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回true
;否则,返回false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有
push to top
,peek/pop from top
,size
, 和is empty
操作是合法的。 - 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
示例 1:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
1 <= x <= 9
- 最多调用
100
次push
、pop
、peek
和empty
- 假设所有操作都是有效的 (例如,一个空的队列不会调用
pop
或者peek
操作)
进阶:
- 你能否实现每个操作均摊时间复杂度为
O(1)
的队列?换句话说,执行n
个操作的总时间复杂度为O(n)
,即使其中一个操作可能花费较长时间。
实现:
class MyQueue {
public:
MyQueue() {
}
// 插入时使用两个栈操作,保证插入的数据在栈底
void push(int x) {
stack<int> stForward;
while (!stReverse.empty()) {
stForward.push(stReverse.top());
stReverse.pop();
}
stForward.push(x);
while (!stForward.empty()) {
stReverse.push(stForward.top());
stForward.pop();
}
}
// 直接返回并删除栈顶元素
int pop() {
int val = stReverse.top();
stReverse.pop();
return val;
}
// 直接返回栈顶元素
int peek() {
return stReverse.top();
}
bool empty() {
return stReverse.empty();
}
private:
stack<int> stReverse;
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
*/
树
144. 二叉树的前序遍历
给你二叉树的根节点
root
,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
示例 4:
输入:root = [1,2]
输出:[1,2]
示例 5:
输入:root = [1,null,2]
输出:[1,2]
提示:
- 树中节点数目在范围
[0, 100]
内 -100 <= Node.val <= 100
**进阶:**递归算法很简单,你可以通过迭代算法完成吗?
实现:
递归:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> result;
// 递归,对于一个节点,先输出该节点值,然后输出器左子节点值,最后输出其右子节点值
vector<int> preorderTraversal(TreeNode* root) {
if (root) {
result.push_back(root->val);
preorderTraversal(root->left);
preorderTraversal(root->right);
}
return result;
}
};
迭代:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
// 使用栈保存节点
stack<TreeNode*> st;
TreeNode* node = root;
while (node || !st.empty()) {
// 对于一个节点,先输出其值,然后将其加入到栈中
while (node) {
result.emplace_back(node->val);
st.push(node);
node = node->left;
}
// 弹出最后一个节点,并将node切换到其右子节点
node = st.top();
st.pop();
node = node->right;
}
return result;
}
};
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
if (!root) {
return result;
}
// 使用栈保存节点
stack<TreeNode*> st;
st.push(root);
while (!st.empty()) {
// 取出栈顶元素,并记录其值
TreeNode* node = st.top();
st.pop();
result.emplace_back(node->val);
// 将其右子节点加入到栈中
if (node->right) {
st.push(node->right);
}
// 将其左子节点加入到栈中,后加左子树是为了保证此节点的左子节点在栈顶,即下一轮遍历的节点
if (node->left) {
st.push(node->left);
}
}
return result;
}
};
Morris思路:
新建临时节点,令该节点为 root;
如果当前节点的左子节点为空,则遍历当前节点的右子节点;
如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点;
如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点,当前节点更新为当前节点的左子节点。
如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。当前节点更新为当前节点的右子节点。
重复步骤 2 和步骤 3,直到遍历结束。
先序遍历:
- 建立连接的时候添加当前节点的值
- 当前节点的左子节点为空时添加当前节点的值
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
TreeNode* cur1 = root;
TreeNode* cur2 = nullptr;
while (cur1) {
cur2 = cur1->left;
if (cur2) {
// cur2 不为空时,找到该节点下最右边的叶子节点,前提是没有与cur1连接过
while (cur2->right && cur2->right != cur1) {
cur2 = cur2->right;
}
// 如果未与cur1连接则建立连接,并继续处理cur1->left,开始下一轮循环
if (!cur2->right) {
result.emplace_back(cur1->val);
cur2->right = cur1;
cur1 = cur1->left;
continue;
} else { // 已连接则断开,然后继续后处理该节点的右子树
cur2->right = nullptr;
}
} else {
result.emplace_back(cur1->val);
}
//result.emplace_back(cur2->val);
// 继续处理当前节点的右子树
cur1 = cur1->right;
}
return result;
}
};
94. 二叉树的中序遍历
给定一个二叉树的根节点
root
,返回它的 中序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
示例 4:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fEOP8Ns9-1648025139920)(https://assets.leetcode.com/uploads/2020/09/15/inorder_5.jpg)]
输入:root = [1,2]
输出:[2,1]
示例 5:
输入:root = [1,null,2]
输出:[1,2]
提示:
- 树中节点数目在范围
[0, 100]
内 -100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
实现:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
if (!root) {
return res;
}
// 使用栈保存节点
stack<TreeNode*> st;
TreeNode* node = root;
while (node || !st.empty()) {
// 先处理当前节点的左子节点,并依次加入栈中
while (node) {
st.push(node);
node = node->left;
}
// 弹出栈顶元素, 将记录其值,并将node赋值为其右子节点
node = st.top();
st.pop();
res.emplace_back(node->val);
node = node->right;
}
return res;
}
};
Morris思路:
新建临时节点,令该节点为 root;
如果当前节点的左子节点为空,则遍历当前节点的右子节点;
如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点;
如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点,当前节点更新为当前节点的左子节点。
如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。当前节点更新为当前节点的右子节点。
重复步骤 2 和步骤 3,直到遍历结束。
中序遍历:
- 当前节点的左子节点为空时添加当前节点的值,
- 断开时添加左子树最右边的叶子节点的值
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
if (!root) {
return res;
}
TreeNode* cur1 = root;
TreeNode* cur2 = nullptr;
while (cur1) {
cur2 = cur1->left;
if (cur2) {
while (cur2->right && cur2->right != cur1) {
cur2 = cur2->right;
}
if (cur2->right) {
res.emplace_back(cur2->right->val);
cur2->right = nullptr;
} else {
cur2->right = cur1;
cur1 = cur1->left;
continue;
}
} else {
res.emplace_back(cur1->val);
}
cur1 = cur1->right;
}
return res;
}
};
145. 二叉树的后序遍历
难度简单767
给你一棵二叉树的根节点 root
,返回其节点值的 后序遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[3,2,1]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
提示:
- 树中节点的数目在范围
[0, 100]
内 -100 <= Node.val <= 100
**进阶:**递归算法很简单,你可以通过迭代算法完成吗?
实现:
迭代:
思路1:记录其处理过的上一个节点,若记录节点为当前节点的右子节点或其右子节点为空,则不再处理其右子树
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if (!root) {
return res;
}
// 使用栈保存节点
stack<TreeNode*> st;
TreeNode* node = root;
// 使用prev记录上一个处理的节点
TreeNode* prev = nullptr;
while (node || !st.empty()) {
while (node) {
st.push(node);
node = node->left;
}
node = st.top();
// 若记录节点为当前节点的右子节点或其右子节点为空,则记录该节点值
if (!node->right || node->right == prev) {
res.emplace_back(node->val);
st.pop();
prev = node;
node = nullptr;
continue;
}
node = node->right;
}
return res;
}
};
参照先序遍历的做法,对于一个节点,先保存其值,然后保存右子节点的值,最后保存左子节点的值,保存方法都为插入到数组头部
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if (!root) {
return res;
}
stack<TreeNode*> st;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
res.insert(res.begin(), node->val);
if (node->left) {
st.push(node->left);
}
if (node->right) {
st.push(node->right);
}
}
return res;
}
};
Morris思路:
新建临时节点,令该节点为 root;
如果当前节点的左子节点为空,则遍历当前节点的右子节点;
如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点;
如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点,当前节点更新为当前节点的左子节点。
如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。当前节点更新为当前节点的右子节点。
重复步骤 2 和步骤 3,直到遍历结束。
后序遍历:
- 断开连接时,倒序输出从当前节点的左子节点到其前驱节点这条路径上的所有节点。
- 最后倒叙输出根节点到其最右边叶子节点的路径
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
// 倒叙输出当前节点到其最右叶子节点的路径
void addPath(vector<int>& res, TreeNode* node) {
int count = 0;
while (node) {
++count;
res.emplace_back(node->val);
node = node->right;
}
reverse(res.end() - count, res.end());
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if (!root) {
return res;
}
TreeNode* cur1 = root;
TreeNode* cur2 = nullptr;
while (cur1) {
cur2 = cur1->left;
if (cur2) {
while (cur2->right && cur2->right != cur1) {
cur2 = cur2->right;
}
if (cur2->right) {
cur2->right = nullptr;
addPath(res, cur1->left);
} else {
cur2->right = cur1;
cur1 = cur1->left;
continue;
}
}
cur1 = cur1->right;
}
addPath(res, root);
return res;
}
};
102. 二叉树的层序遍历
给你二叉树的根节点
root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
提示:
- 树中节点数目在范围
[0, 2000]
内 -1000 <= Node.val <= 1000
实现:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (!root) {
return res;
}
// 使用que记录当前层的节点
queue<TreeNode*> que;
// 使用queTmp记录下一层的节点
queue<TreeNode*> queTmp;
que.push(root);
vector<int> vecTmp;
while (!que.empty()) {
TreeNode* node = que.front();
que.pop();
vecTmp.push_back(node->val);
if (node->left) {
queTmp.push(node->left);
}
if (node->right) {
queTmp.push(node->right);
}
// 当前层遍历完则继续下一层
if (que.empty()) {
que.swap(queTmp);
res.push_back(vecTmp);
vector<int>().swap(vecTmp);
}
}
return res;
}
};
优化:记录每一层的节点数量,替换之前使用两个队列
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (!root) {
return res;
}
queue<TreeNode*> que;
// 使用size记录每一层节点数量
int size = 1;
que.push(root);
while (!que.empty()) {
vector<int> vecTmp;
int count = 0;
for (int i = 0; i != size; ++i) {
TreeNode* node = que.front();
que.pop();
vecTmp.push_back(node->val);
if (node->left) {
que.push(node->left);
++count;
}
if (node->right) {
que.push(node->right);
++count;
}
}
size = count;
res.push_back(vecTmp);
}
return res;
}
};
104. 二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
实现:
广度优先:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int maxDepth(TreeNode* root) {
if (!root) {
return 0;
}
// 记录每一层数量,遍历完进入下一层
int size = 1;
int depth = 0;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int count = 0;
for (int i = 0; i != size; ++i) {
TreeNode* node = que.front();
que.pop();
if (node->left) {
que.push(node->left);
++count;
}
if (node->right) {
que.push(node->right);
++count;
}
}
size = count;
++depth;
}
return depth;
}
};
深度优先:
class Solution {
public:
int maxDepth(TreeNode* root) {
return root ? max(maxDepth(root->left), maxDepth(root->right)) + 1 : 0;
}
};
101. 对称二叉树
给你一个二叉树的根节点
root
, 检查它是否轴对称。
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
提示:
- 树中节点数目在范围
[1, 1000]
内 -100 <= Node.val <= 100
**进阶:**你可以运用递归和迭代两种方法解决这个问题吗?
实现:
递归:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool dfs(TreeNode* node1, TreeNode* node2) {
if (!node1 && !node2) {
return true;
}
if (node1 && node2 && node1->val == node2->val) {
return dfs(node1->left, node2->right) && dfs(node1->right, node2->left);
} else {
return false;
}
}
bool isSymmetric(TreeNode* root) {
if (!root) {
return false;
}
return dfs(root->left, root->right);
}
};
迭代:
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (!root) {
return false;
}
queue<TreeNode*> que;
// 使用队列保存左右子树的节点
que.push(root);
que.push(root);
while (!que.empty()) {
TreeNode* node1 = que.front();
que.pop();
TreeNode* node2 = que.front();
que.pop();
if (!node1 && !node2) {
continue;
}
if (!node1 || !node2 || node1->val != node2->val) {
return false;
}
// 将需要比较的两个子节点以此放入
que.push(node1->left);
que.push(node2->right);
que.push(node2->left);
que.push(node1->right);
}
return true;
}
};
226. 翻转二叉树
给你一棵二叉树的根节点
root
,翻转这棵二叉树,并返回其根节点。
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
示例 2:
输入:root = [2,1,3]
输出:[2,3,1]
示例 3:
输入:root = []
输出:[]
提示:
- 树中节点数目范围在
[0, 100]
内 -100 <= Node.val <= 100
实现:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (!root) {
return root;
}
// 对于一个节点,先翻转左子树,然后翻转右子树
TreeNode* left = invertTree(root->left);
TreeNode* right = invertTree(root->right);
// 交换该节点的左右子节点
root->left = right;
root->right = left;
return root;
}
};
112. 路径总和
给你二叉树的根节点
root
和一个表示目标和的整数targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和targetSum
。如果存在,返回true
;否则,返回false
。叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。
示例 2:
输入:root = [1,2,3], targetSum = 5
输出:false
解释:树中存在两条根节点到叶子节点的路径:
(1 --> 2): 和为 3
(1 --> 3): 和为 4
不存在 sum = 5 的根节点到叶子节点的路径。
示例 3:
输入:root = [], targetSum = 0
输出:false
解释:由于树是空的,所以不存在根节点到叶子节点的路径。
提示:
- 树中节点的数目在范围
[0, 5000]
内 -1000 <= Node.val <= 1000
-1000 <= targetSum <= 1000
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if (!root) {
return false;
}
// 如果该节点为叶子节点,则进行比较
if (!root->left && !root->right) {
return root->val == targetSum;
}
// 否则将targetSum减去当前节点值,继续遍历
return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
}
};
700. 二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点
root
和一个整数值val
。
你需要在 BST 中找到节点值等于
val
的节点。 返回以该节点为根的子树。 如果节点不存在,则返回null
。
示例 1:
输入:root = [4,2,7,1,3], val = 2
输出:[2,1,3]
Example 2:
输入:root = [4,2,7,1,3], val = 5
输出:[]
提示:
- 数中节点数在
[1, 5000]
范围内 1 <= Node.val <= 107
root
是二叉搜索树1 <= val <= 107
实现:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if (!root) {
return root;
}
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
TreeNode* node = que.front();
que.pop();
if (node->val == val) {
return node;
}
if (node->left && node->val > val) {
que.push(node->left);
}
if (node->right && node->val < val) {
que.push(node->right);
}
}
return nullptr;
}
};
701. 二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点
root
和要插入树中的值value
,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
示例 1:
输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
示例 2:
输入:root = [40,20,60,10,30,50,70], val = 25
输出:[40,20,60,10,30,50,70,null,null,25]
示例 3:
输入:root = [4,2,7,1,3,null,null,null,null,null,null], val = 5
输出:[4,2,7,1,3,5]
提示:
- 树中的节点数将在
[0, 104]
的范围内。 -108 <= Node.val <= 108
- 所有值
Node.val
是 独一无二 的。 -108 <= val <= 108
- 保证
val
在原始BST中不存在。
实现:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (!root) {
root = new TreeNode(val);
return root;
}
TreeNode* node = root;
while (1) {
if (node->val < val) {
if (!node->right) {
node->right = new TreeNode(val);
break;
}
node = node->right;
} else {
if (!node->left) {
node->left = new TreeNode(val);
break;
}
node = node->left;
}
}
return root;
}
};
98. 验证二叉搜索树
给你一个二叉树的根节点
root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3]
输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
- 树中节点数目范围在
[1, 104]
内 -2^31 <= Node.val <= 2^31 - 1
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool isValidBST(TreeNode* root) {
if (!root) {
return false;
}
queue<pair<TreeNode*, pair<long, long>>> que;
que.push(pair<TreeNode*, pair<long, long>>(root, pair<long, long>(LONG_MIN, LONG_MAX)));
while (!que.empty()) {
pair<TreeNode*, pair<long, long>> pa = que.front();
que.pop();
if (pa.first->left) {
if (pa.first->left->val >= pa.first->val || pa.first->left->val <= pa.second.first) {
return false;
}
que.push(pair<TreeNode*, pair<long, long>>(pa.first->left, pair<long, long>(pa.second.first, pa.first->val)));
}
if (pa.first->right) {
if (pa.first->right->val <= pa.first->val || pa.first->right->val >= pa.second.second) {
return false;
}
que.push(pair<TreeNode*, pair<long, long>>(pa.first->right, pair<long, long>(pa.first->val, pa.second.second)));
}
}
return true;
}
};
653. 两数之和 IV - 输入 BST
给定一个二叉搜索树
root
和一个目标结果k
,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回true
。
示例 1:
输入: root = [5,3,6,2,4,null,7], k = 9
输出: true
示例 2:
输入: root = [5,3,6,2,4,null,7], k = 28
输出: false
提示:
- 二叉树的节点个数的范围是
[1, 10^4]
. -10^4 <= Node.val <= 10^4
root
为二叉搜索树-10^5 <= k <= 10^5
实现:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool findTarget(TreeNode* root, int k) {
if (!root) {
return false;
}
unordered_set<int> us;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
TreeNode* node = que.front();
que.pop();
if (us.find(k - node->val) != us.end()) {
return true;
}
us.insert(node->val);
if (node->left) {
que.push(node->left);
}
if (node->right) {
que.push(node->right);
}
}
return false;
}
};
235. 二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
说明:
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉搜索树中。
实现:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode* node = root;
while (node) {
if (p->val < node->val && q->val < node->val) {
node = node->left;
} else if (p->val > node->val && q->val > node->val) {
node = node->right;
} else {
return node;
}
}
return root;
}
};