刷题记录04

力扣530.二叉搜索树的最小绝对值

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。

差值是一个正数,其数值等于两值之差的绝对值。

  • 树中节点的数目范围是 [2, 104]
  • 0 <= Node.val <= 105

解题思路:

这题的解题思路和98.验证二叉搜索树的思路是一样的,中序遍历的二叉搜索树节点值是升序的,我们只需要保证按照中序遍历的顺序访问节点值,然后记录当前节点与前一个节点值的差值得的最小值即可。

解法一:递归法

递归法参考98.验证二叉搜索树的方法,可以将所有节点搜集到list或数组中,遍历比较,也可以直接在递归过程中,比较前一个节点和当前节点的差值。

收集节点值:

    public int getMinimumDifference(TreeNode root) {
        int min = Integer.MAX_VALUE;
        List list = new ArrayList<>();
        getMinimumInorderNode(root, list);
        for (int i = 1; i < list.size(); i++) {
            if (list.get(i) - list.get(i-1) < min) min = list.get(i) - list.get(i-1);
        }
        return min;
    }

    /**
     * 以中序遍历的方式将节点存到list中
     * @param root
     * @param list
     */
    public void getMinimumInorderNode(TreeNode root,List list) {
        if (root == null) return;
        getMinimumInorderNode(root.left, list);
        list.add(root.val);
        getMinimumInorderNode(root.right, list);
    }

递归过程中,直接比较节点值之差

class Solution {
    TreeNode preNode1 = null;
    int minAbs = Integer.MAX_VALUE;
    public int getMinimumDifference(TreeNode root) {
        getMinimumInorderNode1(root);
        return minAbs;
    }

    /**
     * 以中序遍历顺序遍历节点值
     * @param curNode
     */
    public void getMinimumInorderNode1(TreeNode curNode){
        if (curNode == null) return;
        getMinimumInorderNode1(curNode.left);// 左
        if (preNode1 != null && curNode.val - preNode1.val < minAbs) {
            minAbs = curNode.val - preNode1.val;// 中
        }
        preNode1 = curNode;// 前一个节点更新
        getMinimumInorderNode1(curNode.right);// 右
    }
}

解法二:迭代法

同样的,本题也能使用迭代法进行改进,只需要在访问当前节点的时候将当前节点与前一个节点差值与当前最小值比较即可。

class Solution {
    public int getMinimumDifference(TreeNode root) {
        int min = Integer.MAX_VALUE;
        Stack stack  = new Stack<>();
        TreeNode preNode = null;
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode curNode = stack.pop();
            if (curNode != null) {
                if (curNode.right != null) stack.push(curNode.right);
                stack.push(curNode);
                stack.push(null);
                if (curNode.left != null) stack.push(curNode.left);
            }else {
                curNode = stack.pop();
                if (preNode != null && curNode.val - preNode.val < min) min = curNode.val - preNode.val;
                preNode = curNode;
            }
        }
        return min;
    }
}

力扣501.二叉搜索树中的众数

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

力扣501.二叉搜索树中的众数

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

解法一:统计节点节点频率

解题思路:

通过遍历整个二叉树,将所有节点的频率用Map存储,然后在二叉树中,寻找出现频率最高的节点放入结果集中,返回。

这里二叉树的遍历方式不关键,只要保证遍历完所有节点即可。

采用统计节点值评率的方式适合于任意二叉树,不仅仅是本题的二叉搜索树。

代码实现:

class Solution {
    public int[] findMode(TreeNode root) {
        Map map = new HashMap<>();
        mapAllTreeNode(root, map);
        int maxCount = 0;// 当前最大频率
        List  list = new ArrayList<>();
        for (Map.Entry m : map.entrySet()) {
            if (m.getValue() > maxCount) {// 更新最大频率
                maxCount = m.getValue();
                list = new ArrayList<>();// 清空List
                list.add(m.getKey());
            }else if (m.getValue() == maxCount){// 最大频率相同,添加到List中
                list.add(m.getKey());
            }
        }
        // 将List转为int[]
        int[] res = list.stream().mapToInt(Integer::intValue).toArray();
        return res;
    }

