Tag Archives: BST

剑指 Offer 36. 二叉搜索树与双向链表

剑指 Offer 36. 二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

为了让您更好地理解问题,以下面的二叉搜索树为例:

我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。

特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

注意:本题与主站 426 题相同:https://leetcode-cn.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/

注意:此题对比原题有改动。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


如何将二叉搜索树转换in-place转换为有序双向链表。

此题同时考察了多个知识点,首先二叉搜索树转换为有序结果,需要使用二叉树的中序遍历,然后要in-place转换为双向链表,则需要在遍历二叉树的过程中,对每个节点,修改其left和right指针,使其分别指向转换后的双向链表的前驱和后继节点。

使用递归的方法,设置两个全局变量pre和head,分别表示当前遍历节点的前驱节点,以及转换后的双向链表的头结点。完整代码如下:

class Solution {
private:
    Node *pre, *head;
    void DFS(Node *root) {
        if(root == NULL) return;

        DFS(root->left);
        if(head == NULL) head = root;
        else pre->right = root;
        
        root->left = pre;
        pre = root;
        DFS(root->right);
    }
public:
    Node* treeToDoublyList(Node* root) {

        if(root == NULL) return NULL;

        pre = head = NULL;

        DFS(root);

        head->left = pre;
        pre->right = head;

        return head;
    }
};

本代码提交AC,用时12MS。

LeetCode Construct Binary Search Tree from Preorder Traversal

1008. Construct Binary Search Tree from Preorder Traversal

Return the root node of a binary search tree that matches the given preorder traversal.

(Recall that a binary search tree is a binary tree where for every node, any descendant of node.left has a value < node.val, and any descendant of node.right has a value > node.val.  Also recall that a preorder traversal displays the value of the node first, then traverses node.left, then traverses node.right.)

Example 1:

Input: [8,5,1,7,10,12]
Output: [8,5,10,1,7,null,12]

Note: 

  1. 1 <= preorder.length <= 100
  2. The values of preorder are distinct.

根据二叉搜索树的先序遍历结果,构造该二叉搜索树。

简单题。先序遍历是根、左、右,又因为是二叉搜索树,所以左<根<右,所以数组中第一个元素是根节点,然后找到数组右边第一个大于根节点的树,把数组划分为左子树和右子树,然后递归构造。

完整代码如下:

class Solution {
public:
	TreeNode* Work(vector<int> &preorder, int l, int r) {
		if (l > r)return NULL;
		if (l == r)return new TreeNode(preorder[l]);
		TreeNode *root = new TreeNode(preorder[l]);
		int mid = -1;
		for (int i = l; i <= r; ++i) {
			if (preorder[i] > root->val) {
				mid = i;
				break;
			}
		}
		if (mid == -1)root->left = Work(preorder, l + 1, r);
		else {
			root->left = Work(preorder, l + 1, mid - 1);
			root->right = Work(preorder, mid, r);
		}
		return root;
	}
	TreeNode* bstFromPreorder(vector<int>& preorder) {
		return Work(preorder, 0, preorder.size() - 1);
	}
};

本代码提交AC,用时8MS。

LeetCode Balance a Binary Search Tree

1382. Balance a Binary Search Tree

Given a binary search tree, return a balanced binary search tree with the same node values.

A binary search tree is balanced if and only if the depth of the two subtrees of every node never differ by more than 1.

If there is more than one answer, return any of them.

Example 1:

Input: root = [1,null,2,null,3,null,4,null,null]
Output: [2,1,3,null,null,null,4]
Explanation: This is not the only correct answer, [3,1,4,null,2,null,null] is also correct.

Constraints:

  • The number of nodes in the tree is between 1 and 10^4.
  • The tree nodes will have distinct values between 1 and 10^5.

给定一棵二叉搜索树,要求把这棵二叉搜索树进行平衡化处理。一棵平衡二叉搜索树首先要是一棵二叉搜索树,其次要是平衡的,平衡的意思是左右子树的高度差不超过1。

要构造一棵平衡二叉搜索树,其树的根节点应该是整个有序数组的中位数。所以先对原来的二叉搜索树先序遍历,得到有序数组,然后取数组中位数作为根节点,在左右子数组中递归构造二叉搜索树。

完整代码如下:

