[leetcode刷题]——树(BST + Trie)

  此博客主要记录BST(Binary Search Tree)二叉搜索树, 和Trie 前缀树或者叫字典树。

  

一、修剪二叉查找树

669.修建二叉搜索树 (medium) 2021-07-06

给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树不应该改变保留在树中的元素的相对结构(即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在唯一的答案。

  使用递归的方法

 

class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        if(root == null) return null;
        if(root.val > high) return trimBST(root.left, low, high);
        if(root.val < low) return trimBST(root.right, low, high);
        root.left = trimBST(root.left, low, high);
        root.right = trimBST(root.right, low, high);
        return root;
    }
}

 

二、寻找二叉查找树的第k小的元素

230.二叉搜索树中第k小的元素   (medium)  2021-07-07

给定一个二叉搜索树的根节点root ,和一个整数k ,请你设计一个算法查找其中第k 个最小的元素(从1开始计数)。

 

class Solution {
    public int kthSmallest(TreeNode root, int k) {
        int kthSmall = -1;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode node = root;
        int count = 0;
        while(!stack.isEmpty() || node != null){
            while(node != null){
                stack.push(node);
                node = node.left;
            }
            TreeNode node_out = stack.pop();
            count++;
            if(count == k){
                kthSmall = node_out.val;
                break;
            }
            node = node_out.right;
        }
        return kthSmall;
    }
}

 

 

三、把二叉查找树每个节点的值都加上比他大的节点的值

538. 把二叉搜索树转换为累加树(medium) 2021-07-07

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

提醒一下,二叉搜索树满足下列约束条件:

    • 节点的左子树仅包含键 小于 节点键的节点。

    • 节点的右子树仅包含键 大于 节点键的节点。

    • 左右子树也必须是二叉搜索树。

  我的做法虽然有用但是很傻,常规解法,首先遍历得到所有节点的累加和,然后再次遍历一遍修改各个节点的值。

  

class Solution {
    public TreeNode convertBST(TreeNode root) {
        sum = inOrderSum(root);
        return inOrder(root);
    }
    
    //对二叉搜索树进行求和
    int sum = 0;
    public int inOrderSum(TreeNode root){
        if(root == null) return -1;
        inOrderSum(root.left);
        sum = sum + root.val;
        inOrderSum(root.right);
        return sum;
    }
    //对二叉树进行前序遍历,然后修改节点的值
    int preSum = 0;
    public TreeNode inOrder(TreeNode root){
        if(root == null) return null;
        inOrder(root.left);
        int temp = root.val;
        root.val = sum - preSum;
        preSum = preSum + temp;
        inOrder(root.right);
        return root;
    } 
}

  在二叉搜索树中我一直执着于中序遍历,但是这个题使用(右子树 -> 根节点 -> 左子树) 显然更方便。一次遍历就行,时间空间都暴打我的方法。

public TreeNode convertBST(TreeNode root) {
    traver(root);
    return root;
}

private void traver(TreeNode node) {
    if (node == null) return;
    traver(node.right);
    sum += node.val;
    node.val = sum;
    traver(node.left);
}

 

四、二叉查找树的最近公共祖先

235.二叉搜索树的最近公共祖先 (easy) 2021-07-07

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

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

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

  这个题我一点头绪都没有,答案只需要三行就解决了,破防了。

 public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
        if(root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
        return root;
    }

 

五、二叉树最近的公共祖先

236. 二叉树最近的公共祖先  (medium) 2021-07-07

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

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

  这个题和上面的题极其相似,难度增加,但是我依然没有头绪。

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null) return null;
        if(root == p || root == q) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if(left != null && right != null) return root;
        if(left != null) return left;
        if(right != null) return right;
        return null;
    }
}

 

六、从有序数组中构造二叉查找树

108.将有序数组转换成二叉搜索树 (easy) 2021-07-07

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。

高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        int len = nums.length;
        return sortedArrayToBST(nums, 0, len - 1);
    }
    //这个函数的作用是将从l 到 r 的下标的数组进行整理
    public TreeNode sortedArrayToBST(int[] nums, int l, int r){
        if(l > r)   return null;
        int m = l + (r - l) / 2;
        TreeNode root = new TreeNode(nums[m]);
        root.left = sortedArrayToBST(nums, l , m - 1);
        root.right = sortedArrayToBST(nums, m + 1, r);
        return root;
    }
}

 

