验证二叉搜索树c语言,2019 算法面试相关(leetcode)--树、二叉树、二叉搜索树

树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树

二叉树(Binary Tree)是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。

一棵深度为k,且有2^k-1个节点的二叉树,称为满二叉树。这种树的特点是每一层上的节点数都是最大节点数。而在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则此二叉树为完全二叉树。具有n个节点的完全二叉树的深度为floor(log2n)+1。深度为k的完全二叉树,至少有2k-1个叶子节点,至多有2k-1个节点。

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。

从二叉树的定义来看,算是比较适用递归算法的数据结构了

二叉树

function TreeNode(val) {

this.val = val;

this.left = this.right = null;

}

翻转一棵二叉树。

示例:

输入:

4

/ \

2 7

/ \ / \

1 3 6 9

输出:

4

/ \

7 2

/ \ / \

9 6 3 1

备注:

这个问题是受到 Max Howell的 原问题 启发的 :

谷歌:我们90%的工程师使用您编写的软件(Homebrew),但是您却无法在面试时在白板上写出翻转二叉树这道题,这太糟糕了。

又是经典的翻转。

直接用递归解法,分别递归地交换左右子树

var invertTree = function(root) {

if(!root) return root

let right = root.right

root.right = invertTree(root.left)

root.left = invertTree(right)

return root

};

给定一个二叉树,返回它的 前序 遍历。

示例:

输入: [1,null,2,3]

1

\

2

/

3

输出: [1,2,3]

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。

简单说其遍历顺序就是:根--左--右

使用递归可以很容易实现。

var preorderTraversal = function(root) {

return helper(root,[])

};

var helper = function(root,nums){

if(!root) return nums

nums.push(root.val)

helper(root.left,nums)

helper(root.right,nums)

return nums

}

不过题目有要求用迭代算法,这里我们可以想到之前提过的数据结构:栈

我们可以先将父结点的值放到数组里,然后将父节点入栈,等遍历完左子树,再出栈,去遍历右子树。

var preorderTraversal = function(root){

let treeStack = []

let res = []

while(root || treeStack.length){

while(root){

res.push(root.val)

treeStack.push(root)

root = root.left

}

if(treeStack.length){

root = treeStack.pop()

root = root.right

}

}

return res

}

给定一个二叉树,返回它的中序 遍历。

示例:

输入: [1,null,2,3]

1

\

2

/

3

输出: [1,3,2]

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。若二叉树为空则结束返回,否则:

(1)中序遍历左子树

(2)访问根结点

(3)中序遍历右子树

简单说其遍历顺序就是:左--根--右

同理,用递归也可以很简单的实现。

var inorderTraversal = function(root) {

return helper(root,[])

};

var helper = function(root,nums){

if(!root) return nums

helper(root.left,nums)

nums.push(root.val)

helper(root.right,nums)

return nums

}

如果不用递归,用迭代呢?一样,也可以用栈去实现,先把父结点入栈,去遍历左子树,然后出栈将父节点放进数组,在同样去遍历右子树

var inorderTraversal = function(root){

let treeStack = []

let res = []

while(root || treeStack.length){

while(root){

treeStack.push(root)

root = root.left

}

if(treeStack.length){

root = treeStack.pop()

res.push(root.val)

root = root.right

}

}

return res

}

给定一个二叉树,返回它的 后序 遍历。

示例:

输入: [1,null,2,3]

1

\

2

/

3

输出: [3,2,1]

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

后序遍历首先遍历左子树,然后遍历右子树,最后访问根结点,在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点。即:

若二叉树为空则结束返回,否则:

(1)后序遍历左子树

(2)后序遍历右子树

(3)访问根结点

简单说其遍历顺序就是:左--右--根

和前序中序一样,用递归也可以很简单的实现。

var postorderTraversal = function(root) {

return helper(root,[])

};

var helper = function(root,nums){

if(!root) return nums

helper(root.left,nums)

helper(root.right,nums)

nums.push(root.val)

return nums

}

那如果不用递归用迭代呢?

leetcode上的前序中序遍历两道题都是标注的中等难度,而这道题却是标注成困难难度。

因为如果还是用之前的思路即还是用栈,是没办法直接实现的。

我们可以以根-右-左顺序入栈,然后一直向左遍历,直至左右子树都为空。出栈并保存到数组中,然后一直重复以上,这里需要注意的是,由于父结点已经遍历过了,这里一直出栈会造成死循环。所以我们还需要引入一个参数pre,来记录上次遍历的树,如果是栈尾的左右子树,则说明该结点已经遍历过,不再重复遍历。

