剑指offer|解析和答案(C++/Python) (三)
参考剑指offer(第二版),这里做一个学习汇总,包括解析及代码。代码均在牛客网进行验证(摘自自己的牛客网笔记)。
整个剑指offer解析链接:
剑指offer|解析和答案(C++/Python) (一).
剑指offer|解析和答案(C++/Python) (二).
剑指offer|解析和答案(C++/Python) (三).
剑指offer|解析和答案(C++/Python) (四).
剑指offer|解析和答案(C++/Python) (五)上.
剑指offer|解析和答案(C++/Python) (五)下.
剑指offer|解析和答案(C++/Python) (六).
习题
解决面试题的思路
1.二叉树的镜像
2.对称的二叉树
3.顺时针打印矩阵
4.包含min函数的栈
5.栈的压入、弹出序列
6.从上到下打印二叉树
7.之字形打印二叉树
8.二叉搜索树的后续遍历序列
9.二叉树中和为某一值的路径
10.复杂链表的复制
11.二叉搜索树与双向链表
12.序列化二叉树
13.字符串的排列
1.二叉树的镜像
请完成一个函数,输入一颗二叉树,该函数输出它的镜像。
思路:
所谓的镜像二叉树,其实就是交换二叉树的左右节点。
先前序遍历这棵树的每个节点,如果遍历到的节点有子节点,就交换它的两个子节点。当交换所有非叶节点的左、右节点之后,就得到了树的镜像。
参考图示:
代码:
C++
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if(pRoot == NULL)
return ;
//如果没有子节点
if(pRoot -> left == NULL && pRoot -> right == NULL){
return ;
}
//交换左右子节点 实现镜像操作
TreeNode* temp = pRoot -> left;
pRoot -> left = pRoot -> right;
pRoot -> right = temp;
//如果还右子节点 进行递归
Mirror(pRoot -> right);
//如果还左子节点 进行递归
Mirror(pRoot -> left);
}
};
Python:
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 返回镜像树的根节点
def Mirror(self, root):
# write code here
if root == None:
return
if root.left == None and root.right == None:
return
temp = root.left
root.left = root.right
root.right = temp
self.Mirror(root.right)
self.Mirror(root.left)
2.对称的二叉树
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
思路:
使用递归判断,如果前序遍历,即先遍历左子节点再遍历右子节点,和其对称遍历,即先遍历右子节点再遍历左子节点。同时考虑nullptr,若两种遍历方法得到是一样的则完全对称,反之不对称。
参考图示:
代码:
C++
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
bool isSymmetrical(TreeNode* pRoot)
{
return isSymmetricalCore(pRoot, pRoot);
}
bool isSymmetricalCore(TreeNode* pRoot1, TreeNode* pRoot2){
if(pRoot1 == NULL && pRoot2 == NULL){
return true;
}
else if(pRoot1 == NULL || pRoot2 == NULL){
return false;
}
if(pRoot1 -> val == pRoot2 -> val){
return isSymmetricalCore(pRoot1 -> left, pRoot2 -> right)
&& isSymmetricalCore(pRoot1 -> right, pRoot2 -> left);
}else {
return false;
}
}
};
Python
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSymmetrical(self, pRoot):
# write code here
return self.isSymmetricalCore(pRoot, pRoot)
def isSymmetricalCore(self, pRoot1, pRoot2):
if pRoot1 == None and pRoot2 == None:
return True
if pRoot1 == None or pRoot2 == None:
return False
if pRoot1.val != pRoot2.val:
return False
else :
return self.isSymmetricalCore(pRoot1.left, pRoot2.right) \
and self.isSymmetricalCore(pRoot1.right, pRoot2.left)
3.顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
思路:
参考图示:
打印矩阵的第一圈是从左上角(0,0)开始的,第二圈是(1,1)开始的。所以可以选取左上角为(start,start)为起点的一圈进行分析。
对于5 x 5的矩阵,最后一圈只有一个数字,对应坐标为(2,2),且5 > 2 x 2。对于6 x 6的矩阵,最后一圈左上角坐标(2,2),且6 > 2 x 2。所以可以得出循环条件是cols > 2 x start 且 rows > 2 x start。
注意:
最后一圈可能只有一行或者一列,甚至一个数字,那么打印一圈就不需要四步,只需要一步、两步及三步。
1.一步。一圈至少会有一步。
2.两步。需要终止的行号大于起始行号。
3.三步。需要终止的行号大于起始行号且终止列号大于起始列号(两行两列)。
4.四步。需要终止的行号大于起始行号且终止列号大于起始列号+1(三行两列)。
代码:
C++:
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
int rows = matrix.size();
int cols = matrix[0].size();
vector<int> matrixCircle;
vector<int> matrixNumbers;
//从左上角(0, 0)开始顺时针打印
int start = 0;
while(cols > start * 2 && rows > start * 2){
matrixNumbers = printMatrixCore(matrix, rows, cols, start);
matrixCircle.insert(matrixCircle.end(), matrixNumbers.begin(),
matrixNumbers.end());
start++;
}
return matrixCircle;
}
vector<int> printMatrixCore(vector<vector<int> >matrix, int rows, int cols, int start){
int endX = cols - 1 - start;
int endY = rows - 1 - start;
vector<int> matrixCircle;
//从左到右打印一行
for(int i = start; i <= endX; ++i){
int number = matrix[start][i];
matrixCircle.push_back(number);
}
//从上到下打印一列
if(start < endY){
for(int i = start + 1; i <= endY; ++i){
int number = matrix[i][endX];
matrixCircle.push_back(number);
}
}
//从左到右打印一行
if(start < endX && start < endY){
for(int i = endX - 1; i >= start; --i){
int number = matrix[endY][i];
matrixCircle.push_back(number);
}
}
//从下到上打印一列
if(start < endX && start < endY - 1){
for(int i = endY - 1; i >= start + 1; --i){
int number = matrix[i][start];
matrixCircle.push_back(number);
}
}
return matrixCircle;
}
};
Python
# -*- coding:utf-8 -*-
class Solution:
# matrix类型为二维列表,需要返回列表
def printMatrix(self, matrix):
# write code here
rows = len(matrix)
cols = len(matrix[0])
start = 0
matrixCircle = []
matrixNumbers = []
while rows > 2 * start and cols > 2 * start:
matrixNumbers = self.printMatrixCore(matrix, rows, cols, start)
#延长列表
matrixCircle.extend(matrixNumbers)
start = start + 1
return matrixCircle
def printMatrixCore(self, matrix, rows, cols, start):
endX = cols - 1 - start
endY = rows - 1 - start
matrixCircle = []
#从左到右
for i in range(start, endX + 1):
number = matrix[start][i]
matrixCircle.append(number)
#从上到下
if endY > start:
for i in range(start + 1, endY + 1):
number = matrix[i][endX]
matrixCircle.append(number)
#从右到左
if endX > start and endY > start:
for i in range(endX - 1, start - 1, -1):
number = matrix[endY][i]
matrixCircle.append(number)
#从下到上
if endY > start + 1 and endX >start:
for i in range(endY - 1, start, -1):
number = matrix[i][start]
matrixCircle.append(number)
return matrixCircle
4.包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
思路:
push和top以及pop使用一个栈均可实现。问题是min函数。如果每次压入一个新元素进栈时,都将栈里的元素进行排序,让最小的元素位于栈顶,那么就能在O(1)时间内得到最小元素。但是这样做不能保证最后压入的元素能最新出栈。
如果使用额外的一个变量来保存最小元素,有更小的元素则更新这个变量,使用min函数的时候就弹出这个变量,但是这样当变量弹出时,无法得到次小的变量。
所以就把每次的最小元素(之前的最小元素和新压入栈的元素的两者的最小值)都保存起来放在一个辅助栈里面。
参考下表:
代码:
C++
class Solution {
public:
stack<int> m_data;//数据栈
stack<int> m_min;//辅助栈
void push(int value) {
m_data.push(value);
if(m_min.size() == 0||value < m_min.top() ){
m_min.push(value);
}else{
m_min.push(m_min.top());
}
}
void pop() {
//assert(m_data.size() > 0 && m_min.size() > 0);
m_data.pop();
m_min.pop();
}
int top() {
//assert(m_data.size() > 0 && m_min.size() > 0);
return m_data.top();
}
int min() {
//assert(m_data.size() > 0 && m_min.size() > 0);
return m_min.top();
}
};
Python
# -*- coding:utf-8 -*-
class Solution:
def push(self, node):
# write code here
self.m_data.append(node)
if len(self.m_min) == 0 or node < self.m_min[-1] :
self.m_min.append(node)
else :
self.m_min.append(self.m_min[-1])
def pop(self):
# write code here
if len(self.m_data) == 0 or len(self.m_min) == 0:
return
self.m_data.pop()
self.m_min.pop()
def top(self):
# write code here
if len(self.m_data) == 0 or len(self.m_min) == 0:
return
return self.m_data[-1]
def min(self):
# write code here
if len(self.m_data) == 0 or len(self.m_min) == 0:
return
return self.m_min[-1]
def __init__(self):
self.m_data = []#数据栈
self.m_min = []#辅助栈
5.栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
思路:
建立一个辅助栈,把输入的第一个序列中的数字一次压入该辅助栈,并按照第二个序列的顺序依次从该栈中弹出数字。
参考下表:
压入栈序列为{1,2,3,4,5},弹出栈序列为{4,5,3,2,1}对应的压栈和弹出过程。
压入顺序为{1,2,3,4,5},弹出栈序列为{4,3,5,1,2}对应的压栈和弹出过程。
规律:
如果下一个弹出的数字刚好是栈顶数字,那么直接弹出;如果下一个弹出的数字不在栈顶,则把压栈序列中还没有入栈的数字压入辅助栈,直到把下一个需要弹出的数字压入栈顶为止;如果所有数字都压入栈后,仍然没有找到下一个弹出的数字,那么该序列不可能是一个弹出序列。
代码:
C++
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
bool isPossible = false;
if (pushV.size() == 0 || popV.size() == 0){
return isPossible;
}
int indexPush = 0;
int indexPop = 0;
stack<int> stackData;//辅助栈
while(indexPop < popV.size()){
//当弹出的数字不是辅助栈的栈顶,那么就通过压入序列压入辅助栈
while(stackData.empty() ||popV[indexPop] != stackData.top()){
//溢出情况
if(indexPush >= pushV.size()){
break;
}
stackData.push(pushV[indexPush]);
++indexPush;
}
//先进行判断通过压入的值得到辅助栈的栈顶是否等于弹出序列
if(stackData.top() != popV[indexPop])
break;//不等于直接中断
//若等于,则要弹出辅助栈栈顶
stackData.pop();
++indexPop;
}
//当弹出序列全部弹出完,并且辅助栈为空,则true
if(indexPop == popV.size() && stackData.empty())
isPossible = true;
return isPossible;
}
};
Python
class Solution:
def IsPopOrder(self, pushV, popV):
# write code here
if len(pushV) == 0 or len(popV) == 0:
return False
isPossible = False
indexPush = 0
indexPop = 0
stackData = []
while indexPop < len(popV):
while len(stackData) == 0 or stackData[-1] != popV[indexPop]:
if indexPush == len(pushV):#判断是否溢出
break #溢出直接中断
stackData.append(pushV[indexPush])#压入辅助栈
indexPush = indexPush + 1
if indexPush == len(pushV) and stackData[-1] != popV[indexPop]:
#如果是因为溢出造成的中断
#且栈顶不等于压入栈的序列
break
stackData.pop()#弹出一个
indexPop = indexPop + 1#压栈序列加1
if indexPop == len(popV) and len(stackData) == 0:
isPossible = True
return isPossible
6.从上到下打印二叉树
从上往下打印出二叉树的每个节点,同层节点从左至右打印。如下图所示二叉树,则依次打印出8,6,10,5,7,9,11.
分析打印过程:
每次打印一个节点时,如果该节点有子节点,那么把该节点的子节点放到队列的末尾。接下来把队列的头部取出来(也就是第一个进入队列的节点),重复前面的打印操作。直到队列中所有节点被打印出来。
C++:
STL已经为我们实现了一个很好的deque(两端都可以进出的队列)。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> listTreeNode;
deque<TreeNode*> dequeTreeNode;
if(root == NULL)
return listTreeNode;
dequeTreeNode.push_back(root);//先放入根节点
while(dequeTreeNode.size()){
TreeNode* pNode = dequeTreeNode.front();//返回第一个数据
dequeTreeNode.pop_front();//弹出第一个数据 头部删除
listTreeNode.push_back(pNode->val);//把打印值放入
if(pNode->left)
dequeTreeNode.push_back(pNode->left);//加入队列
if(pNode->right)
dequeTreeNode.push_back(pNode->right);//加入队列
}
return listTreeNode;
}
};
Python:
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 返回从上到下每个节点值列表,例:[1,2,3]
def PrintFromTopToBottom(self, root):
# write code here
queueTreeNode = []
listTreeNode = []
if root == None:
return listTreeNode
queueTreeNode.append(root)
while len(queueTreeNode) :
pNode = queueTreeNode.pop(0)#弹出第0个元素
listTreeNode.append(pNode.val)#把值放入
if pNode.left :
queueTreeNode.append(pNode.left)
if pNode.right :
queueTreeNode.append(pNode.right)
return listTreeNode
举一反三:分行从上到下打印二叉树
从上到下打印二叉树,同一层节点按从左到右的顺序打印,没一层打印一行。例如上图二叉树的打印结果为:
8
6 10
5 7 9 11
思路:
1.这题和前面的面试题类似,可以用一个队列来保存将要打印的节点。为了把二叉树的每一行单独打印到一行里,我们需要两个变量:一个变量表示在当前层中还没有打印的节点数;另一个变量表示下一层节点的数目。
参考代码如下(摘抄剑指offer)
class Solution {
public:
void Print(BinaryTreeNode* pRoot){
if(pRoot == nullptr)
return
std::queue<BinaryTreeNode*> nodes;
nodes.push(pRoot);
int nextLevel = 0;
int toBePrinted = 1;
while(!nodes.empty()){
BinaryTreeNode* pNode = nodes.front();
printf("%d",pNode->m_nValue);
if(pNode->m_left != nullptr){
nodes.push(pNode->m_left);
++nextLevel;
}
if(pNode->m_right != nullptr){
nodes.push(pNode->m_right);
++nextLevel;
}
nodes.pop();
--toBePrinted;
if(toBePrinted == 0){
printf("\n");
toBePrinted = nextLevel;
nextLevel = 0
}
}
}
};
变量toBePrinted表示当前层中还没有打印的节点数,变量nextLevel表示下一层的节点数。如果一个节点有子节点,则没把一个子节点加入队列,同时把变量nextLevel加1。没打印一个节点,toBePrinted减1。当toBePrinted等于0时,表示当前层所有节点已经打印完毕,可以继续打印下一层。
2.和下面那道题(之字形打印二叉树)思路相似。使用两个队列来保存树节点,一个队列保存当前层的树节点,一个队列保存下一个队列的树节点。没当一个队列元素全部弹出(先入先出,即打印完毕),则提行,使用下一个队列。
代码:
C++
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int> > listTreeNode;//返回打印的二叉树
vector<int> currentListTreeNode;//当前层 打印的二叉树
deque<TreeNode*> dequeTreeNode[2];//使用两个队列
int current = 0;
int next = 1;
if(pRoot == NULL)//先判断是否为空
return listTreeNode;
dequeTreeNode[current].push_back(pRoot);//先放入根节点
while(!dequeTreeNode[current].empty() || !dequeTreeNode[next].empty() ){
TreeNode* pNode = dequeTreeNode[current].front();//返回第一个数据
currentListTreeNode.push_back(pNode->val);
dequeTreeNode[current].pop_front();//弹出第一个数据 头部删除
if(pNode->left){
dequeTreeNode[next].push_back(pNode->left);
}
if(pNode->right){
dequeTreeNode[next].push_back(pNode->right);
}
if(dequeTreeNode[current].empty()){//当前层 打印完毕
listTreeNode.push_back(currentListTreeNode);
currentListTreeNode.clear();
current = 1 - current;
next = 1 - next;
}
}
return listTreeNode;
}
};
Python
class Solution:
def Print(self, pRoot):
# write code here
if pRoot == None:
return []
listTreeNode = []#返回的二叉树序列
currentTreeNode = []#当前层的二叉树序列
currentTree = 0#当前层级
nextTree = 1#下一层级
dequeCurrent = []#创建两个队列
dequeNext = []
#dequeCurrent.append(pRoot)#先放入根节点
dequeCurrent = [pRoot]
while dequeCurrent or dequeNext:
pNode = dequeCurrent.pop(0)#得到队列的第一个元素
currentTreeNode.append(pNode.val)#把值放入
if pNode.left :
dequeNext.append(pNode.left)
if pNode.right :
dequeNext.append(pNode.right)
if dequeCurrent == []: #当前队列使用完
currentTree = 1 - currentTree
nextTree = 1 - nextTree
listTreeNode.append(currentTreeNode)
currentTreeNode = []#清空
dequeCurrent = dequeNext
dequeNext = []
return listTreeNode
7.之字形打印二叉树
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。如下图二叉树打印顺序为:
1
3 2
4 5 6 7
15 14 13 12 11 10 9 8
思路:
按之子形打印二叉树需要两个栈。我们在打印某一个层的节点时,把下一层的子节点保存到相应的栈里。如果当前打印的是奇数层(第一层、第三层等),则先保存左子节点再保存右子节点到第一个栈里;如果当前打印的是偶数层(第二层、第四层等),则先保存右子节点再保存左子节点到第二个栈里。
为什么需要两个栈?一个栈用来打印当前层的数据,一个栈用来保存下一层的数据。
分析过程:
代码:
C++:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int> > listTreeNode;//返回打印的二叉树
vector<int> currentListTreeNode;//当前层 打印的二叉树
stack<TreeNode*> levels[2];//使用两个栈
int current = 0;
int next = 1;
if(pRoot == NULL)//先判断是否为空
return listTreeNode;
levels[current].push(pRoot);//先放入根节点 栈是push
while(!levels[current].empty() || !levels[next].empty() ){
TreeNode* pNode = levels[current].top();//得到栈顶
levels[current].pop();//弹出栈顶
currentListTreeNode.push_back(pNode->val);
if(current == 0){//奇数层 0其实是第一层开始 先放左节点 再右节点
if(pNode->left){
levels[next].push(pNode->left);
}
if(pNode->right){
levels[next].push(pNode->right);
}
}else{//偶数层 先放右节点 再左节点
if(pNode->right){
levels[next].push(pNode->right);
}
if(pNode->left){
levels[next].push(pNode->left);
}
}
if(levels[current].empty()){//当前层 打印完毕
listTreeNode.push_back(currentListTreeNode);
currentListTreeNode.clear();
current = 1 - current;
next = 1 - next;
}
}
return listTreeNode;
}
};
Python:
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def Print(self, pRoot):
# write code here
if pRoot == None:
return []
listTreeNode = []#返回的二叉树序列
currentTreeNode = []#当前层的二叉树序列
currentTree = 0#当前层级
nextTree = 1#下一层级
stackCurrent = []#创建两个栈
stackNext = []
#stackCurrent.append(pRoot)#先放入根节点
stackCurrent = [pRoot]
while stackCurrent or stackNext:
pNode = stackCurrent.pop()#得到栈顶
currentTreeNode.append(pNode.val)#把值放入
if currentTree == 0:#奇数层 先放左节点
if pNode.left :
stackNext.append(pNode.left)
if pNode.right :
stackNext.append(pNode.right)
else : #偶数层 先放右节点
if pNode.right :
stackNext.append(pNode.right)
if pNode.left :
stackNext.append(pNode.left)
if stackCurrent == []: #当前栈节点使用完
currentTree = 1 - currentTree
nextTree = 1 - nextTree
listTreeNode.append(currentTreeNode)
currentTreeNode = []#清空
stackCurrent = stackNext
stackNext = []
return listTreeNode
8.二叉搜索树的后续遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。例如输入数组{5, 7,6, 9, 11, 10, 8},则返回true。如果输入是{7, 4, 6, 5}则返回false。
思路:
在后续遍历中:
1.最后一个数字是根节点的值。剩下的数字可以分为两个部分。
2.第一部分是左子树节点的值,它们都比根节点的值小。
3.第二部分是右子树节点的值,它们都比根节点的值大。
4.后续遍历:顺序是左子树–>右子树–>根节点。
代码:
C++
class Solution {
public:
bool VerifySquenceOfBST(vector<int> sequence) {
if(sequence.size() == 0){
return false;
}
return VerifySquenceOfBSTCore(sequence, 0, sequence.size() - 1);
}
bool VerifySquenceOfBSTCore(vector<int> sequence, int beginID, int endID ){
// 判空
if(sequence.empty() || beginID > endID)
return false;
int root = sequence[endID];//得到根节点
int index;
int i = beginID;
for(; i < endID; ++i){//找左右子树分界点
if(sequence[i] > root){
index = i;
break;
}
}
index = i;
//检查右子树是否存在比根节点小的值
for(int j = index; j < endID; ++j){
if(sequence[j] < root){
return false;
}
}
bool left = true;
if(index > beginID)//如果有左子树 对左子树进行递归
left = VerifySquenceOfBSTCore(sequence, beginID, index - 1);
bool right = true;
if(index < endID - 1)//如果有右子树 对右子树今昔递归
right = VerifySquenceOfBSTCore(sequence, index, endID - 1);
return (left && right) ;
}
};
Python
class Solution:
def VerifySquenceOfBST(self, sequence):
# write code here
if sequence == None or len(sequence) == 0:
return False
length = len(sequence)
root = sequence[length - 1]
for i in range(length):#找左右子树分界点
index = i
if sequence[i] > root:
break
#判断右子树是否存在比根节点更小的节点
for i in range(index, length):
if sequence[i] < root:
return False
left = True
#如果存在左子树
if index > 0:#递归左子树
left = self.VerifySquenceOfBST(sequence[0:index])
right = True
#如果存在右子树
if index < length - 1:
right = self.VerifySquenceOfBST(sequence[index : -1])
return (left and right)
9.二叉树中和为某一值的路径
输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
思路:
参考图示
遍历上述二叉树的过程:
1.当前序遍历的方式访问到某一节点时,把该节点添加到路径上,并累积该节点的值。
2.如果该节点是叶节点,并且路径中的节点值刚好等于输入的整数,则符合当前路径要求,保存下来。
3.如果当前节点不是叶节点,则继续访问它的子节点。
4.当前节点访问结束后,递归函数自动回到它的父节点(无论路径是否符合,若符合则会保存/打印)。需要确保函数在推出之前要做在路径上删除当前节点,并减去当前节点的值。
代码:
C++
class Solution {
public:
vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
vector<vector<int> > path;
vector<vector<int> >* pPath;
pPath = &path;
vector<int> pathList;//单条路径
if(root == NULL)
return path;
FindPathCore(root, expectNumber, pPath, pathList, 0);
return path;
}
void FindPathCore(TreeNode* pRoot, int expectNumber,
vector<vector<int> >* pPath,
vector<int> pathList,
int currentNumber){
currentNumber += pRoot->val;//添加当前值
pathList.push_back(pRoot->val);//添加当前路径
bool leaf = (pRoot->left == NULL && pRoot->right == NULL);//判断是不是叶节点
if(leaf && currentNumber == expectNumber){//如果是叶节点 并且当前值和期待值相等
pPath->push_back(pathList);//添加一条路径
//pathList.clear();//清空
}
//如果不是叶节点则遍历其叶节点
if(pRoot->left){//有左节点
FindPathCore(pRoot->left, expectNumber, pPath, pathList, currentNumber);
}
if(pRoot->right){//有右节点
FindPathCore(pRoot->right, expectNumber, pPath, pathList, currentNumber);
}
//如果是叶节点 但不符合路径值 则删除该路径最后的节点
pathList.pop_back();
}
};
Python
class Solution:
# 返回二维列表,内部每个列表表示找到的路径
def FindPath(self, root, expectNumber):
# write code here
path = []#二维列表
pathList = []#一维列表存储单个路径
currentNumber = 0
if root == None:
return path
self.FindPathCore(root, expectNumber, path, pathList, currentNumber)
return path
def FindPathCore(self, root, expectNumber, path, pathList, currentNumber):
currentNumber += root.val#计算当前值
pathList.append(root.val)#添加当前路径
isLeaf = (root.left == None and root.right == None)#判断是否是叶节点
if isLeaf and currentNumber == expectNumber:#如果是叶节点 并且 当前值和期望值相等
path.append(pathList[:])#添加路径
#pathList = []#清空路径
#如果不是叶节点 则遍历其子节点
if root.left:
self.FindPathCore(root.left, expectNumber, path, pathList, currentNumber)
if root.right:
self.FindPathCore(root.right, expectNumber, path, pathList, currentNumber)
#如果是节点 但值不相等 则删除当前节点
pathList.pop()
10.复杂链表的复制
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
一个含有5个节点的复制链表如下图所示:
思路:
1.首先复制原链表节点,并使用pNext 连起来。针对原链表的节点pSibling(random)可以通过每次变量查找,时间复杂度是O(n^2)。
2.可以通过空间换取时间,将原始链表和复制链表的结点通过哈希表对应起来,这样查找的时间就从O(N)变为O(1)。具体为:复制原始链表上的每个结点N创建N’,然后把这些创建出来的结点用pNext连接起来。同时把<N,N’>的配对信息方法一个哈希表中;然后设置复制链表中的每个结点的pSibling指针,如果原始链表中结点N的pSibling指向结点S,那么在复制链表中,对应的N’应该指向S’。
3.这种方法优于前两种方法,不使用辅助空间,分3步走。a)复制链表,即扩充原链表。b)在复制链表上链接随机节点。c)复制链表和原链表断开,得到复制链表。
参考下图:
a)复制链表,即扩充原链表。
把复制的节点N’链接在N的后面。
b)在复制链表上链接随机节点。
根据原链表上的pSibling(random),使得复制的链表也链接到pSibling’(random’)。
c)复制链表和原链表断开,得到复制链表。
代码:
C++
/*
struct RandomListNode {
int label;
struct RandomListNode *next, *random;
RandomListNode(int x) :
label(x), next(NULL), random(NULL) {
}
};
*/
class Solution {
public:
RandomListNode* Clone(RandomListNode* pHead)
{
//1.复制链表 即扩充原链表
CloneNodes(pHead);
//2.在复制链表上链接随机节点
ConnectRandomNodes(pHead);
//3.复制链表和原链表断开 得到复制链表
return ReconnectNodes(pHead);
}
void CloneNodes(RandomListNode* pHead){
RandomListNode* pNode = pHead;
while(pNode != NULL){
RandomListNode* pClone = new RandomListNode(pNode->label);
pClone->next = pNode->next;
pNode->next = pClone;
pNode = pClone->next;
}
}
void ConnectRandomNodes(RandomListNode* pHead){
RandomListNode* pNode = pHead;
while(pNode != NULL){
RandomListNode* pClone = pNode->next;
if(pNode->random != NULL){
pClone->random = pNode->random->next;
}
pNode = pClone->next;
}
}
RandomListNode* ReconnectNodes(RandomListNode* pHead){
RandomListNode* pCloneHead = NULL;
RandomListNode* pNode = pHead;
RandomListNode* pCloneNode = NULL;
//获得原链表和复制链表的头指针
if(pNode != NULL){
pCloneHead = pNode->next;
pCloneNode = pCloneHead;
pNode->next = pCloneNode->next;
pNode = pNode->next;
}
//拆开两个链表
while(pNode != NULL){
pCloneNode->next = pNode->next;
pCloneNode = pCloneNode->next;
pNode->next = pCloneNode->next;
pNode = pNode->next;
}
return pCloneHead;
}
};
Python
# -*- coding:utf-8 -*-
# class RandomListNode:
# def __init__(self, x):
# self.label = x
# self.next = None
# self.random = None
class Solution:
# 返回 RandomListNode
def Clone(self, pHead):
# write code here
self.CloneNodes(pHead)
self.ConnectRandomNodes(pHead)
return self.ReconnectNodes(pHead)
def CloneNodes(self, pHead):
pNode = pHead
while pNode != None :
pClone = RandomListNode(pNode.label)
pClone.next = pNode.next
pNode.next = pClone
pNode = pClone.next
def ConnectRandomNodes(self, pHead):
pNode = pHead
while pNode != None:
pClone = pNode.next
if pNode.random != None:
pClone.random = pNode.random.next#这部很关键
pNode = pClone.next
def ReconnectNodes(self,pHead):
pNode = pHead
pCloneHead = None
pCloneNode = None
if pNode != None:
pCloneHead = pNode.next
pCloneNode = pCloneHead
pNode.next = pCloneNode.next
pNode = pNode.next
while pNode != None:
pCloneNode.next = pNode.next
pCloneNode = pCloneNode.next
pNode.next = pCloneNode.next
pNode = pNode.next
return pCloneHead
11.二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。一棵二叉搜索树及转换之后的排序双向链表如下图所示:
思路:
由于转换之后的链表是排好序的,所以可以采取中序遍历树种的每个节点,这是因为中序遍历算法的特点是按照从小到大的顺序遍历二叉树的每个节点。当遍历到根节点时,我们可以看做三个部分:1.值为10的节点。2.根节点为6的左子树。3.根节点为14的左子树。根据排序链表的定义,值为10的节点将和它左子树的最大的一个节点(值为8的节点)链接起来,同时和右子树最小的节点(值为12的节点)链接起来。如下图所示:
按照中序遍历的顺序:当遍历到根节点(值为10时),左子树已经转换成一个排序链表了,处于链表中的最后一个节点是当前值最大的节点。把该节点(值为8)和根节点链接起来,此时链表最后一个节点就是10了。接着遍历右子树,把根节点和右子树相连即可。使用递归操作。
代码:
C++
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree)
{
if(pRootOfTree == NULL){
return NULL;
}
TreeNode* listLastNode = NULL;//双向链表的最后一个节点
// 递归建立双向链表
ConvertNode(pRootOfTree, listLastNode);
TreeNode* pHead = listLastNode;
// 查找双向链表首节点
while(pHead && pHead->left){
pHead = pHead->left;
}
return pHead;
}
// 对BST中序遍历,得到有序序列;调整序列元素的指针,将有序序列调整为双向链表
void ConvertNode(TreeNode* pCurrentNode, TreeNode* &listLastNode){
// 边界条件(递归出口)
if(pCurrentNode == NULL){
return ;
}
// 遍历左子树
if(pCurrentNode->left)
ConvertNode(pCurrentNode->left, listLastNode);
// 建立双向链接
pCurrentNode->left = listLastNode; // 单侧链接
if(listLastNode)
listLastNode->right = pCurrentNode; // 单侧链接
listLastNode = pCurrentNode;
//遍历右子树
if(pCurrentNode->right)
ConvertNode(pCurrentNode->right, listLastNode);
}
};
Python
class Solution:
def Convert(self, pRootOfTree):
# write code here
if pRootOfTree == None:
return
#链接双向链表
self.ConvertNode(pRootOfTree)
#找到双向链表的头
pHead = self.listLastNode
while pHead and pHead.left:
pHead = pHead.left
return pHead
def ConvertNode(self, pCurrentNode):
if pCurrentNode == None:
return
#边界条件
if pCurrentNode.left != None:
self.ConvertNode(pCurrentNode.left )
pCurrentNode.left = self.listLastNode
if self.listLastNode != None:
self.listLastNode.right = pCurrentNode
self.listLastNode = pCurrentNode
if pCurrentNode.right != None:
self.ConvertNode(pCurrentNode.right)
def __init__(self):
#双向链表的尾部
self.listLastNode = None
12.序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
思路:
把下图二叉树进行序列化成字符串:“1,2,4,#,#,#,3,5,#,#,6,#,#”
序列化二叉树:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串。需要注意的是,序列化二叉树的过程中,如果遇到空节点,需要以某种符号(这里用0xFFFFFFFF或#)表示。
反序列化二叉树:根据某种遍历顺序得到的序列化字符串,重构二叉树。具体思路是按前序遍历“根左右”的顺序,根节点位于其左右子节点的前面,即非空(0xFFFFFFFF/ #)的第一个节点是某子树的根节点,左右子节点在该根节点后,以空节点(0xFFFFFFFF/ #)为分隔符。
代码:
C++
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<int> nodes;//保存序列化二叉树
char* Serialize(TreeNode *root) {
nodes.clear();//清空
SerializeCore(root);
// vector数组转int数组
int bufSize = nodes.size();
int *buff = new int[bufSize];
for(int i=0; i < bufSize; ++i)
buff[i]=nodes[i];
return (char*)buff;//强制类型转换
}
TreeNode* Deserialize(char *str) {
int *p=(int*)str;//强制类型转换
return DeserCore(p);
}
void SerializeCore(TreeNode *pNode){
if(pNode == NULL){
nodes.push_back(0xFFFFFFFF);//标记空节点
}else{
nodes.push_back(pNode->val);
SerializeCore(pNode->left);
SerializeCore(pNode->right);
}
}
TreeNode* DeserCore(int*& p){//此处需要加引用
if(*p == 0xFFFFFFFF){
p++;
return NULL;
}
TreeNode *root = new TreeNode(*p);
p++;
root->left = DeserCore(p);//递归构造左子树并连接到根节点的左孩子
root->right = DeserCore(p);//递归构造右子树并连接到根节点的右孩子
return root;
}
};
Python:
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def Serialize(self, root):
if not root:
return '#'
#前序遍历
return str(root.val) +',' + self.Serialize(root.left) +','+ self.Serialize(root.right)
def Deserialize(self, s):
list = s.split(',')
return self.deserializeTree(list)
def deserializeTree(self, list):
if len(list)<=0:
return None
val = list.pop(0)
root = None
if val != '#':
root = TreeNode(int(val))
root.left = self.deserializeTree(list)
root.right = self.deserializeTree(list)
return root
13.字符串的排列
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
思路:
以 abc 为例:
(1)将字符串分为两个部分,首元素以及剩余元素;
(2)将首元素不断和其他元素进行交换;
(3)以剩余元素组成的字符串内进行递归全排列;
(4)直到交换到最后一个元素,输出当前排列;
注意:
(1)重复元素需要跳过,否则会输出重复排列
(2)在得到子串的全排列后,需要将交换的元素恢复
(3) 需要按照字典顺序输出,所以需要对最终排列结果进行排序
参考图示:
代码:
C++
class Solution {
public:
vector<string> Permutation(string str) {
if (str.empty())
return vector<string>();
if (str.size()==1)
return vector<string> {str};
vector<string> res;
PermutationCore(str,0,res);
// 按字典序排列
sort(res.begin(),res.end());
return res;
}
void PermutationCore(string str ,int begin, vector<string> &res) {
if (begin >= str.size()){
res.push_back(str);
return;
}
for (int i = begin; i < str.size(); ++i) {
// 如果出现重复元素;跳过
if (str[begin] == str[i] && begin!=i){
continue;
}
// 将首元素和后面元素交换
char tmp = str[begin];
str[begin] = str[i];
str[i] = tmp;
PermutationCore(str,begin+1,res);//递归
// 将交换恢复
tmp = str[begin];
str[begin] = str[i];
str[i] = tmp;
}
}
};
Python
# -*- coding:utf-8 -*-
class Solution:
def Permutation(self, ss):
# write code here
#创建set() 一个集合
#set()函数创建一个无需不重复的元素集,不需要判断元素是否重复
output = set()
if len(ss) == 0:
return output
charList = list(ss)#把ss转换成列表
self.PermutationCore(charList, 0, output)
return sorted(list(output))#对所有可迭代的对象进行排序操作。
def PermutationCore(self, charList, index, output):
if index == len(charList) - 1:
#join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。
#add()给set()集合添加元素
output.add(''.join(charList[:]))
else:
for i in range(index, len(charList)):
#如果是重复的字符,则可以跳过
if charList[index] == charList[i] and index != i:
continue
else:
#依次与后面每个字符交换
charList[index], charList[i] = charList[i], charList[index]
self.PermutationCore(charList, index + 1, output)#递归
#交换回来
charList[index], charList[i] = charList[i], charList[index]