七、根据有序链表构造平衡二叉查找树

109. 有序链表转换二叉搜索树  (medium) 2021-07-07

给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

    我用数组的思路去解这个题,用时击败 5%的用户。

  评论里说到,思想的懒惰不利于写出牛逼的程序,我深以为然。

class Solution {
    public TreeNode sortedListToBST(ListNode head) {
        ListNode node = head;
        int count = 0;  //链表的长度
        while(node != null){
            count ++;
            node = node.next;
        }
        return toBST(head, 0, count - 1);
    }
    public TreeNode toBST(ListNode head, int l, int r){
        if(l > r) return null;
        int m = l + (r - l) / 2;
        TreeNode root = new TreeNode(searchList(head, m));
        root.left = toBST(head, l, m - 1);
        root.right = toBST(head, m + 1, r);
        return root;
    }
    //寻找链表中下标为m 的元素, m小于链表的长度
    public int searchList(ListNode head, int m){
        ListNode node = new ListNode();
        node.next = head;
        for(int i = 0; i <= m; i++){
            node = node.next;
        }
        return node.val;
    }
}

   碰到链表找中点的算法,使用快慢指针,让快的指针一次移动两格,慢的一次一格。当快指针到队尾的时候,慢指针正好是在链表中间。

  链表相对于数组有自己的优势,其不需要记录数组的下标,只需要将链表断开就行。

  转换思路,用时击败 100%

class Solution {
    public TreeNode sortedListToBST(ListNode head) {
        if(head == null) return null;
        if(head.next == null) return new TreeNode(head.val);
        ListNode fastNode = head;
        ListNode slowNode = head;
        ListNode midLeftNode = head;
        while(fastNode != null && fastNode.next != null){
            fastNode = fastNode.next.next;
            midLeftNode = slowNode;
            slowNode = slowNode.next;
        }
        TreeNode root = new TreeNode(slowNode.val);
        midLeftNode.next = null; //将中间节点的左边节点的next指针置为null
        root.left = sortedListToBST(head);
        root.right = sortedListToBST(slowNode.next);
        return root;
    }
}

 

 

八、在二叉查找树中寻找两个节点,使它们的和为一个定值

653. 两数之和IV  ( BST)  (easy) 2021-07-07

给定一个二叉搜索树和一个目标结果,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true。

  思想需要简单,将二叉查找树中序遍历为有序列表,然后对列表进行查找。

class Solution {
    public boolean findTarget(TreeNode root, int k) {
        List<Integer> l = TreeToList(root);
        int i = 0;
        int j = l.size() - 1;
        while(i < j){
            int sum = l.get(i) + l.get(j);
            if(sum == k) return true;
            if(sum > k){
                j--;
            }else{
                i++;
            }
        }
        return false;
    }
    List<Integer> list = new ArrayList<>();
    public List<Integer> TreeToList(TreeNode root){
        if(root == null) return null;
        TreeToList(root.left);
        list.add(root.val);
        TreeToList(root.right);
        return list;
    }
}

 

九、在二叉查找树中查找两个节点之差的最小绝对值

530. 二叉搜索树的最小绝对值   (easy) 2021-07-08

给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。

   思想上又偷懒了。这个题我将二叉搜索树转换为有序列表,然后在列表的基础上寻找两节点差的最小值。时间击败76% ,空间击败 11%

  事实上并不需要转换为有序列表,这一步增加了空间使用。

class Solution {
    public int getMinimumDifference(TreeNode root) {
        List<Integer> l = inOrder(root);
        int len = l.size();
        int minDiffer = l.get(1) - l.get(0);
        for(int i = 2; i < len ; i++){
            minDiffer = Math.min(minDiffer, (l.get(i) - l.get(i - 1)));
        }
        return minDiffer;
    }
    List<Integer> list = new ArrayList<>();
    public List<Integer> inOrder(TreeNode root){
        if(root == null) return null;
        inOrder(root.left);
        list.add(root.val);
        inOrder(root.right);
        return list;
    }
}

  修改后的代码如下; 时间击败100% , 空间击败 97%

 