class Solution {
public:
	void preIter(TreeNode* root, vector<int> &nums) {
		if (root == NULL)return;
		preIter(root->left, nums);
		nums.push_back(root->val);
		preIter(root->right, nums);
	}
	TreeNode* constructBST(vector<int> &nums, int l, int r) {
		if (l > r)return NULL;
		if (l == r)return new TreeNode(nums[l]);

		int mid = l + (r - l) / 2;
		TreeNode* root = new TreeNode(nums[mid]);
		root->left = constructBST(nums, l, mid - 1);
		root->right = constructBST(nums, mid + 1, r);
		return root;
	}
	TreeNode* balanceBST(TreeNode* root) {
		vector<int> nums;
		preIter(root, nums);
		int n = nums.size();
		return constructBST(nums, 0, n - 1);
	}
};

本代码提交AC,用时180MS。

LeetCode Contains Duplicate III

220. Contains Duplicate III

Given an array of integers, find out whether there are two distinct indices i and j in the array such that the absolute difference between nums[i] and nums[j] is at most t and the absolute difference between i and j is at most k.

Example 1:

Input: nums = [1,2,3,1], k = 3, t = 0
Output: true

Example 2:

Input: nums = [1,0,1,1], k = 1, t = 2
Output: true

Example 3:

Input: nums = [1,5,9,1,5,9], k = 2, t = 3 Output: false

给定一个数组,问是否存在两个相距k之内的差的绝对值在t之内的数。
这个题有点难度,是LeetCode Contains Duplicate II的升级版。
首先,要求距离在k之内,所以想到维护一个长度为k的滑动窗口,在这个滑动窗口内判断是否有差的绝对值在t之内的两个数。
具体参考讨论区。用一个set来保存长度为k的滑动窗口window,每次往前滑动一格,如果窗口大于k了,则删除窗口左边的元素。对于即将进入窗口的数nums[i],我们需要判断窗口内是否存在一个x,使得$|x-nums[i]|\leq t$,展开就是$nums[i]-t\leq x\leq t+nums[i]$。
由于set内部是有序的树结构,所以可以用set.lower_bound直接判断大于等于$nums[i]-t$的数x是否在window中。如果在的话,我们再判断是否满足$x\leq t+nums[i]$,这个式子等价于$x-nums[i]\leq t$,为什么要这么写呢,因为nums[i]有可能是INT_MAX或者INT_MIN,为了防止溢出,需要把相关的操作数转换为long long。
完整代码如下:

typedef long long ll;
class Solution {
public:
    bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t)
    {
        set<ll> window;
        for (int i = 0; i < nums.size(); ++i) {
            if (i > k)
                window.erase(nums[i – k – 1]); // 删除窗口左边的元素
            auto it = window.lower_bound(ll(nums[i]) – t); // x>=nums[i]-t
            if (it != window.end() && *it – nums[i] <= t)
                return true; // x<=nums[i]+t
            window.insert(nums[i]);
        }
        return false;
    }
};

本代码提交AC,用时19MS。

LeetCode Kth Smallest Element in a BST

230. Kth Smallest Element in a BST

Given a binary search tree, write a function kthSmallest to find the kth smallest element in it.

Example 1:

Input: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
Output: 1

Example 2:

Input: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
Output: 3

Follow up:
What if the BST is modified (insert/delete operations) often and you need to find the kth smallest frequently? How would you optimize the kthSmallest routine?

Constraints:

  • The number of elements of the BST is between 1 to 10^4.
  • You may assume k is always valid, 1 ≤ k ≤ BST's total elements.

给定一棵二叉搜索树,问树中第k小的元素是哪个。 因为BST的特点是左<=根<=右,所以对树中序遍历之后就得到升序序列,取出第k个元素即可。代码如下:

class Solution {
private:
    void inOrder(TreeNode* root, int& k, int& target)
    {
        if (k < 0)
            return;
        if (root->left)
            inOrder(root->left, k, target);
        –k;
        if (k == 0)
            target = root->val;
        if (root->right)
            inOrder(root->right, k, target);
    }
public:
    int kthSmallest(TreeNode* root, int k)
    {
        int target;
        inOrder(root, k, target);
        return target;
    }
};

本代码提交AC,用时26MS,时间复杂度是O(k)。
Follow up指出如果可以修改节点,怎样才能使得复杂度降低到O(height of tree),可以给每个节点增加一个属性rank:左子树节点个数,该属性的含义就是小于该节点的节点个数,也即当前节点在中序遍历中的位置(rank)。查找的过程就类似二分了,从根节点开始,如果k小于当前节点的rank,说明第k小的数在左子树;如果k大于当前节点的rank,说明第k小的数在右子树;如果k==rank,则当前节点就是第k小的数。这样的查找过程就是不断的在当前节点选择走左子树还是右子树,走过的节点最多是树的高度。
rank属性可以在建BST时完成。

LeetCode Convert BST to Greater Tree