    /**
     * 用map统计整棵树中节点值的频率
     * @param curNode
     * @param map
     */
    public void mapAllTreeNode(TreeNode curNode, Map map) {
        if (curNode == null) return;
        mapAllTreeNode(curNode.left, map);
        map.put(curNode.val, map.getOrDefault(curNode.val, 0) + 1);
        mapAllTreeNode(curNode.right, map);
    }
}

解法二:直接获取频率最大的节点值

由于二叉搜索树的特性,通过中序遍历得到的二叉树节点值顺序是升序的,这保证了所有节点值相同的节点是挨着的,因此可以直接完成统计一个数值的出现频率,然后继续统计下一个,在这个过程中,直接找到最大频率的节点值,不需要先统计所有节点值的频率。

采用左中右的中序遍历方式,在处理中间节点时,判断前置节点与当前节点值的大小,相等的话计数器加一,当前直接节点为空或前置节点和当前节点值不同时,将计数器置为1.

当当前计数器等于最大计数器时,将当前节点值加入list,当计数器大于最大计数时,更新最大计数器,清空lsit,然后添加当前节点值到List中。最后将List转为itn数组返回。

class Solution {
    int maxCount = 0;
    TreeNode preNode2 = null;
    List list1 = new ArrayList<>();
    int count = 0;
    public int[] findMode(TreeNode root) {
        getModeList(root);
        return list1.stream().mapToInt(Integer::intValue).toArray();
    }
    public void getModeList(TreeNode curNode) {
        if (curNode == null) return;
        getModeList(curNode.left);// 左
        if (preNode2 == null) count = 1;// 前置节点为空
        else if (preNode2.val == curNode.val) {//前后节点相同,频率+1
            count++;
        }else {// 前后节点值不同
            count = 1;
        }
        preNode2 = curNode;// 更新前置节点
        if (count == maxCount) {
            list1.add(curNode.val);//当前节点频率等于目前的最大频率,添加到list中
        }
        if (count > maxCount) {// 当前频率更高
            maxCount = count;
            list1 = new ArrayList<>();// 清空list
            list1.add(curNode.val);
        }
        getModeList(curNode.right);
    }
}

解法三:迭代法

使用迭代法其实就是将对应的中序遍历代码进行改动,在处理当前节点时,将其替换为上面的统计频率过程。

    public int[] findMode(TreeNode root) {
        int maxCount = 0;
        TreeNode preNode = null;
        List list = new ArrayList<>();
        int count = 0;
        if (root == null) return new int[0];
        Stack stack = new Stack<>();
        TreeNode curNode = root;
        while (curNode != null ||!stack.isEmpty()) {
            if (curNode != null) {
                stack.push(curNode);// 当前节点入栈
                curNode = curNode.left;// 左边节点
            }else {
                // 处理中间节点
                curNode = stack.pop();
                if (preNode == null) count = 1;// 前置节点为空
                else if (preNode.val == curNode.val) {// 前后节点值相同
                    count++;
                }else {// 前后节点值不同
                    count = 1;
                }
                preNode = curNode;
                if (count == maxCount) list.add(curNode.val);
                if (count > maxCount) {
                    maxCount = count;// 最大频率更新
                    list = new ArrayList<>();// 清空list
                    list.add(curNode.val);
                }
                // 右边节处理
                curNode = curNode.right;
            }
        }
        return list.stream().mapToInt(Integer::intValue).toArray();
    }
}

力扣236.二叉树的最近公共祖先

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

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

解题思路:

在p和q所在子树中,存在两种情况,一种是p和q在最近公共祖先的左右子树中,另一种是,q或p就是这个最近公共祖先,另一个在这个节点的子树中。

采用递归回溯的方式,当左右子树中存在p或q节点时,得到对应的节点,否则得到null。

整个二叉树的遍历采用左右中的后序遍历顺序,在处理中节点的时候进行左右子树结果的判定。

递归参数和返回值:参数为根节点root和目标节点p和q,返回值为root所在子树结果,找到p或q值则返回该节点,否则返回null

递归结束条件:当节点为空,或者节点等于p或q时,返回对应结果

