1.链表
在链表部分主要有节点查找,增加节点,删除节点,反转链表或者查找链表是否有环等问题,这些问题一般可以通过节点遍历,双指针,快慢双指针等解决。
1.1 合并k个已经排序好的链表
最难的一个问题是,合并k个已经排序好的链表,使得整个链表有序,这里面涉及的方法有归并排序和双指针。
其实这道题我们也可以两两比较啊,只要遍历链表数组,取出开头的两个链表,按照上述思路合并,然后新链表再与后一个继续合并,如此循环,知道全部合并完成。但是,这样太浪费时间了。既然都是归并排序的思想了,那我们可不可以直接归并的分治来做,而不是顺序遍历合并链表呢?答案是可以的!归并排序是什么?简单来说就是将一个数组每次划分成等长的两部分,对两部分进行排序即是子问题。对子问题继续划分,直到子问题只有1个元素。还原的时候呢,将每个子问题和它相邻的另一个子问题利用上述双指针的方式,1个与1个合并成2个,2个与2个合并成4个,因为这每个单独的子问题合并好的都是有序的,直到合并成原本长度的数组。对于这k个链表,就相当于上述合并阶段的k个子问题,需要划分为链表数量更少的子问题,直到每一组合并时是两两合并,然后继续往上合并,这个过程基于递归:
终止条件:左右区间大小相等或者左边大于右边。
返回值:已经排序好的链表。
本级任务:将区间划分成左右两个区间。
具体看代码:
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
ListNode *Merge2(ListNode *pHead1, ListNode *pHead2)
{
if(pHead1 == nullptr)
{
return pHead2;
}
if(pHead2 == nullptr)
{
return pHead1;
}
//加一个表头
ListNode* head = new ListNode(0);
ListNode* cur = head;
//两个链表都要不为空
while(pHead1 && pHead2){
//取较小值的节点
if(pHead1->val <= pHead2->val){
cur->next = pHead1;
//只移动取值的指针
pHead1 = pHead1->next;
}else{
cur->next = pHead2;
//只移动取值的指针
pHead2 = pHead2->next;
}
//指针后移
cur = cur->next;
}
//哪个链表还有剩,直接连在后面
if(pHead1)
cur->next = pHead1;
else
cur->next = pHead2;
return head->next;
}
ListNode* DivelistNode(vector<ListNode*>& lists, int left, int right)
{
if(left > right)
{
return nullptr;
}
if(left == right)
{
return lists[left];
}
int mid = (left + right)/2;
return Merge2(DivelistNode(lists, left, mid), DivelistNode(lists, mid+1, right));
}
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param lists ListNode类vector
* @return ListNode类
*/
ListNode* mergeKLists(vector<ListNode*>& lists) {
// write code here
return DivelistNode(lists, 0, lists.size() - 1);
}
};
1.2 两个链表相加
给出两个链表使其相加,在这个方法中首先我先算出两个链表的长度大小,然后用双指针遍历两个链表,在这个过程中创建一个栈用于存储值,先遍历长的链表并将值入栈然后遍历两个链表并将他们的值相加后入栈。最后创建一个头节点并在栈中取出值与10相余后插入到头节点的下一个节点中。 还有一种比较直接的方法是先反转链表然后再依此遍历两个链表并取出它们的值相加。
#include <stack>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head1 ListNode类
* @param head2 ListNode类
* @return ListNode类
*/
ListNode* addInList(ListNode* head1, ListNode* head2) {
// write code here
stack<int> s;
int len1 = 0, len2 = 0;
ListNode* p1 = head1;
ListNode* p2 = head2;
while(p1 != nullptr)
{
len1++;
p1= p1->next;
}
while(p2!= nullptr)
{
len2++;
p2= p2->next;
}
if(len1 < len2)
{
p1 = head2;
p2 = head1;
int temp = len1;
len1 = len2;
len2 = temp;
}else {
p1 = head1;
p2 = head2;
}
int i = 0;
while(i < len1 - len2)
{
i++;
s.push(p1->val);
p1 = p1->next;
}
while(i < len1)
{
i++;
s.push(p1->val + p2->val);
p1 = p1->next;
p2 = p2->next;
}
int count = 0;
int val;
ListNode* h = new ListNode(-1);
while(!s.empty())
{
int temp = s.top();
s.pop();
val = (temp + count) % 10;
ListNode* node = new ListNode(val);
count = (temp + count) /10;
node->next = h->next;
h->next = node;
}
if (count > 0)
{
ListNode* node = new ListNode(count);
node->next = h->next;
h->next = node;
}
return h->next;
}
};
1.3 排序两个链表
这道题要求排序无序的链表,可以采用归并并分治的方法,常规数组的归并排序是分治思想,即将数组从中间个元素开始划分,然后将划分后的子数组作为一个要排序的数组,再将排好序的两个子数组合并成一个完整的有序数组,因此采用的是递归。而链表中我们也可以用同样的方式,只需要找到中间个元素的前一个节点,将其断开,就可以将链表分成两个子链表,然后继续划分,直到最小,然后往上依次合并。
class Solution {
public:
//合并两段有序链表
ListNode* merge(ListNode* pHead1, ListNode* pHead2) {
//一个已经为空了,直接返回另一个
if(pHead1 == NULL)
return pHead2;
if(pHead2 == NULL)
return pHead1;
//加一个表头
ListNode* head = new ListNode(0);
ListNode* cur = head;
//两个链表都要不为空
while(pHead1 && pHead2){
//取较小值的节点
if(pHead1->val <= pHead2->val){
cur->next = pHead1;
//只移动取值的指针
pHead1 = pHead1->next;
}else{
cur->next = pHead2;
//只移动取值的指针
pHead2 = pHead2->next;
}
//指针后移
cur = cur->next;
}
//哪个链表还有剩,直接连在后面
if(pHead1)
cur->next = pHead1;
else
cur->next = pHead2;
//返回值去掉表头
return head->next;
}
ListNode* sortInList(ListNode* head) {
//链表为空或者只有一个元素,直接就是有序的
if(head == NULL || head->next == NULL)
return head;
ListNode* left = head;
ListNode* mid = head->next;
ListNode* right = head->next->next;
//右边的指针到达末尾时,中间的指针指向该段链表的中间
while(right != NULL && right->next != NULL){
left = left->next;
mid = mid->next;
right = right->next->next;
}
//左边指针指向左段的左右一个节点,从这里断开
left->next = NULL;
//分成两段排序,合并排好序的两段
return merge(sortInList(head), sortInList(mid));
}
};
1.4 排序链表
第一种方法,建立一个数组存储链表中的元素,在数组中利用快速排序算法求解,然后建立新链表赋值新的数组。
第二种方法:使用归并排序,将一个链表以中心不断断开,直到断开为空节点或只有一个断点,然后采用合并方式合并有序的链表,时间o(nlogn),空间o(n)。代码如下,具体步骤为
1.首先判断链表是否为空或者是否为只有一个元素,如果是直接返回有序数组。
2.利用快慢指针,当指针指向尾部的时候,慢指针刚好是一半,将链表从慢指针处断开,然后递归合并两个已排序的链表。编写一个函数合并两个已经排好序的链表。
class Solution {
public:
//合并两段有序链表
ListNode* merge(ListNode* pHead1, ListNode* pHead2) {
//一个已经为空了,直接返回另一个
if(pHead1 == NULL)
return pHead2;
if(pHead2 == NULL)
return pHead1;
//加一个表头
ListNode* head = new ListNode(0);
ListNode* cur = head;
//两个链表都要不为空
while(pHead1 && pHead2){
//取较小值的节点
if(pHead1->val <= pHead2->val){
cur->next = pHead1;
//只移动取值的指针
pHead1 = pHead1->next;
}else{
cur->next = pHead2;
//只移动取值的指针
pHead2 = pHead2->next;
}
//指针后移
cur = cur->next;
}
//哪个链表还有剩,直接连在后面
if(pHead1)
cur->next = pHead1;
else
cur->next = pHead2;
//返回值去掉表头
return head->next;
}
ListNode* sortInList(ListNode* head) {
//链表为空或者只有一个元素,直接就是有序的
if(head == NULL || head->next == NULL)
return head;
ListNode* left = head;
ListNode* mid = head->next;
ListNode* right = head->next->next;
//右边的指针到达末尾时,中间的指针指向该段链表的中间
while(right != NULL && right->next != NULL){
left = left->next;
mid = mid->next;
right = right->next->next;
}
//左边指针指向左段的左右一个节点,从这里断开
left->next = NULL;
//分成两段排序,合并排好序的两段
return merge(sortInList(head), sortInList(mid));
}
};
1.5 判断一个链表是否为回文结构
题目:给定一个链表,请判断该链表是否为回文结构。回文是指该字符串正序逆序完全一致。
解题思路一:可以先正序遍历链表并用一个容器保存,然后用另一个容器再次保存然后逆转,最后判断两个容器输出的顺序是否一致。
解题思路二:双指针法,先用一个数组保存正序遍历的元素,然后两个指针一个从前往后遍历,一个从后往前遍历,判断遍历的元素是否相同直到中间指针重合。
1.6 链表的基偶重排
题目:给定一个单链表,请设定一个函数,将链表的奇数位节点和偶数位节点分别放在一起,重排后输出。注意是节点的编号而非节点的数值。
解题思路:采用双指针,一个指向基数下标的元素,一个指向偶数下标的元素,然后将基数和偶数的元素拆开成两个链表然后将两个链表前后拼接起来。
1.7 删除链表中重复的元素
题目:给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。例如:
给出的链表为1→2→3→3→4→4→5 返回1→2→5.
给出的链表为1→1→1→2→3, 返回2→3.
解题思路一:由于链表是按顺序排列的,所以可以先在链表处加表头然后判断当前的链表的下两个节点的数值是否相同,如果相同则删掉这些节点。
2 二分查找
二分查找是针对已经排序的数组而言的,从中间值比较不断拆分数组,直到找到位置或者找不到,这个查找过程的时间复杂度为log n。
2.1 寻找峰值
题目:给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可。1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于2.假设 nums[-1] = nums[n] = −∞−∞ 3.对于所有有效的 i 都有 nums[i] != nums[i + 1] 4.你可以使用O(logN)的时间复杂度实现此问题吗?
解题思路:采用二分查找的思想,首先找中间的位置,如果左边的值大于中间的值,则去左边区间查找,如果右边的值大于中间的值则去右边区间查找,因为较大的数所在的区间一定能找到峰值.
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @return int整型
*/
int findPeakElement(vector<int>& nums) {
// write code here
int left = 0;
int right = nums.size() - 1;
int mid = 0;
while(left < right)
{
mid = (left + right)/2;
if(nums[mid] < nums[mid + 1])
{
left = mid + 1;
}else{
right = mid;
}
}
return left;
}
};
2.2 数组中的逆序对
题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007 数据范围: 对于 50%的数据, size≤104,对于 100% 的数据, size≤105,数组中所有数字的值满足0≤val≤109 要求:空间复杂度O(n),时间复杂度 O(nlogn)。 逆序对是左与其右边的任意数只要左边的数比右边的大就可以组成逆序对。
解题思路:因为我们在归并排序过程中会将数组划分成最小为1个元素的子数组,然后依次比较子数组的每个元素的大小,依次取出较小的一个合并成大的子数组这里我们也可以用相同的方法划分,划分之后相邻一个元素的子数组就可以根据大小统计逆序对,而不断往上合并的时候,因为已经排好序了,我们逆序对可以往上累计。我们主要有以下三个阶段。
- step 1: 划分阶段:将待划分区间从中点划分成两部分,两部分进入递归继续划分,直到子数组长度为1.
- step 2: 排序阶段:使用归并排序递归地处理子序列,同时统计逆序对,因为在归并排序中,我们会依次比较相邻两组子数组各个元素的大小,并累计遇到的逆序情况。而对排好序的两组,右边大于左边时,它大于了左边的所有子序列,基于这个性质我们可以不用每次加1来统计,减少运算次数。
- step 3: 合并阶段:将排好序的子序列合并,同时累加逆序对。
class Solution {
public:
int mod = 1000000007;
int mergeSort(int left, int right, vector<int>& data, vector<int>& temp){
//停止划分
if(left >= right)
return 0;
//取中间
int mid = (left + right) / 2;
//左右划分合并
int res = mergeSort(left, mid, data, temp) + mergeSort(mid + 1, right, data, temp);
//防止溢出
res %= mod;
int i = left, j = mid + 1;
for(int k = left; k <= right; k++)
temp[k] = data[k];
for(int k = left; k <= right; k++){
if(i == mid + 1)
data[k] = temp[j++];
else if(j == right + 1 || temp[i] <= temp[j])
data[k] = temp[i++];
//左边比右边大,答案增加
else{
data[k] = temp[j++];
//统计逆序对
res += mid - i + 1;
}
}
return res % mod;
}
int InversePairs(vector<int> data) {
int n = data.size();
vector<int> res(n);
return mergeSort(0, n - 1, data, res);
}
};
2.3 旋转数组的最小数字
题目:有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。
解题思路:由于旋转数组是由AB两个有序数组通过转换变成BA,因为A部分和B部分都是各自有序的,所以我们还是想用分治来试试,每次比较中间值,确认目标值(最小元素)所在的区间。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @return int整型
*/
int minNumberInRotateArray(vector<int>& nums) {
// write code here
int left = 0;
int right = nums.size() - 1;
int mid;
while(left < right)
{
mid = (left + right)/2;
if(nums[mid] > nums[right])
{
left = mid + 1;
}else if(nums[mid] == nums[right])
{
right -= 1;
}else {
right = mid;
}
}
return nums[left];
}
};
3 二叉树
3.1 前序遍历
先遍历根节点再遍历左节点最后再右节点
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
#include <stack>
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @return int整型vector
*/
// 递归解法
// void preorder(TreeNode* root, vector<int>& nums)
// {
// if (root == NULL)
// {
// return;
// }
// nums.push_back(root->val);
// preorder(root->left, nums);
// preorder(root->right, nums);
// }
// 非递归解法
void preorder(TreeNode* root, vector<int>& nums)
{
stack<TreeNode*> stack;
TreeNode *p = root;
while(p || !stack.empty())
{
if(p)
{
nums.push_back(p->val);
stack.push(p);
p = p->left;
}else{
p = stack.top();
stack.pop();
p = p->right;
}
}
}
vector<int> preorderTraversal(TreeNode* root) {
// write code here
vector<int> nums;
preorder(root, nums);
return nums;
}
};
3.2 中序遍历
先遍历左节点再到根节点最后到右节点。
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
#include <cstddef>
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @return int整型vector
*/
//递归遍历
void inorder(TreeNode* root, vector<int>& res)
{
if (root == NULL)
{
return;
}
inorder(root->left, res);
res.push_back(root->val);
inorder(root->right, res);
}
// 非递归遍历
void inorder_1(TreeNode* root, vector<int>& res)
{
stack<TreeNode*> temp;
TreeNode* p = root;
while(p || !temp.empty())
{
if(p)
{
temp.push(p);
p = p->left;
}else {
p = temp.top();
res.push_back(p->val);
temp.pop();
p = p->right;
}
}
}
vector<int> inorderTraversal(TreeNode* root) {
// write code here
vector<int> res;
inorder_1(root, res);
return res;
}
};
3.3 后序遍历
后序遍历的节点顺序为左节点再右节点,再到根节点。我们非递归遍历可以模仿中序遍历,先一路向左遍历,直到没有左孩子,此时最左边的节点为根节点,它需要右节点为空或者右节点被访问过才可以被访问。依此类推遍历整棵树。
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
#include <stack>
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @return int整型vector
*/
// 递归遍历
void postorder(TreeNode* root, vector<int>& res)
{
if(root == nullptr)
{
return;
}
postorder(root->left, res);
postorder(root->right, res);
res.push_back(root->val);
}
// 非递归遍历
void postorder_1(TreeNode* root, vector<int>& res)
{
stack<TreeNode*> stack;
TreeNode* pre = NULL;
while (root != NULL || !stack.empty()) {
while (root != NULL) {
stack.push(root);
root = root->left;
}
TreeNode* node = stack.top();
stack.pop();
if(node->right == NULL || pre == node->right)
{
res.push_back(node->val);
pre = node;
}else{
stack.push(node);
root = node->right;
}
}
}
vector<int> postorderTraversal(TreeNode* root) {
// write code here
vector<int> res;
postorder_1(root, res);
return res;
}
};
3.4 层次遍历
二叉树的最大深度
#include <algorithm>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @return int整型
*/
// 递归遍历
// int maxDepth(TreeNode* root) {
// // write code here
// static int res = 0;
// if (root == NULL)
// {
// return 0;
// }
// if(root->left == NULL and root->right == NULL)
// {
// return 1;
// }
// int count1 = maxDepth(root->left);
// int count2 = maxDepth(root->right);
// return (count1 > count2 ? count1:count2) + res + 1;
// }
//非递归层次遍历
int maxDepth(TreeNode* root) {
//空节点没有深度
if(root == NULL)
return 0;
//队列维护层次后续节点
queue<TreeNode*> q;
//根入队
q.push(root);
//记录深度
int res = 0;
//层次遍历
while(!q.empty()){
//记录当前层有多少节点
int n = q.size();
//遍历完这一层,再进入下一层
for(int i = 0; i < n; i++){
TreeNode* node = q.front();
q.pop();
//添加下一层的左右节点
if(node->left)
q.push(node->left);
if(node->right)
q.push(node->right);
}
//深度加1
res++;
}
return res;
}
};
3.5 二叉搜索树与双向链表
思路:采用中序遍历的方法去遍历二叉树,然后在中间去确定子树的第一个首节点以及将子树的左右孩子链接。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
TreeNode *pre;
TreeNode *head;
TreeNode* Convert(TreeNode* pRootOfTree) {
if(pRootOfTree == NULL)
{
return nullptr;
}
Convert(pRootOfTree->left);
if(pre == NULL )
{
pre = pRootOfTree;
head = pRootOfTree;
}else {
pre->right = pRootOfTree;
pRootOfTree->left = pre;
pre = pRootOfTree;
}
Convert(pRootOfTree->right);
return head;
}
};
3.6 合并二叉树
题目:已知两颗二叉树,将它们合并成一颗二叉树。合并规则是:都存在的结点,就将结点值加起来,否则空的位置就由另一个树的结点来代替。
解题思路:将问题简化为子树的处理,当t1节点为空则返回的是t2,如果两个都不为空,则加值,然后遍历两棵树的左子树和右子树并赋予给当前值加起来的节点的左右孩子,最后返回子树的根节点。
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
#include <cstddef>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param t1 TreeNode类
* @param t2 TreeNode类
* @return TreeNode类
*/
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
// write code here
if(t1 == NULL)
{
return t2;
}
if(t2 == NULL)
{
return t1;
}
t1->val = t1->val + t2->val;
t1->left = mergeTrees(t1->left, t2->left);
t1->right = mergeTrees(t1->right, t2->right);
return t1;
}
};
3.7 判断是不是二叉搜索树
题目:给定一个二叉树根节点,请你判断这棵树是不是二叉搜索树。二叉搜索树满足每个节点的左子树上的所有节点均小于当前节点且右子树上的所有节点均大于当前节点。
解题思路:
一:由于二叉搜索数中序序列是递增的,所以可以用中序递归,然后判断是否是递增序列从而判断是否为二叉搜索树,实现是通过逐步判断子树是否是二叉搜索树,如果任何一个子树不符合条件则整个二叉树都不符合。
二:每个节点的值都满足一个区间范围。初始化,根节点的范围 int 的范围。如果当前节点的值小于左区间或者大于右区间,则返回 false。否则,继续分别递归左右儿子节点:1.递归左儿子,并将左儿子的右区间修改为父节点的值;2.递归右儿子,并将右儿子的左区间修改为父节点的值。 最后,如果没有返回false,说明满足二叉搜索树,返回true。
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
#include <climits>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @return bool布尔型
*/
//先序遍历
// bool isValidBST_node(TreeNode* root, int l, int r)
// {
// if(root == nullptr)
// {
// return true;
// }
// if(root->val < l || root->val >r)
// return false;
// return isValidBST_node(root->left, l, root->val) && isValidBST_node(root->right, root->val,r);
// }
// bool isValidBST(TreeNode* root) {
// return isValidBST_node(root, INT_MIN, INT_MAX);
// }
long pre = INT_MIN;
//中序遍历
bool isValidBST(TreeNode* root) {
if(root == NULL)
return true;
//先进入左子树
if(!isValidBST(root->left))
return false;
if(root->val <= pre)
return false;
//更新最值
pre = root->val;
//再进入右子树
if(!isValidBST(root->right))
return false;
return true;
}
};
3.8 判断是不是完全二叉树
题目:给定一个二叉树,判断其是不是完全二叉树,完全二叉树的定义:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树。
解题思路:完全二叉树是只有最后一层和次下层才会有叶子节点,以使用队列辅助进行层次遍历——从上到下遍历所有层,每层从左到右,只有次下层和最下层才有叶子节点,其他层出现叶子节点就意味着不是完全二叉树。具体实现就是给定一个标志,看叶子节点是否是连续出现,如果在遍历连续的叶子节点有其他不是叶子节点意味着不是完全二叉树。
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
#include <cstddef>
#include <queue>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @return bool布尔型
*/
bool isCompleteTree(TreeNode* root) {
// write code here
if(root == NULL)
return true;
queue<TreeNode*> sz;
sz.push(root);
bool flag = false;
while (!sz.empty()) {
int len = sz.size();
for(int i = 0; i < len; i++)
{
TreeNode* cur = sz.front();
sz.pop();
if(cur==NULL)
{
flag=true;
}else {
if (flag) {
return false;
}
sz.push(cur->left);
sz.push(cur->right);
}
}
}
return true;
}
};
3.9 判断是不是平衡二叉树
平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
解题思路:我们可以对每个节点找到其左右子树的深度,如果左右子树的深度不符合则返回false,然后返回子树最大深度+1为自己的深度给其父节点,在一个节点中同时判断左右子树是否是平衡二叉树。
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pRoot TreeNode类
* @return bool布尔型
*/
int deep(TreeNode* root)
{
if(root == nullptr)
return 0;
int left = deep(root->left);
int right = deep(root->right);
return (left > right) ? left + 1 : right + 1;
}
bool IsBalanced_Solution(TreeNode* pRoot) {
// write code here
if (pRoot == nullptr) {
return true;
}
int left_len = deep(pRoot->left);
int right_len = deep(pRoot->right);
if((left_len - right_len) > 1 || (left_len - right_len) < -1){
return false;
}
return IsBalanced_Solution(pRoot->left) && IsBalanced_Solution(pRoot->right);
}
};
3.10 二叉搜索树的公共祖先
题目:
解题思路一:搜索路径比较,由于二叉树的特点是左子树小于根节点小于右子树,所以我们一定可以找到根节点到指定节点的路径,找到两个所需节点的路径后比较路径获得公共祖先。
class Solution {
public:
//求得根节点到目标节点的路径
vector<int> getPath(TreeNode* root, int target) {
vector<int> path;
TreeNode* node = root;
//节点值都不同,可以直接用值比较
while(node->val != target){
path.push_back(node->val);
//小的在左子树
if(target < node->val)
node = node->left;
//大的在右子树
else
node = node->right;
}
path.push_back(node->val);
return path;
}
int lowestCommonAncestor(TreeNode* root, int p, int q) {
//求根节点到两个节点的路径
vector<int> path_p = getPath(root, p);
vector<int> path_q = getPath(root, q);
int res;
//比较两个路径,找到第一个不同的点
for(int i = 0; i < path_p.size() && i < path_q.size(); i++){
if(path_p[i] == path_q[i])
//最后一个相同的节点就是最近公共祖先
res = path_p[i];
else
break;
}
return res;
}
};
解题思路二:
对于某一个节点若是p与q都小于等于这个这个节点值,说明p、q都在这个节点的左子树,而最近的公共祖先也一定在这个节点的左子树;若是p与q都大于等于这个节点,说明p、q都在这个节点的右子树,而最近的公共祖先也一定在这个节点的右子树。而若是对于某个节点,p与q的值一个大于等于节点值,一个小于等于节点值,说明它们分布在该节点的两边,而这个节点就是最近的公共祖先,因此从上到下的其他祖先都将这个两个节点放到同一子树,只有最近公共祖先会将它们放入不同的子树,每次进入一个子树又回到刚刚的问题,因此可以使用递归。
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @param p int整型
* @param q int整型
* @return int整型
*/
int lowestCommonAncestor(TreeNode* root, int p, int q) {
// write code here
if(root == nullptr)
return -1;
if((root->val >= p && root->val <= q) || (root->val <= p && root->val >= q))
return root->val;
else if(root->val >= p && root->val >= q)
return lowestCommonAncestor(root->left, p, q);
else
return lowestCommonAncestor(root->right, p, q);
}
};
3.11 在二叉树中找到最近公共祖先
题目:
解题思路:
方法一:先找出两个目标点的路径,然后比较路径获取最近的公共祖先。其中如何找路径是使用深度优先搜索以及回溯处理,具体就是设置一个外部标志标志是否找到路径,先将节点push进一个vector,如果在自己以及子树中找不到路径需要回溯pop掉,如果找到则将外部标志标志成功,这样这条路径就不会被回溯pop掉。
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @param o1 int整型
* @param o2 int整型
* @return int整型
*/
bool flag = false; // 记录是否找到路径
void dfs(TreeNode* root, vector<int>& path, int o)
{
if(root == nullptr || flag)
{
return;
}
path.push_back(root ->val);
if (root->val == o) {
flag = true;
return;
}
dfs(root->left, path, o);
dfs(root->right, path, o);
if (flag) {
return;
}
path.pop_back();
}
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
// write code here
vector<int> path_o1, path_o2;
dfs(root, path_o1, o1);
flag = false;
dfs(root, path_o2, o2);
int res;
for(int i = 0; i < path_o1.size() && i < path_o2.size(); i++)
{
if(path_o1[i] == path_o2[i])
{
res = path_o1[i];
}else {
break;
}
}
return res;
}
};
方法二:递归法,将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。解题步骤如下
- step 1:如果o1和o2中的任一个和root匹配,那么root就是最近公共祖先。
- step 2:如果都不匹配,则分别递归左、右子树。
- step 3:如果有一个节点出现在左子树,并且另一个节点出现在右子树,则root就是最近公共祖先.
- step 4:如果两个节点都出现在左子树,则说明最低公共祖先在左子树中,否则在右子树。
- step 5:继续递归左、右子树,直到遇到step1或者step3的情况。
class Solution {
public:
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
//该子树没找到,返回-1
if(root == NULL)
return -1;
//该节点是其中某一个节点
if(root->val == o1 || root->val == o2)
return root->val;
//左子树寻找公共祖先
int left = lowestCommonAncestor(root->left, o1, o2);
//右子树寻找公共祖先
int right = lowestCommonAncestor(root->right, o1, o2);
//左子树为没找到,则在右子树中
if(left == -1)
return right;
//右子树没找到,则在左子树中
if(right == -1)
return left;
//否则是当前节点
return root->val;
}
};
3.12 重建二叉树
题目:给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。
解题思路:对于二叉树的前序遍历,我们知道序列的第一个元素必定是根节点的值,因为序列没有重复的元素,因此中序遍历中可以找到相同的这个元素,而我们又知道中序遍历中根节点将二叉树分成了左右子树两个部分,如下图所示:
我们可以发现,数字1是根节点,并将二叉树分成了(247)和(3568)两棵子树,而子树的的根也是相应前序序列的首位,比如左子树的根是数字2,右子树的根是数字3,这样我们就可以利用前序遍历序列找子树的根节点,利用中序遍历序列区分每个子树的节点数。
具体做法:
- step 1:先根据前序遍历第一个点建立根节点。
- step 2:然后遍历中序遍历找到根节点在数组中的位置。
- step 3:再按照子树的节点数将两个遍历的序列分割成子数组,将子数组送入函数建立子树。
- step 4:直到子树的序列长度为0,结束递归。
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param preOrder int整型vector
* @param vinOrder int整型vector
* @return TreeNode类
*/
TreeNode* reConstructBinaryTree(vector<int>& preOrder, vector<int>& vinOrder) {
// write code here
int n = preOrder.size();
int m = vinOrder.size();
if(m == 0 || n == 0)
return nullptr;
TreeNode* root = new TreeNode(preOrder[0]);
for(int i = 0; i < vinOrder.size(); i++)
{
if(preOrder[0] == vinOrder[i])
{
vector<int> leftPre(preOrder.begin() + 1, preOrder.begin() + i + 1);
vector<int> leftVin(vinOrder.begin(), vinOrder.begin() + i);
root->left = reConstructBinaryTree(leftPre, leftVin);
vector<int> rightPre(preOrder.begin() + i + 1, preOrder.end());
vector<int> rightVin(vinOrder.begin() + i + 1, vinOrder.end());
root->right= reConstructBinaryTree(rightPre, rightVin);
}
}
return root;
}
};
待续更新......