LeetCode Convert BST to Greater Tree Given a Binary Search Tree (BST), convert it to a Greater Tree such that every key of the original BST is changed to the original key plus sum of all keys greater than the original key in BST. Example:

Input: The root of a Binary Search Tree like this:
              5
            /   \
           2     13
Output: The root of a Greater Tree like this:
             18
            /   \
          20     13

给定一棵二叉搜索树,要求把每个点的值换成BST中大于等于该点的其他所有点的值之和。 很有意思的一个题,因为BST满足左<=根<=右,则比某个点大的点在其根节点和右兄弟上,为了得到根节点和右兄弟节点之和,可以采用右->根->左的遍历顺序,也就是和先序遍历正好相反的顺序遍历BST,同时记录遍历过的数之和。 代码如下: [cpp] class Solution { private: void revPreOrder(TreeNode* root, int& sum) { if (root->right)revPreOrder(root->right, sum); sum += root->val; root->val = sum; if (root->left)revPreOrder(root->left, sum); } public: TreeNode* convertBST(TreeNode* root) { if (!root)return NULL; int sum = 0; revPreOrder(root, sum); return root; } }; [/cpp] 本代码提交AC,用时46MS。]]>

LeetCode Minimum Absolute Difference in BST

LeetCode Minimum Absolute Difference in BST Given a binary search tree with non-negative values, find the minimum absolute difference between values of any two nodes. Example:

Input:
   1
    \
     3
    /
   2