单层递归逻辑:首先对左右节点进行递归,递归完成后处理当前节点,当根据左右子树的结果,返回当前节点所在子树的结果。

左右子树的结果都为空,根节点返回空,表示p和q都不在当前子树中

左子树和右子树结果都不为空,则当前根节点就是最近公共祖先,返回该节点即可。

左子树或右子树的结果其中一个不为空,则放回该结果节点,表示当前子树结果即为该节点。

代码实现:

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) {// p和q在root的左右子树中,root为最近公共祖先
            return root;
        }else if (left == null && right != null){// 右子树存在p或q,左子树不存在
            return right;
        }else if (left != null && right == null) {// 左子树存在p或q,右子树存在
            return left;
        }else {// p和q不存在于root的子树中,返回null
            return null;
        }
    }
}

力扣235.二叉搜索树的最近公共祖先

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

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

解法一:直接当做普通二叉树求解

二叉搜索树也是也是一棵二叉树,所以力扣236的解题方式也是能够直接使用的,只是浪费了二叉搜索树的特性,且需要遍历完所有的节点而已。

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) {// p和q在root的左右子树中,root为最近公共祖先
            return root;
        }else if (left == null && right != null){// 右子树存在p或q,左子树不存在
            return right;
        }else if (left != null && right == null) {// 左子树存在p或q,右子树存在
            return left;
        }else {// p和q不存在于root的子树中,返回null
            return null;
        }
    }
}

解法二:递归法(利用二叉搜索树的特性) 

解题思路:

二叉搜索树的特性是:左子树所有节点值小于根节点,右子树所有节点值大于根节点。

另外,p和q与它们的最近公共祖先节点root有下面的几种关系:

  1. root值大于p和q的值,表示p和q一定在root的左子树中
  2. root值小于p和q的值,表示p和q一定在root的右子树中
  3. 否则,要么p和q分别在root的左右子树中,或者p或q一个在子树中,一个为根节点root,此时root为p和q的最近公共祖先

基于此,我们只需要在正常先序遍历递归二叉树的时候,判定root节点值和p与q节点值的关系即可。

另外,由于本题的设定,p和q是一定能够找打最近公共祖先的,可以不需要考虑返回的是null节点

代码实现:

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) return null;
        if (root.val > p.val && root.val > q.val) {// p和q值小于根节点
            return lowestCommonAncestor(root.left, p, q);
        }else if (root.val < p.val && root.val < q.val){// p和q值大于根节点
            return lowestCommonAncestor(root.right, p, q);
        }else {
            return root;
        }
    }
}

解法三:迭代法

解法二还能够写成迭代形式,只需要将对应的递归代码修改为节点赋值即可

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while (root != null) {
            if (root.val > p.val && root.val > q.val) {// p和q值小于根节点
                root = root.left;
            } else if (root.val < p.val && root.val < q.val) {// p和q值大于根节点
                root = root.right;
            } else {
                return root;
            }
        }
        return null;
    }
}

力扣701.二叉搜索树中的插入操作

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 

解法一:递归法

由于二叉搜索树的特性,我们很好去找到新插入节点应该插入的地方

递归参数和返回值:参数值为当前根节点和插入节点值,返回值为根节点值

递归结束条件:当节点值为空时,表示到达插入节点位置,新建节点并返回当前节点

单层递归:当val值大于根节点值时,应该在右子树中插入,递归右子树建立新的右子树,当val值小于根节点时,应该在左子树中插入,递归左子树建立新的左子树。最后返回当前根节点。

代码实现:

class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) return new TreeNode(val);
        if (root.val < val) {// 插入到右子树中
            root.right = insertIntoBST(root.right, val);
        }else if (root.val > val) {// 插入到左子树中
            root.left = insertIntoBST(root.left, val);
        }
        return root;
    }
}

下面这个代码和上面其实是一样的,只是将最后节点插入的代码提到了上一层,此时root==null的判断只是来保证原始二叉树为空的时候能够处理,当二叉树不为空时,到不到这一层。