class Solution {
    private int minDiffer = Integer.MAX_VALUE;
    private TreeNode preNode = null;
    public int getMinimumDifference(TreeNode root) {
        inOrder(root);
        return minDiffer;
    }

    private void inOrder(TreeNode root){
        if(root == null) return ;
        inOrder(root.left);
        if(preNode != null) minDiffer = Math.min(minDiffer, root.val - preNode.val);
        preNode = root;
        inOrder(root.right);
        return ;
    }
}

 

 

 

 十、寻找二叉查找树中出现次数最多的值

 501、二叉搜索树中的众数  (easy) 2021-07-08

给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

假定 BST 有如下定义:

结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树
例如:
给定 BST [1,null,2,2],

    耐心一点,复杂一点的题还是可以做的。

  需要注意的是,如果preNode直接赋值为 new TreeNode() 会给 val  赋值为 0,可废了不少功夫找bug。

  另,这个题官方测试用例有bug, [1,null,0,0,1,1,0] 这玩意儿不是二叉搜索树

 

class Solution {
    private List<Integer> list = new ArrayList<>();
    private int maxFre = 0;
    private int curFre = 1;
    //private TreeNode preNode = new TreeNode();
    private TreeNode preNode = null;
    public int[] findMode(TreeNode root) {
        inOrder(root);
        int len = list.size();
        int[] ret = new int[len];
        for(int i = 0; i < len; i++){
            ret[i] = list.get(i);
        }
        return ret;
    }
    private void inOrder(TreeNode root){
        if(root == null) return;
        inOrder(root.left);
        if(preNode != null && root.val == preNode.val){
            curFre++;
        }
        if(curFre == maxFre){
            list.add(root.val);
        }else if(curFre > maxFre){
            list.clear();
            list.add(root.val);
            maxFre = curFre;
            curFre = 1;
        }
        preNode = root;
        inOrder(root.right);
    }
}

 

Trie

一、 实现一个Trie  

208. 实现(Trie)前缀树 (medium)   2021-07-10

Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。

请你实现 Trie 类:

Trie() 初始化前缀树对象。
void insert(String word) 向前缀树中插入字符串 word 。
boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。

  !  !  !  这个题我并没有理解,以下是 答案

class Trie {
    
    private class TrieNode{  //每个节点最多有26个不同的小写字母
        private boolean isEnd;
        private TrieNode[] next;
        public TrieNode(){
            isEnd = false;
            next = new TrieNode[26];
        }
    }
    private TrieNode root;

    /** Initialize your data structure here. */
    public Trie() {
        root = new TrieNode();
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        TrieNode cur = root;
        for(int i = 0, len = word.length(), ch; i < len; i++){
            ch = word.charAt(i) - 'a';
            if(cur.next[ch] == null){
                cur.next[ch] = new TrieNode();
            }
            cur = cur.next[ch];
        }
        cur.isEnd = true;

    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        TrieNode cur = root;
        for(int i = 0, len = word.length(), ch; i < len; i++){
            ch = word.charAt(i) - 'a';
            if(cur.next[ch] == null){
                return false;
            }
            cur = cur.next[ch];
        }
        return cur.isEnd;

    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        TrieNode cur = root;
        for(int i = 0, len = prefix.length(), ch; i < len; i++){
            ch = prefix.charAt(i) - 'a';
            if(cur.next[ch] == null){
                return false;
            }
            cur = cur.next[ch];
        }
        return true;
    }
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */

 

二、实现一个Trie, 用来求前缀和

677. 键值映射   (medium)  2021-07-10

实现一个 MapSum 类,支持两个方法,insert 和 sum:

MapSum() 初始化 MapSum 对象
void insert(String key, int val) 插入 key-val 键值对,字符串表示键 key ,整数表示值 val 。如果键 key 已经存在,那么原来的键值对将被替代成新的键值对。
int sum(string prefix) 返回所有以该前缀 prefix 开头的键 key 的值的总和。

  这个题还没有解决,未完待续。

 

posted @ 2021-07-07 21:56  -野比大雄-  阅读(129)  评论(0)    收藏  举报