var postorderTraversal = function(root) {

if(!root) return []

let treeStack = [root]

let res = []

let pre

while(treeStack.length){

root = treeStack[treeStack.length - 1]

if((!root.left && !root.right) || (pre &&(pre == root.left || pre == root.right))){

res.push(root.val)

treeStack.pop()

pre = root

}else{

if(root.right) treeStack.push(root.right)

if(root.left) treeStack.push(root.left)

}

}

return res

}

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

节点的左子树只包含小于当前节点的数。

节点的右子树只包含大于当前节点的数。

所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:

2

/ \

1 3

输出: true

示例 2:

输入:

5

/ \

1 4

/ \

3 6

输出: false

解释: 输入为: [5,1,4,null,null,3,6]。

根节点的值为 5 ,但是其右子节点值为 4 。

验证二叉搜索树,其实就是验证其是否有序,我们可以对其进行中序遍历,如果遍历后的数据是有序的,则说明是二叉搜索树,反之则不是。

var isValidBST = function(root) {

if(!root) return true

let nums = []

helper(root,nums)

for(let i = 0; i < nums.length - 1; i++){

if(nums[i] >= nums[i + 1]) return false

}

return true

}

var helper = function(root,nums){

if(!root) return

helper(root.left,nums)

nums.push(root.val)

helper(root.right,nums)

}

那么能不能用o(1)的空间复杂度来解决呢?事实上我们没必要把数据都保存下来,对于右子树而言,只要所有右子树都大于其父节点即可,同理,所有左子树小于其父节点即可。

我们可以用两个变量:min和max,对于左子树,则判断是否小于min;对于右子树,则判断是否大于max

然后不断递归遍历,同时更新min和max

var isValidBST = function(root) {

return helper(root,undefined,undefined)

}

var helper = function(root,min,max){

if(!root ) return true

if(min != undefined && root.val <= min) return false

if(max != undefined && root.val >= max) return false

return helper(root.right,root.val,max) && helper(root.left,min,root.val)

}

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

image

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1

输出: 3

解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4

输出: 5

解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

说明:

所有节点的值都是唯一的。

p、q 为不同节点且均存在于给定的二叉树中。

首先可以确定如果root为null或者p或q是根节点,那么说明最近的公共祖先就是根结点root。

我们同样可以使用递归去实现,分别递归左右结点,将其设为根节点,如果返回null,说明公共祖先在另一侧,如果都为null,说明公共结点就是root

var lowestCommonAncestor = function(root, p, q) {

if(root == null || root == p || root == q) return root

let left = lowestCommonAncestor(root.left,p,q)

let right = lowestCommonAncestor(root.right,p,q)

return !left ? right : (!right ? left : root)

}

或者我们可以同时分别求p和q的最近祖先

var findLowestAncestor = function(root,p){

if(root == null || root == p || root.left == p || root.right == p) return root

return findLowestAncestor(root.left,p) || findLowestAncestor(root.right,p)

}

一层一层往上遍历,直到p和q的祖先相遇

var lowestCommonAncestor = function(root, p, q) {

let qSet = new Set()

qSet.add(q)

while(p != q){

if(qSet.has(p)) return p

while(q != root){

q = findAncestor(root,q)

if(p == q) return p

qSet.add(q)

}

p = findAncestor(root,p)

}

return p

};

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

image

示例 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 为不同节点且均存在于给定的二叉搜索树中。

题目中的p和q有可能两个都在同侧,在递归的过程中,另一侧则没有必要去递归了,但因为二叉树无序,所以上边的算法没办法去判断,就算p和q不在另一侧,也只能继续递归下去。

而在二叉搜索树中,上边的那个算法就可以优化下

如果p和q都小于root的值,则说明p和q都在左侧,向左边递归即可;

如果p和q都大于root的值,则说明p和q都在右侧,向右边递归即可;

否则,就说明p和q在root的左右两侧,则其最近公共祖先就是root。

如果(root.val - p.val) 和 (root.val - q.val)的积是正的,则说明他们在同一侧

var lowestCommonAncestor = function(root, p, q) {

return (root.val - p.val) * (root.val - q.val) <= 0 ? root : lowestCommonAncestor(root.val >= q.val ? root.left : root.right, p, q)

};

这里当然也可以不用递归

var lowestCommonAncestor = function(root, p, q) {

while ((root.val - p.val) * (root.val - q.val) > 0) root = p.val < root.val ? root.left : root.right

return root

};

你可能感兴趣的:(验证二叉搜索树c语言)