class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) return new TreeNode(val);
        if (root.val < val) {// 插入到右子树中
            if (root.right == null) {
                root.right = new TreeNode(val);
            }else {// 递归右子树
                insertIntoBST(root.right, val);
            }
        }else if (root.val > val) {// 插入到左子树中
            if (root.left  == null) {
                root.left = new TreeNode(val);
            }else {
                insertIntoBST(root.left, val);
            }
        }
        return root;
    }
}

解法二:迭代法

本题也可以使用迭代法求解,使用一个前置节点记录父节点,然后采用迭代的方式找到插入节点的位置,最后插入节点,返回root根节点即可。

代码实现:

class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
            if (root == null) return new TreeNode(val);
            TreeNode curNode = root;
            TreeNode preNode = root;
            while (curNode != null)  {
                preNode = curNode;// 更新前置节点
                if (curNode.val > val) curNode = curNode.left;// 迭代左子树
                else if (curNode.val < val) curNode  = curNode.right;// 迭代右子树
            }
            if (preNode.val > val){// 插入到左孩子
                preNode.left = new TreeNode(val);
            }else if (preNode.val < val) {// 插入到右孩子
                preNode.right = new TreeNode(val);
            }
            return root;
    }
}

力扣450.删除二叉搜索树中的节点

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

解法一:迭代法

在递归寻找删除节点的过程中,可能找到删除节点,也可能找不到,找不到时,返回null,找到待删除节点curNode时,需要处理下面几种情况

  • 待删除节点的左右子树都为空,删除该节点即可
  • 待删除节点的左子树为空,右子树不为空,此时用待删除节点的右子树替换该节点即可
  • 待删除节点的左子树不为空,右子树为空,此时用待删除节点的左子树替换该节点即可
  • 待删除节点的左右子树都不为空,此时需要找到右子树中最左边的节点finalNode,然后让待删除节点的整个左子树替换掉finalNode的左子树(该左子树为空),最后用待删除节点的右子树替换待删除节点

递归参数和返回值:递归参数为当前根节点和待删除节点值

递归结束条件:当根节点为空时,返回空

单层递归:先处理节点,判断节点与key的关系,然后当当前节点值等于key值,删除节点,否则根据节点值和key的关系,递归左右子树。注意左右子树递归的时候返回值要用root的对应左右孩子接收。 

代码实现:

    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) return null;// 未找到删除的节点
        if (root.val == key) {// 找到删除的节点
            if (root.left == null && root.right == null) {
                // 左右子树都为空
                return null;
            }else if (root.left == null && root.right != null) {
                // 左子树为空,右子树不为空
                return root.right;
            }else if (root.left != null && root.right == null) {
                // 左子树不为空,右子树为空
                return root.left;
            }else {
                // 左右子树都不为空
                TreeNode curNode = root.right;
                while (curNode.left != null) {// 找root节点右子树中最左边的节点
                    curNode = curNode.left;
                }
                // 把root的左子树放到root右子树最左边节点的左子树上
                curNode.left =  root.left;
                root = root.right;
            }
        }else if (root.val > key) {
            // 递归左子树
            root.left = deleteNode(root.left, key);
        }else {
            // 递归右子树
            root.right = deleteNode(root.right, key);
        }
        return root;
    }

解法二:普通二叉树的删除方式

一种通用的二叉树删除方式,通过将目标节点与右子树最左边节点交换,多次操作待删除节点值,通过交换节点值的方式实现删除目标节点。

class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) return null;
        if (root.val == key) {
            if (root.right == null) {// 交换root和curNode节点值能保证这一步的运行
                return root.left;
            }
            // 寻找当前节点的右子树最左边节点
            TreeNode curNode = root.right;
            while (curNode.left != null) {
                curNode = curNode.left;
            }
            // 交换root节点和curNode节点的值
            curNode.val ^= root.val;
            root.val ^= curNode.val;
            curNode.val ^= root.val;
        }
        root.left = deleteNode(root.left, key);
        root.right = deleteNode(root.right, key);
        return root;
    }
}

方法三 :迭代法

迭代法的解法是先找到删除节点和它的父节点,然后再根据父节点是否为空,删除对应的节点。

父节点为空表示删除的是根节点,否则需要判定删除节点为父节点的左孩子还是右孩子,然后删除对应的节点。

