- 55 二叉树的深度【BFS】【DFS】
- 56 数字在升序数组中出现的次数 【二分查找】
- 24 反转链表 【递归】【链表】
- 4 二维数组中的查找【二分查找】
- 25 合并两个排序的链表 【链表】【递归】
55 二叉树的深度【BFS】【DFS】
题目:
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度,根节点的深度视为 1 。
解题思路:
1)利用深度优先搜索的思想,利用递归实现,获得二叉树的深度。同时递归就可以看作是栈的实现, 当函数一层层调用自己时就是入栈,返回结果时就是出栈
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
// 深度优先搜索的思想,用递归方法实现
int TreeDepth(TreeNode* pRoot) {
if(pRoot == NULL) return 0;
cout << pRoot->val << endl; // 深度优先遍历树
int left_depth = TreeDepth(pRoot->left);
int right_depth = TreeDepth(pRoot->right);
int depth = max(left_depth, right_depth) + 1;
return depth;
}
};
2)采用广度优先搜索思想,用队列queue实现
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
// 利用Queue,采用广度优先搜索的思想得到树的深度
// 空间复杂度0(N), 时间复杂度O(N)
int TreeDepth(TreeNode* pRoot) {
queue<TreeNode*> q;
int level = 0;
if(pRoot != NULL){ q.push(pRoot); }
while(!q.empty()){
int size = q.size();
while(size--){
if(q.front()->left != NULL){ q.push(q.front()->left); }
if(q.front()->right != NULL){ q.push(q.front()->right); }
q.pop();
}
level++;
}
return level;
}
};
56 数字在升序数组中出现的次数:【二分查找】
题目:
给定一个长度为 n 的非降序数组和一个非负数整数 k ,要求统计 k 在数组中出现的次数
解题思路1(我的):
利用二分查找,找到k在数组中的位置,然后向前和后循环,统计k出现次数
代码:
class Solution {
public:
// 1.首先利用二分查找 找到 k 在数组中的位置
int find(const vector<int> data ,const int &k, const int &low, const int& high){
if(low == high) return -1;
int position = -1;
int middle = (low + high) / 2;
if(k == data[middle]){
return middle;
}else if(k < data[middle]){
position = find(data, k, low, middle);
} else{
position = find(data, k, middle + 1, high);
}
return position;
}
int findCount(const vector<int> &data ,const int &k, const int& posi){
int sum = 1;
int toLeft = posi;
int toRight = posi;
while(data[--toLeft] == k){
sum += 1;
}
while(data[++toRight] == k){
sum += 1;
}
return sum;
}
int GetNumberOfK(vector<int> data ,int k) {
int position = find(data, k, 0, data.size());
if(position == -1) {
cout << "数组中不存在" << k << endl;
return 0;
}
int count = findCount(data, k, position);
return count;
}
};
解题思路2(官方):
利用二分查找找到k的下界和上界,最后上界减去下界就是结果
24 反转链表 【递归】【链表】
题目:
给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
解题思路1:
从头到尾遍历一遍结点,进行翻转
代码:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode* pCurrent = NULL; // 指向当前节点
ListNode* pPrev = NULL; // 存前一个指针
while(pHead != NULL){
pCurrent = pHead;
pHead = pHead->next;
pCurrent->next = pPrev;
pPrev = pCurrent;
}
return pCurrent;
} */
};
解题思路2:
递归进行翻转
ListNode* ReverseList(ListNode* pHead) {
if(pHead == NULL || pHead->next == NULL) return pHead;
ListNode* pReversedHead = ReverseList(pHead->next); // 指向当前节点
pHead->next->next = pHead;
pHead->next=NULL;
return pReversedHead;
}
我的总结:
一开始我想用栈对链表进行反转,但指针的关系被我弄得有些很混乱,而且用栈的空间复杂度是O(N). 其实链表的反转不需要用堆栈,就从头到尾把链表遍历一遍,反转就行。可以用递归,或者非递归的方法。
4 二维数组中的查找【二分查找】
题目:
在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]]
给定 target = 7,返回 true。
给定 target = 3,返回 false。
解题思路:
利用二分查找,由于数组是按行,按列递增的,要找最右上角或者左下角的元素,判断之后才能有分支。例如target设置为最右上角的原素9,这样比较后,若小于9就将target向左移动,若大于9就将target向下移动
11 旋转数组的最小数组 【二分查找】
题目:
有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。
数据范围:1 \le n \le 100001≤n≤10000,数组中任意元素的值: 0 \le val \le 100000≤val≤10000
要求:空间复杂度:O(1)O(1) ,时间复杂度:O(logn)O(logn)
解题思路:
利用二分查找
代码:
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
int start = 0;
int end = rotateArray.size() - 1;
int middle = (start + end / 2);
while(start != end){
if( rotateArray[(start+end)/2] > rotateArray[end] ) {
start = (start+end) / 2 + 1;
} else if( rotateArray[(start+end)/2] < rotateArray[end] ) {
end = (start+end) / 2;
} else{
end -= 1;
}
}
return rotateArray[start];
// 二分法用中间的middle值和end端点作比较,若:
// 1. 中间值 > end值, 最小值一定在右边
// 2. 中间值 < end值, 最小值一定在左边
// 3. 中间值 == end值,最小值即可能在左边,也可能在右边,因此将end减去1,直到start == end时返回
// [1, 0, 1, 1, 1, 1, 1]
// [1, 1, 1, 1, 1, 0, 1]
}
};
25 合并两个排序的链表 【链表】【递归】
题目:
输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
解题思路:
就是比较两个头节点哪个小,就先接入哪个节点。也可以用递归方法实现。
用递归方法需要考虑好 结束条件 和 缩小递归空间。需要注意的是创建单链表是需要自己创建一个虚拟的头节点pVHead,返回结果时返回pVHead->next, 这样链表里每个节点都有一个头节点
代码:
迭代法:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
ListNode* pResult = new ListNode(-1);
ListNode* prev = pResult;
while(pHead1 != NULL && pHead2 != NULL){
if(pHead1->val < pHead2->val) {
prev->next = pHead1;
pHead1 = pHead1->next;
prev = prev->next;
} else{
prev->next = pHead2;
pHead2 = pHead2->next;
prev = prev->next;
}
}
prev->next = pHead1 ? pHead1:pHead2;
return pResult->next;
}
};
77 按之字形顺序打印二叉树 【二叉树】【队列】【栈】
题目:
给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替)
解题思路:
其实就是二叉树的层级遍历
代码:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
// 问题在于怎么判断该层数的遍历顺序:用一个level计数变量判断奇偶
// 以及如何实现该层的遍历顺序: 用vector的向后插入和向前插入实现,push_back() 和 insert() 函数
// 空间复杂度 O(N),时间复杂度O(N)
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> result;
if(pRoot == NULL) return result;
queue<TreeNode*> q;
q.push(pRoot);
int level = 0; // 用来判断当前层数是正序输出 还是倒序输出
vector<int> tmp;
while(!q.empty()){
int size = q.size();
if(level % 2 == 1) //当level为奇数时,此层需要倒序遍历
{
while(size--){
if(q.front()->left != NULL) q.push(q.front()->left);
if(q.front()->right != NULL) q.push(q.front()->right);
tmp.insert(tmp.begin(), q.front()->val); // 在vector tmp的头部插入数据
q.pop();
}
} else { //正序遍历
while(size--){
if(q.front()->left != NULL) q.push(q.front()->left);
if(q.front()->right != NULL) q.push(q.front()->right);
tmp.push_back(q.front()->val);
q.pop();
}
}
result.push_back(tmp);
level += 1;
tmp.clear();
}
return result;
}
};
JZ35 复杂链表的复制
题目:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。
解题思路:
- 用map将原链表节点地址 和 新链表映射地址存储好,创建完新链表后 再一一进行还原
- 将新节点插入的形式创建到原链表中,用双指针复制好random映射关系后,之后再用双指针把链表拆开
代码:
思路1:
/*
struct RandomListNode {
int label;
struct RandomListNode *next, *random;
RandomListNode(int x) :
label(x), next(NULL), random(NULL) {
}
};
*/
class Solution {
public:
// 解题思路1:
// 我都没看懂这个题深拷贝是什么意思, 就是要在堆区重新申请数据
// 原链表本身还好复制,但这个random的指向关系,如何复制? 我用了一个map来存储原链表的映射关系
RandomListNode* Clone(RandomListNode* pHead) {
RandomListNode* vHead = new RandomListNode(0); // 创建一个虚拟的头节点
RandomListNode* result = vHead;
map<RandomListNode*, RandomListNode*> m;
while(pHead != NULL){
RandomListNode* tmp = new RandomListNode(pHead->label); // new操作返回一个该类型数据创建在堆区的地址
vHead->next = tmp;
tmp->random = pHead->random; // 讲原先的random指针关系记录
m.insert(pair<RandomListNode*, RandomListNode*>( pHead, tmp )); // 将原地址和新地址的映射存到map里
pHead = pHead->next;
vHead = vHead->next;
}
vHead = result->next;
while(vHead != NULL){
if(vHead->random != NULL){
vHead->random = m.find(vHead->random)->second;
}
vHead = vHead->next;
}
return result->next;
}
};
思路2:
/*
struct RandomListNode {
int label;
struct RandomListNode *next, *random;
RandomListNode(int x) :
label(x), next(NULL), random(NULL) {
}
};
*/
class Solution {
public:
// 需要注意的点:
// 1)当newP指向NULL时,不能再调用newP->next了
// 2)注意循环中要把指针往后移
// 3)continue会直接跳出一层while循环
RandomListNode* Clone(RandomListNode* pHead) {
if(pHead == NULL) return NULL;
RandomListNode* pointer = pHead;
while(pointer != NULL){
RandomListNode* tmp = new RandomListNode(pointer->label); // 1. 创建新的节点
tmp->next = pointer->next;
pointer->next = tmp; // 2. 新节点插入到链表中
pointer = tmp->next;
}
// 3. 复制映射关系
RandomListNode* oldP = pHead;
RandomListNode* newP = pHead->next;
while(oldP != NULL){
if(oldP->random != NULL){
newP->random = oldP->random->next;
}
oldP = oldP->next->next;
if(newP->next != NULL){
newP = newP->next->next;
}
}
oldP = pHead;
newP = pHead->next;
RandomListNode* result = pHead->next;
while(oldP != NULL){
oldP->next = oldP->next->next;
oldP = oldP->next;
if(newP->next != NULL){
newP->next = newP->next->next;
newP = newP->next;
}
}
return result;
}
};
思路2精简版代码:
/*
struct RandomListNode {
int label;
struct RandomListNode *next, *random;
RandomListNode(int x) :
label(x), next(NULL), random(NULL) {
}
};
*/
class Solution {
public:
RandomListNode* Clone(RandomListNode* pHead) {
if(pHead == NULL) return NULL;
unordered_map<RandomListNode*, RandomListNode*> m;
RandomListNode* cur = pHead;
while(cur != NULL){
m[cur] = new RandomListNode(cur->label); //1. 建立老节点和新建的节点之间的映射关系
cur = cur->next;
}
cur = pHead;
while(cur != NULL){
m[cur]->next = m[cur->next]; // 2. 连接好新建的结点的链表关系
m[cur]->random = m[cur->random]; // 3. 创新新的链表的random映射关系
cur = cur->next;
}
return m[pHead]; // 4. 返回旧的头指针对应的新的头指针
}
};
JZ76 删除链表中重复的结点
题目
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5 处理后为 1->2->5
数据范围:链表长度满足 0 \le n \le 1000 \0≤n≤1000 ,链表中的值满足 1 \le val \le 1000 \1≤val≤1000
进阶:空间复杂度 O(n)\O(n) ,时间复杂度 O(n) \O(n)
例如输入{1,2,3,3,4,4,5}时,对应的输出为{1,2,5},对应的输入输出链表如下图所示:
解题思路:
- 创建虚拟头节点,直接遍历链表
- 递归实现
代码:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
// 处理特殊情况比较头疼,比如{1,1} 和 {1}, 还是得用虚拟头节点
// 时间复杂度 O(N), 空间复杂度O(1)
ListNode* deleteDuplication(ListNode* pHead) {
if(pHead == NULL) return NULL;
ListNode* vHead = new ListNode(-1);
vHead->next = pHead; // 1 创建虚拟头节点
ListNode* lastNode = vHead;
ListNode* cur = vHead;
while(cur != NULL && cur->next != NULL){
if(cur->val == cur->next->val){
int value = cur->val;
while(cur != NULL && cur->val == value){
cur = cur->next;
}
lastNode->next = cur;
continue;
}
lastNode = cur;
cur = cur->next;
}
return vHead->next;
}
};
JZ54 二叉搜索树的第K个节点
题目
给定一棵结点数为n 二叉搜索树,请找出其中的第 k 小的TreeNode结点值。
1.返回第k小的节点值即可
2.不能查找的情况,如二叉树为空,则返回-1,或者k大于n等等,也返回-1
3.保证n个节点的值不一样
思路:
先用中序遍历将树的节点放入一个数组,之后返回第K个值
代码
1、递归实现中序遍历
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
class Solution {
public:
// 把树中序遍历一遍存入数组
void MiddleOrder(TreeNode* proot, vector<int>& numbers){
if(proot == NULL) return;
MiddleOrder(proot->left, numbers);
numbers.push_back(proot->val);
MiddleOrder(proot->right, numbers);
}
int KthNode(TreeNode* proot, int k) {
// write code here
if(proot == NULL or k == 0) return -1;
vector<int> numbers;
MiddleOrder(proot, numbers);
if(k > numbers.size()) return -1;
return numbers[k-1];
}
};
2、栈实现中序遍历
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
class Solution {
public:
// 把树中序遍历一遍存入数组
// 用栈实现中序遍历
int KthNode(TreeNode* proot, int k) {
// write code here
if(proot == NULL or k == 0) return -1;
stack<TreeNode*> s;
TreeNode* tmp = NULL;
s.push(proot);
int i = 0;
while(!s.empty()){
while(s.top()->left != NULL){ // 1.把当前节点最小的左节点插入到栈顶
s.push(s.top()->left);
}
while(!s.empty()){
tmp = s.top();
s.pop();
i += 1;
if( i == k) return tmp->val;
if(tmp->right != NULL){ // 2. 当前节点存在右子树时,把右子树放到栈顶,
// break调,返回到(1),把右子树的最小左节点push到栈中
s.push(tmp->right);
break;
}
}
}
return -1;
}
};
JZ26 树的子结构 【递归】
题目
输入两棵二叉树A,B,判断B是不是A的子结构。(我们约定空树不是任意一个树的子结构)
假如给定A为{8,8,7,9,2,#,#,#,#,4,7},B为{8,9,2},2个树的结构如下,可以看出B是A的子结构
解题思路:
递归思想,先用递归判断是否包含子树,再用递归判断是否子树相等。但要注意递归边界条件的判断
代码:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
// 思路:用递归的方式,递归的检查 前树的子树 是否包含 后树
// 后用递归检查 前树的子树 和 后树 是否相等
// 主要:递归比较两树是否相等时,注意边界条件,当后数的子节点为空时,前树子节点和后树子节点的比较就是true的
bool isSame(TreeNode* pRoot1, TreeNode* pRoot2){
// 如果pRoot2未空,pRoot1空了,或者两个树头节点不相等,返回false
if(pRoot1 == NULL || pRoot1->val != pRoot2->val) return false;
// 将左子树和右子树相等的初值设置为true
bool left = true;
bool right = true;
// 如果后子树 存在左节点, 则有比较左子树的必要,若不存在就说明 前树 直接包含了 后树的 左子树
if(pRoot2->left){
left = isSame(pRoot1->left, pRoot2->left);
}
// 如果后子树 存在右节点, 则有比较右子树的必要,若不存在就说明 前树 直接包含了 后树的 右子树
if(pRoot2->right){
right = isSame(pRoot1->right, pRoot2->right);
}
return left && right;
}
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
// 但凡有一个树为空,返回false
if(!pRoot1 || !pRoot2) return false;
if(isSame(pRoot1, pRoot2)) return true;
if(HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2)){
return true;
} else{
return false;
}
}
};