Output:
1
Explanation:
The minimum absolute difference is 1, which is the difference between 2 and 1 (or between 2 and 3).
Note: There are at least two nodes in this BST.
求一棵二叉搜索树中任意两个节点数值之差的绝对值的最小值。 因为是BST,所以满足左子树<=根节点<=右子树。以根节点root为例,则最小的差可能有两个,一个是root减去左子树中的最大数,另一个是右子树中的最小数减去root,所以整体的最小值就是这两个最小值的最小值。 左子树中的最大数就是左子树中最右下端的叶子节点,右子树中的最小数就是右子树中最左下端的叶子节点。 然后递归的以左右孩子为根更新最小值。代码如下: [cpp] class Solution { public: int getMinimumDifference(TreeNode* root) { int l = INT_MIN, r = INT_MAX; int left_min = INT_MAX, right_min = INT_MAX; if (root->left) { TreeNode* left = root->left; while (left->right)left = left->right; left_min = min(root->val – left->val, getMinimumDifference(root->left)); } if (root->right) { TreeNode* right = root->right; while (right->left)right = right->left; right_min = min(right->val – root->val, getMinimumDifference(root->right)); } return min(left_min, right_min); } }; [/cpp] 本代码提交AC,用时19MS。 还有一种思路,因为是BST,所以其中序遍历的结果是升序的,得到有序数组之后,相邻两个数的差的最小值就是要求的结果。 实际上,我们可以不用完整求出其升序序列之后再计算差,只需要在每次中序遍历时,记住该节点的上一个节点,这样该节点的值减去上一节点的值就相当于有序数组中相邻两个数作差。 代码如下: [cpp] class Solution { private: long long m_nLast, m_nAns; void inorderTraverse(TreeNode* root) { if (!root)return; inorderTraverse(root->left); // 左 m_nAns = min(m_nAns, root->val – m_nLast); m_nLast = root->val; // 根 inorderTraverse(root->right); // 右 } public: Solution() { m_nLast = INT_MIN; m_nAns = INT_MAX; } int getMinimumDifference(TreeNode* root) { inorderTraverse(root); return m_nAns; } }; [/cpp] 本代码提交AC,用时16MS。注意因为可能出现x-INT_MIN导致int溢出,所以两个成员变量定义为long long。 参考:http://bookshadow.com/weblog/2017/02/26/leetcode-minimum-absolute-difference-in-bst/]]>

LeetCode Delete Node in a BST

LeetCode Delete Node in a BST Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return the root node reference (possibly updated) of the BST. Basically, the deletion can be divided into two stages:

  1. Search for a node to remove.
  2. If the node is found, delete the node.
Note: Time complexity should be O(height of tree). Example:
root = [5,3,6,2,4,null,7]
key = 3
    5
   / \
  3   6
 / \   \
2   4   7
Given key to delete is 3. So we find the node with value 3 and delete it.
One valid answer is [5,4,6,2,null,null,7], shown in the following BST.
    5
   / \
  4   6
 /     \
2       7
Another valid answer is [5,2,6,null,4,null,7].
    5
   / \
  2   6
   \   \
    4   7

从二叉搜索树中删除一个节点,并返回一个新的二叉搜索树;如果该节点不存在,则返回原二叉搜索树。 因为是BST,所以很容易找到等于value的节点,问题的关键是怎样删除这个节点,并且保持二叉搜索树的特性:左孩子<根节点<右孩子。 如果要删除的节点的左子树为空,则删除该节点,返回该节点的右子树即可;类似的,如果要删除的节点的右子树为空,则删除该节点,返回该节点的左子树。 难点就是当该节点的左子树和右子树都不为空时,该怎么处理。如下图,假设我们找到了root的值等于key,则需要删除root节点,并调整其子树使满足BST的性质。
       root
      /    \
   left  right
  /   \  /   \
 x    l r     y
具体有两种方案,题目中也举了例子,即可以把left放到root的位置,或者把right放到root的位置。 现在假设要把left放到root的位置,则需要处理left的右子树l。l肯定要被切下来,又l比left大,所以l应该接在right子树,具体位置是接在right子树的最小节点上,即right的最左下端的叶子节点r。也就是变成下面这样:
    left            right
    /  \            /   \
   x               r     y
                  /
                 l 
最后把right子树接在left右子树即可:
            left
            /  \
           x    right
                /   \
               r     y
              /
             l  
知道了调整的思路,代码就好写了: [cpp] class Solution { private: TreeNode* justify(TreeNode *root) { if (root->left == NULL || root->right == NULL)return root->left == NULL ? root->right : root->left; TreeNode *left = root->left, *right = root->right; TreeNode *l = left->right, *r = right; while (r->left) r = r->left; r->left = l; left->right = right; return left; } public: TreeNode* deleteNode(TreeNode* root, int key) { TreeNode *dummy = new TreeNode(-1), *pre = dummy, *child = root; dummy->right = root; while (child != NULL && child->val != key) { pre = child; if (key < child->val) child = child->left; else child = child->right; } if (child == NULL)return dummy->right; if (pre->left == child)pre->left = justify(child); else pre->right = justify(child); return dummy->right; } }; [/cpp] 本代码提交AC,用时33MS。 如果要把right放到root的位置,思路是类似的,需要把right的左子树切下来接到left最右下端的叶子节点上。 参考:https://segmentfault.com/a/1190000005032508]]>

LeetCode Validate Binary Search Tree

98. Validate Binary Search Tree

Given a binary tree, determine if it is a valid binary search tree (BST).

Assume a BST is defined as follows:

  • The left subtree of a node contains only nodes with keys less than the node’s key.
  • The right subtree of a node contains only nodes with keys greater than the node’s key.
  • Both the left and right subtrees must also be binary search trees.

Example 1:

    2
   / \
  1   3

Input: [2,1,3]
Output: true

Example 2:

    5
   / \
  1   4
     / \
    3   6

Input: [5,1,4,null,null,3,6]
Output: false
Explanation: The root node's value is 5 but its right child's value is 4.

验证一棵二叉树是否是二叉搜索树。二叉搜索树树需要满足左孩子小于等于根节点,根节点小于等于右孩子,本题中是严格的小于关系,所以二叉搜索树的中序遍历是严格递增的。一种解法是对二叉树中序遍历,然后检查遍历结果是否是严格递增的,这种解法很简单,不赘述了。
另外一种解法是直接用递归判断,代码如下。我们在每一次递归时,都判断当前节点是否大于最小值,且小于最大值,同时把当前节点的值递归带入。代码中没有求min和max的过程,但是每次调用的时候能正确检查,里面的奥秘需要自己体会,不太好讲。
举个例子吧,下图中节点6违反了BST的规则,虽然13所在子树是BST,但是6小于根节点10,所以不是BST。本代码是怎样判断出来的呢,首先根节点传入右孩子的约束是[10,max],15传入左孩子的约束是[10,15],13传入左孩子的约束是[10,13],所以6并不属于[10,13],不属于BST。

    10
   / \
  5   15
     /  \
    13   20
   /  \
  6   14
class Solution {
public:
    bool helper(TreeNode* root, long long minVal, long long maxVal)
    {
        if (root == NULL)
            return true;
        if (root->val <= minVal || root->val >= maxVal)
            return false;
        return helper(root->left, minVal, root->val) && helper(root->right, root->val, maxVal);
    }
    bool isValidBST(TreeNode* root)
    {
        long long minVal = LLONG_MIN, maxVal = LLONG_MAX;
        return helper(root, minVal, maxVal);
    }
};

本代码提交AC,用时13MS。

二刷。原理相同,更好理解的代码:

class Solution {
public:
	bool isBST(TreeNode* root, int &max_val, int &min_val) {
		if (root == NULL) {
			max_val = INT_MIN;
			min_val = INT_MAX;
			return true;
		}
		// 以root为根的子树的最大和最小值
		max_val = root->val;
		min_val = root->val;
		if (root->left == NULL && root->right == NULL) {
			return true;
		}
		if (root->left) {
			int left_max = INT_MIN, left_min = INT_MAX;
			bool left_tree = isBST(root->left, left_max, left_min);
			if (!left_tree || left_max >= root->val)return false;
			min_val = min(min_val, left_min);
		}
		
		if (root->right) {
			int right_max = INT_MIN, right_min = INT_MAX;
			bool right_tree = isBST(root->right, right_max, right_min);
			if (!right_tree || right_min <= root->val)return false;
			max_val = max(max_val, right_max);
		}
		return true;
	}

	bool isValidBST(TreeNode* root) {
		int max_val = INT_MIN, min_val = INT_MAX;
		return isBST(root, max_val, min_val);
	}
};

本代码提交AC,用时12MS。

LeetCode Find Mode in Binary Search Tree

LeetCode Find Mode in Binary Search Tree Given a binary search tree (BST) with duplicates, find all the mode(s) (the most frequently occurred element) in the given BST. Assume a BST is defined as follows:

  • The left subtree of a node contains only nodes with keys less than or equal to the node’s key.
  • The right subtree of a node contains only nodes with keys greater than or equal to the node’s key.
  • Both the left and right subtrees must also be binary search trees.
For example: Given BST [1,null,2,2],
   1
    \
     2
    /
   2
return [2]. Note: If a tree has more than one mode, you can return them in any order. Follow up: Could you do that without using any extra space? (Assume that the implicit stack space incurred due to recursion does not count).
找到二叉搜索树中所有的众数,如果有多个众数,都要给出。并且Follow up限制空间复杂度为O(1)。 很有意思的一个题,如果把二叉搜索树当成一棵普通的树,并且没有O(1)空间复杂度的限制,则在遍历时,可以使用hash统计每个数出现的频率,然后取最高频率的数。代码如下: [cpp] class Solution { public: void work(unordered_map<int, int>& hash, TreeNode* root, int& modCnt) { if (root == NULL)return; ++hash[root->val]; modCnt = max(modCnt, hash[root->val]); work(hash, root->left, modCnt); work(hash, root->right, modCnt); } vector<int> findMode(TreeNode* root) { unordered_map<int, int> hash; int modCnt = INT_MIN; work(hash, root, modCnt); vector<int> ans; unordered_map<int, int>::iterator it = hash.begin(); while (it != hash.end()) { if (it->second == modCnt)ans.push_back(it->first); ++it; } return ans; } }; [/cpp] 本代码提交AC,用时25MS。 如果使用二叉搜索树的特点并且考虑O(1)的空间复杂度,就有点复杂了。因为二叉搜索树的左孩子小于等于根节点,根节点小于等于右孩子,所以如果对二叉搜索树中序遍历的话,得到的序列就是一个非递减序列,也就是说是一个有序序列,那就好办多了。比如给定序列[1,2,2,3,4,4,5,6],要找出数组中的众数,并且限制O(1)空间复杂度,应该就不难了。 因为要O(1)空间复杂度,就不能用hash统计频率了,因为众数可能有多个,我们没法在一次遍历时就既找到最大的频率,又找到众数,所以可以采取两次遍历的策略。第一遍先找到最大的频率,上面的例子中是2(2和4都出现了两次);第二遍再把频率等于2的数值找出来,就是众数了。 对应到二叉搜索树中,就是对树中序遍历两次,第一次得到最大的频率以及众数的个数,第二遍再把所有众数都找出来。代码如下: [cpp] class Solution { private: vector<int> mods; int curVal = 0, curCnt = 0, modCnt = 0, maxCnt = 0; void handleValue(int v) { if (v != curVal) { curVal = v; curCnt = 0; } ++curCnt; if (curCnt > maxCnt) { // 第二次遍历时不会走此分支 maxCnt = curCnt; modCnt = 1; } else if (curCnt == maxCnt) { if (!mods.empty())mods[modCnt] = curVal; ++modCnt; } } void inorder(TreeNode* root) { if (root == NULL)return; inorder(root->left); handleValue(root->val); inorder(root->right); } public: vector<int> findMode(TreeNode* root) { inorder(root); mods.resize(modCnt); curCnt = 0; modCnt = 0; inorder(root); return mods; } }; [/cpp] 本代码提交AC,用时19MS。 参考:https://discuss.leetcode.com/topic/77335/proper-o-1-space]]>