class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) return null;
        TreeNode curNode = root;
        TreeNode parent = null;
        while (curNode != null) {
            if (curNode.val == key) break;// 找到了待删除节点
            parent = curNode;// 更新父节点
            if (curNode.val > key) curNode = curNode.left;
            else if (curNode.val < key) curNode = curNode.right;
        }
        if (parent == null) {
            // 待删除节点为根节点
            return deleteOneNode(curNode);
        }
        // 否则需要知道父节点的左孩子还是右孩子
        if (parent.left != null && parent.left.val == key) {
            parent.left = deleteOneNode(curNode);
        }
        if (parent.right  != null && parent.right.val == key){
            parent.right = deleteOneNode(curNode);
        }
        return root;
    }

    /**
     * 删除单个目标节点
     * @param root
     * @return
     */
    public TreeNode deleteOneNode(TreeNode root) {
        if (root == null) return null;
        if (root.right == null) return root.left;
        // 找到节点的右子树最左边节点
        TreeNode curNode = root.right;
        while (curNode.left != null) {
            curNode = curNode.left;
        }
        curNode.left = root.left;
        return root.right;
    }
}

力扣669.修剪二叉搜索树

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

所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。

解法一:递归法

本题我们要利用二叉搜索树的特性,当节点值小于左边界low时,它的左子树可以整个扔掉,只需要对它的右子树进行处理,当右子树为空时,删除整个节点(返回空),否则,返回右子树递归的结果。当节点值大于有边界high时,它的右子树可以整个扔掉,只需要处理它的左子树,当左子树为空时,返回空,否则,返回左子树递归处理的结果。

另外,当节点值在区间内时,递归处理节点的左右子树即可(左右子树为空的判定可以省略)。

class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        if (root == null) return null;
        if (root.val < low) {
            if (root.right == null) return null;//右子树为空,返回null
            return trimBST(root.right, low, high);// 右子树不为空,则用右子树递归结果替代当前节点
        }else if (root.val > high) {
            if (root.left == null) return null;//左子树为空,返回null
            return trimBST(root.left, low, high);// 左子树不为空,则用左子树递归结果替代当前节点
        }
        root.left = trimBST(root.left, low, high);
        root.right = trimBST(root.right, low, high);
        return root;
    }
}

解法二:迭代法

由于二叉搜索树的特性:当一个节点值小于左边界时,它的左子树也小于左边界,当它的节点值大于右边界,它的右子树值全部大于右边界。

通过迭代先找到一个在边界值内的节点,然后对它的左子树进行小于左边界节点值处理,对它的右子树进行大于右边界节点值的处理。

代码实现:

class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        if (root == null) return null;
        while (root != null && (root.val < low || root.val > high)) {
            // 找寻根节点值符合要求的头节点
            if (root.val < low) {// 根节点小于左边界,往右子树走
                root = root.right;
            }else {// 根节点大于右边界,往左子树走
                root = root.left;
            }
        }
        TreeNode cur = root;
        // 处理当前节点左孩子小于左边界的情况
        while (cur != null) {
            while (cur.left != null && cur.left.val < low) {
                cur.left = cur.left.right;
            }
            cur = cur.left;
        }
        cur = root;
        // 处理当前节点右孩子大于右边界的情况
        while (cur != null) {
            while (cur.right != null && cur.right.val > high) {
                cur.right = cur.right.left;
            }
            cur = cur.right;
        }
        return root;
    }
}

力扣108.将有序数组转换成二叉搜索树

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

解题思路:

将数组的中间元素作为根节点,然后将该元素左边节点数组作为左子树节点进行递归,将右边节点数组作为右子树进行递归,然后返回根节点。

解法一:建立新的子数组进行递归

通过建立新的子数组作为左右子树递归是最方便的,因为不需要考虑边界的问题,但新数组的创建会增加内存消耗和浪费资源。

代码实现:

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        if (nums.length == 0) return null;
        int mid = nums.length / 2;
        TreeNode root = new TreeNode(nums[mid]);
        // 分割左右数组
        int[] leftNums = Arrays.copyOfRange(nums, 0, mid);
        int[] rightNums = Arrays.copyOfRange(nums, mid +1, nums.length);
        root.left = sortedArrayToBST(leftNums);
        root.right = sortedArrayToBST(rightNums);
        return root;
    }
}

解法二:数组下标进行迭代

为了降低空间的利用,我们可以用数组下标进行迭代,从而避免创建新的子数组,但需要注意数组下标边界的问题。

代码实现:

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return getTreeByArray(nums, 0, nums.length-1);
    }
    /**
     * @param nums
     * @param left 数组起始下标
     * @param right 数组结束下标
     * @return
     */
    public TreeNode getTreeByArray(int[] nums, int left, int right) {
        if (left > right) return null;// 空数组
        int mid = left + (right - left)/2;
        TreeNode root = new TreeNode(nums[mid]);
        root.left = getTreeByArray(nums, left, mid-1);
        root.right = getTreeByArray(nums, mid+1, right);
        return root;
    }
}

解法三:迭代法

迭代法的思想起始就是将递归法进行转换,还是先处理中间节点,然后处理左子树和youzishu通过三个队列分别存储待遍历节点,节点数组左区间下标和右区间下标,处理当前节点时,将数组左右区间的中间值赋值给当前节点,然后判断它是否还有左右子树需要处理,需要的话,创建对应的孩子节点,并将孩子节点和对用的左右区间下标入队。重复操作,直到遍历完所有的节点。

代码实现:

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        if (nums.length == 0) return null;
        TreeNode root = new TreeNode(0);
        // 三个队列,分别存储节点、左边界、右边界
        Queue nodeQueue = new LinkedList<>();
        Queue leftQueue = new LinkedList<>();
        Queue rightQueue = new LinkedList<>();
        // 存储初始队列数据
        nodeQueue.offer(root);
        leftQueue.offer(0);
        rightQueue.offer(nums.length-1);
        while (!nodeQueue.isEmpty()) {
            // 处理当前节点值
            TreeNode curNode = nodeQueue.poll();
            int left = leftQueue.poll();
            int right = rightQueue.poll();
            int mid = left + (right - left)/2;
            curNode.val = nums[mid];
            // 对左右子树数组进行分割和存储值
            if (left < mid) {// 左边还有值
                curNode.left = new TreeNode(0);
                nodeQueue.offer(curNode.left);
                leftQueue.offer(left);
                rightQueue.offer(mid-1);
            }
            if (right > mid) {// 右子树还有值
                curNode.right = new TreeNode(0);
                nodeQueue.offer(curNode.right);
                leftQueue.offer(mid+1);
                rightQueue.offer(right);
            }
        }
        return root;
    }
}

力扣538.把二叉搜索树转换成累加树

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

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

  • 节点的左子树仅包含键 小于 节点键的节点。
  • 节点的右子树仅包含键 大于 节点键的节点。
  • 左右子树也必须是二叉搜索树。

解题思路:

整体思路就是将二叉搜索树中序遍历的顺序反过来遍历,在遍历过程中,将前一个节点的节点值加到当前节点即可。

解法一:递归法

采用右中左的递归遍历顺序,在中的时候,将前一个节点值加到当前节点,并更新前一个节点的值。

代码实现:

class Solution {
    int preValue = 0;
    public TreeNode convertBST(TreeNode root) {
        if (root == null) return null;
        convertBST(root.right);// 右
        // 中
        root.val += preValue;
        preValue = root.val;
        convertBST(root.left);//左
        return root;
    }
}

解法二:迭代法

迭代法的方式只需要保证节点访问方式是右中左即可,处理中间节点时,将前一个节点的值加到当前节点值,并更新前一个节点值的保存。

代码实现1:逆中序遍历

class Solution {
    public TreeNode convertBST(TreeNode root) {
        if (root == null) return null;
        int preValue = 0;
        Stack stack = new Stack<>();
        TreeNode curNode = root;
        while (curNode != null || !stack.isEmpty()) {
            if (curNode != null) {// 一直往右子树访问,将中间节点入栈
                stack.push(curNode);
                curNode = curNode.right;
            }else {
                // 右子树访问完毕,处理中间节点
                curNode = stack.pop();
                // 处理节点时,将当前节点的值加上preValue,并更新 preValue
                curNode.val += preValue;
                preValue = curNode.val;
                curNode = curNode.left;// 然后继续处理左孩子
            }
        }
        return root;
    }
}

代码实现2:(采用统一的迭代格式)

class Solution {
    public TreeNode convertBST(TreeNode root) {
        if (root == null) return null;
        int preValue = 0;
        Stack stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode curNode = stack.pop();
            if (curNode != null) {
                if (curNode.left != null) stack.push(curNode.left);
                stack.push(curNode);
                stack.push(null);
                if (curNode.right != null) stack.push(curNode.right);
            }else {// 处理节点
                curNode = stack.pop();
                curNode.val += preValue;
                preValue = curNode.val;
            }
        }
        return root;
    }
}

力扣77.组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

  • 1 <= n <= 20
  • 1 <= k <= 

解题思路:(回溯法)

回溯法通过for循环和递归实现横向和纵向的遍历,本质是一种暴力破解方法,但在一些暴力无法写出合适代码的情况中很实用。

递归函数返回值和参数:不需要返回值,参数包括n和k,以及此次递归的开始值

void backTrack(int n, int k, int start)

递归结束条件:当当前列表大小等于目标长度k时,将list加入到结果集中,然后结束当前递归。

        if (curList.size() == k) {
            // 递归结束条件
            resList.add(new ArrayList<>(curList));
            return ;
        }

单层递归体:遍历当层递归的开始下标之后的所有值,在这个过程中,将当前值加入当前list,递归,递归结束后,在当前的list中删除当前值。

        for (; start <= n; start++){
            // 单层递归
            curList.add(start);
            backTrack(n, k, start+1);// 递归
            curList.remove(curList.size()-1);// 回溯
        }

代码实现:

class Solution {
    List> resList = new ArrayList<>();
    List curList = new ArrayList<>();
    public List> combine(int n, int k) {
        backTrack(n, k,1);
        return resList;
    }
    public void backTrack(int n, int k, int start) {
        if (curList.size() == k) {
            // 递归结束条件
            resList.add(new ArrayList<>(curList));
            return ;
        }
        for (; start <= n; start++){
            // 单层递归
            curList.add(start);
            backTrack(n, k, start+1);// 递归
            curList.remove(curList.size()-1);// 回溯
        }
    }
}

在单层递归过程中,目前的for循环遍历做了一些无用功,当start之后的值已经无法达到k值的目标时,还是对其进行了遍历,浪费了时间。我们可以在这里对其进行优化,对start的遍历只有当其剩下的值个数能满足k值的条件时,才进行递归,即将不满足条件的部分剪枝掉。

for (; start <= n-(k-curList.size()) +1; start++)

这里k-curList.size()就是还需要多少个值,由于区间是左闭的,因此需要+1.

力扣216.组合总数3

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次 

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

解题思路:

本题的思路其实好力扣77是一样的,只是需要增加结果集条件。

递归函数返回值和参数:无返回值,参数为k,n,start,注意这里我们使用n减当前值的方式

void backTrackCombinationSum3(int k, int n, int start)

递归结束条件:

当curList的大小等于k时,就得结束递归,只是需要判定n是否为0,为0则将curList加入到结果集中

        if (curList.size() == k){
            // curList大小等于k后,必须结束当层递归
            if (n == 0) {
                // 找到一组符合条件的,添加到结果集
                resList.add(new ArrayList<>(curList));
            }
            return;// 结束当层递归
        }

单层递归:

本题的单层宽度最高为9,深度其实也是最大为9,

        for (; start <= 9; start++) {
            curList.add(start);
            backTrackCombinationSum3(k,n-start,start+1);// 递归
            curList.remove(curList.size()-1);//回溯
        }

代码实现:

class Solution {
    List> resList = new ArrayList<>();
    List curList = new ArrayList<>();
    public List> combinationSum3(int k, int n) {
        backTrackCombinationSum3(k,n,1);
        return resList;
    }
    public void backTrackCombinationSum3(int k, int n, int start){
        if (curList.size() == k){
            // curList大小等于k后,必须结束当层递归
            if (n == 0) {
                // 找到一组符合条件的,添加到结果集
                resList.add(new ArrayList<>(curList));
            }
            return;// 结束当层递归
        }
        for (; start <= 9; start++) {
            curList.add(start);
            backTrackCombinationSum3(k,n-start,start+1);// 递归
            curList.remove(curList.size()-1);//回溯
        }
    }
}

力扣17.电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

解题思路:

这里我们假设输入的数字是符合规定的,因此不需要对非法字符进行处理,但在一些场景中可能需要考虑输入的合法性问题。

递归函数和返回值:回溯的算法通常没有返回值,参数为数字字符串digits和当前递归的下标index

void backTrackLetterCombinations(String digits, int index)

递归结束条件:当当前字符串长度等于输入的数字字符串digits的长度时,加入结果,结束当前递归

        if (curStr.length() == digits.length()){// 递归结束条件
            strList.add(curStr.toString());
            return;
        }

单层递归:遍历当前字符串,添加对应字符,递归,回溯。注意需要根据index获取对应字符集,字符集需要提前保存数字和字符集的对应关系。

        int digit = digits.charAt(index) - '0';// 获取当前index位置的数字
        String s = map[digit];  //获取对应字符串
        for (int i = 0; i < s.length(); i++) {// 遍历字符串
            curStr.append(s.charAt(i));// 添加当前字符
            backTrackLetterCombinations(digits,index+1);// 递归
            curStr.delete(curStr.length()-1,curStr.length());//回溯
        }

整体代码:

class Solution {
    String[] map = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    List strList = new ArrayList<>();
    StringBuilder curStr = new StringBuilder();
    public List letterCombinations(String digits) {
        if (digits.length() == 0) {
            return strList;
        }
        backTrackLetterCombinations(digits,0);
        return strList;
    }
    void backTrackLetterCombinations(String digits, int index) {
        if (curStr.length() == digits.length()){// 递归结束条件
            strList.add(curStr.toString());
            return;
        }
        int digit = digits.charAt(index) - '0';// 获取当前index位置的数字
        String s = map[digit];  //获取对应字符串
        for (int i = 0; i < s.length(); i++) {// 遍历字符串
            curStr.append(s.charAt(i));// 添加当前字符
            backTrackLetterCombinations(digits,index+1);// 递归
            curStr.delete(curStr.length()-1,curStr.length());//回溯
        }
    }
}  

力扣39.组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

递归函数返回值和参数:无返回值,参数包括数组,当前目标值,当前访问下标

void backTrackCombinationSum(int[] candidates, int target, int index)

递归结束条件:当target等于0时,将当前结果加入结果集,然后结束当前递归,当target小于0时,直接结束当前递归

        if (target == 0) {// 得到一个结果,递归结束
            resList.add(new ArrayList<>(curList));
            return;
        }
        if (target < 0) return;// 剩余target小于0,结束递归

单层递归:

由于本题所有数字能够被复用,因此每次递归的时候,不需要index加1,下一层递归的初始访问下标和本层递归相同。但由于组合要求结果不重复,因此不能每次都从0开始遍历

        for (int i = index; i < candidates.length; i++) {
            curList.add(candidates[i]);
            backTrackCombinationSum(candidates,target-candidates[i], i);// 递归
            curList.remove(curList.size()-1);// 回溯
        }

整体代码:

class Solution {
    List> resList = new ArrayList<>();
    List curList = new ArrayList<>();
    public List> combinationSum(int[] candidates, int target) {
        backTrackCombinationSum(candidates, target, 0);
        return resList;
    }
    public void backTrackCombinationSum(int[] candidates, int target, int index) {
        if (target == 0) {// 得到一个结果,递归结束
            resList.add(new ArrayList<>(curList));
            return;
        }
        if (target < 0) return;// 剩余target小于0,结束递归
        for (int i = index; i < candidates.length; i++) {
            curList.add(candidates[i]);
            backTrackCombinationSum(candidates,target-candidates[i], i);// 递归
            curList.remove(curList.size()-1);// 回溯
        }
    }
}

你可能感兴趣的:(算法练习记录,学习,算法,java)