LeetCode HOT 100Ⅴ

目录

102:二叉树的层序遍历

98:验证二叉搜索树

105:从前序与中序遍历序列构造二叉树(多看这个题!)

114:二叉树展开为链表

236:二叉树的最近公共祖先(可以多看看)

538:把二叉搜索树转换为累加树

560:和为 K 的子数组

394:字符串解码

238:除自身以外数组的乘积(多看)

406:根据身高重建队列

17:电话号码的字母组合

31:下一个排列(这题多看!!!一定要多看!!)


102:二叉树的层序遍历

思路:层序遍历是每一层是一组,所以queue里存放的是当前层所有节点的左右子节点,如果子节点为null跳过,temp存放该层所有的节点。

要注意的是每次for循环queue的size都会变,所以要在大循环while里先记录queue的size再进行for循环

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()){
            //queue的size会变,所以要先记录
            int len = queue.size();
            List<Integer> temp  = new LinkedList<>();
            for(int i = 0; i < len; ++i){
                TreeNode cur = queue.poll();
                if(cur == null) continue;
                temp.add(cur.val);
                queue.offer(cur.left);
                queue.offer(cur.right);
            }
            if(!temp.isEmpty()) res.add(temp);
        }
        return res;
    }
}

98:验证二叉搜索树

思路:二叉搜索树即左子节点<根节点<右子节点,所以对于左节点来说大于根节点则返回false,右子节点小于同理

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
        return fun(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }
    public boolean fun(TreeNode node, long min, long max){
        if(node == null) return true; //节点为空满足条件
        if(node.val <= min || node.val >= max) return false;
        //左子树的节点小,右子树节点大
        //要比较的只有node.val和left和right,所以min和max设置为最小和最大数
        return fun(node.left, min, node.val) && fun(node.right, node.val, max);
    }
}

105:从前序与中序遍历序列构造二叉树(多看这个题!)

思路:前序数组中第一个元素为根节点,中序数组中根节点左侧的是左子树,右侧的是右子树。这里的递归也是在子树中继续利用前序数组找到根节点,再在中序数组中寻找左右子树直到构造出二叉树。(这题是真的好玩!)

 //多看这道题!!!树的典型题,以及递归!!!
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return recur(preorder, 0, preorder.length, inorder, 0, inorder.length);
    }
    
    //head和tail都是索引
    public TreeNode recur(int[] preorder, int head1, int tail1, int[] inorder, int head2, int tail2){
        if(head1 == tail1) return null;
        int root_val = preorder[head1]; 
        TreeNode root = new TreeNode(root_val);//前序遍历的第一个值是根结点
        int index = 0;
        for(int i = head2; i < tail2; i++){
            //找到中序遍历里的根节点,左侧是左子树,右侧是右子树
            if(root_val == inorder[i]){
                index = i;
                break;
            }
        }
        //左子树的长度
        int leftLen = index - head2;
        //在前序遍历中,左子树是从根节点的下一个到加上长度
        //(在递归函数里此范围最后一个不遍历,是右子树的)
        root.left = recur(preorder, head1 + 1, head1 + 1 + leftLen, inorder, head2, index);
        //index表示根节点所在的索引
        root.right = recur(preorder, head1 + 1 + leftLen, tail1, inorder, index + 1, tail2);
        return root;
    }
}

114:二叉树展开为链表

思路:递归。相当于是前序遍历,先跟再左再右,展开后的“链表”还是树,所以需要将每个节点的左子节点置为null。

class Solution {
    public void flatten(TreeNode root) {
        //前序遍历
        List<TreeNode> res = new ArrayList<TreeNode>();
        recur(root, res);
        int size = res.size();
        //展开成单链表的形式但还是树,所有节点的左子节点都为null
        for(int i = 1; i < size; i++){
            TreeNode pre = res.get(i - 1), cur = res.get(i);
            pre.left = null;
            pre.right = cur;
        }

    }
    public void recur(TreeNode node, List<TreeNode> list){
        if(node != null){
            list.add(node);
            recur(node.left, list);
            recur(node.right, list);
        }
    }
}

236:二叉树的最近公共祖先(可以多看看)

思路:递归。深度优先搜索。

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null) return null;
        //p、q中有一个是另一个的祖先直接输出
        if(root.val == p.val || root.val == q.val) return root;
        //p、q均在左子树
        else if(dfs(root.left, p) && dfs(root.left, q))
            return lowestCommonAncestor(root.left, p, q);
        //p、q均在右子树
        else if(dfs(root.right, p) && dfs(root.right, q))
            return lowestCommonAncestor(root.right, p, q);
        return root;
    }
    //寻找p、q在哪里
    public boolean dfs(TreeNode root, TreeNode target){
        if(root == null) return false;
        if(root.val == target.val) return true;
        return dfs(root.left, target) || dfs(root.right, target);
    }
}

538:把二叉搜索树转换为累加树

思路:递归。相当于是倒着的中序遍历。

 //从右下开始逆序叠加 右->根->左
class Solution {
    int sum = 0;
    public TreeNode convertBST(TreeNode root) {
        if(root != null){
            convertBST(root.right);
            sum+= root.val;
            root.val = sum; //把节点替换成累加后的结果
            convertBST(root.left);
        }
        return root;
    }
}

560:和为 K 的子数组

思路:前缀和。哈希表中存放前缀和,也就是nums中当前元素和之前元素累加的结果sum,当sum - k在哈希表中存在,那么就说明从存在的那个sum对应的元素开始到当前元素构成的子数组的和为目标k,所以计数器+value即为结果

  • getOrDefault(key, defaultValue):如果key存在返回对应的value,不存在则返回定义的defaultValue值
class Solution {
    public int subarraySum(int[] nums, int k) {
        //前缀和
        int count = 0, sum = 0;
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
            //map里存放的是当前元素累加和,sum-k在map里即存在和为k的子集
            if(map.containsKey(sum - k)) count += map.get(sum - k);
            map.put(sum, map.getOrDefault(sum, 0) + 1);
        }
        return count;
    }
}

394:字符串解码

思路:首先待解码的字符串右四种类型:数字、[、]、字符

  • 当是数字的时候:不一定是个位数,所以需要做*10处理,如果是个位数num最开始是0,还有-'0'将字符转换为数字
  • 当是字符的时候:res存放的是一个[ ]内的所有字符,所以直接添加进去
  • 当是 [ 的时候:需要将该括号所乘的倍数存入倍数栈中,将括号内字符存入字符栈中,然后重置num和res,因为[]内还可能有[],所以用栈存放,遇到]输出的时候直接用栈尾的元素
  • 当是 ] 的时候:到了该输出的时刻了,因为字符可能要重复,所以需要一个可变长串存放重复的字符串,从倍数栈中取出当前需要的倍数,将res乘之后拼接当前括号上层括号内的字符,因为可能是嵌套的,外层还需对整个倍数,所以要拼接
class Solution {
    public String decodeString(String s) {
        //res存放一个[]中的字母(不重复)
        StringBuilder res = new StringBuilder();
        int num = 0; //记录倍数
        LinkedList<Integer> numStack = new LinkedList<>();
        LinkedList<String> strStack = new LinkedList<>();
        for(Character c : s.toCharArray()){
            //num*10是因为遍历的是字符,可能不是个位数
            if(c >= '0' && c <='9') num = num * 10 + c - '0';
            else if(c == '['){
                numStack.addLast(num);
                strStack.addLast(res.toString());
                //一定要置0,不然*10会出问题
                num = 0;
                res = new StringBuilder();
            }
            else if(c ==']'){
                StringBuilder temp = new StringBuilder();
                int k = numStack.removeLast();
                for(int i = 0; i < k; i++) temp.append(res);
                //把之前做好的字符串和本次拼接起来
                res = new StringBuilder(strStack.removeLast() + temp);
            }
            else res.append(c);
        }
        return res.toString();

    }
}

238:除自身以外数组的乘积(多看)

思路:第一个循环先记录当前元素的左部分乘积,因为不包括自身,所以先将上次的结果放入res中,然后再乘以当前元素给下一轮循环。第二个循环是计算右部分的乘积并与上一个循环得到的左部分结果相乘,注意这里小心漏掉res[0]的结果,所以需要先乘当前元素,然后将其与res[ i - 1 ]相乘而不是和res[ i ]相乘。

class Solution {
    /*
    原数组:       [1       2       3       4]
    左部分的乘积:   1       1      1*2    1*2*3
    右部分的乘积: 2*3*4    3*4      4      1
    结果:        1*2*3*4  1*3*4   1*2*4  1*2*3*1
    */
    public int[] productExceptSelf(int[] nums) {
        int[] res = new int[nums.length];
        int leftRes = 1, rightRes = 1;
        for(int i = 0; i < nums.length; i++){
            res[i] = leftRes;
            leftRes *= nums[i];
        }
        for(int i = nums.length - 1; i > 0; i--){
            rightRes *= nums[i];
            res[i - 1] *= rightRes;
        }
        return res;
    }
}

406:根据身高重建队列

思路:二维数组里存放的是当前人的身高和前面有几个比他高的,所以将人按从高到低排列,然后根据有几个比他高的直接用索引插入即可。

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        //先从高到低排序
        Arrays.sort(people, (a, b) -> {
            if(a[0] != b[0]) return b[0] - a[0];
            else return a[1] - b[1];
        });
        List<int[]> res = new ArrayList<int[]>();
        int len = people.length;
        for(int i = 0; i < len; i++){
            int[] person = people[i];
            //直接根据前面有几个比当前高,作为索引插入
            res.add(person[1], person);
        }
        return res.toArray(new int[len][]);
    }
}

17:电话号码的字母组合

思路:回溯回溯。相当于排列组合,先构建字典,然后根据输入的数字将数字对应的字母利用回溯进行排列组合。

class Solution {
    public String map[] = {
        " ",
        "",
        "abc",
        "def",
        "ghi",
        "jkl",
        "nmo",
        "pqrs",
        "tuv",
        "wxzy"
    };
    public ArrayList<String> res;
    public List<String> letterCombinations(String digits) {
        res = new ArrayList<String>();
        if(digits.equals("")) return res;
        backtrack(digits, 0, "");
        return res;
    }
    public void backtrack(String digits, int index, String s){
        if(index == digits.length()){
            res.add(s);
            return;
        }
        Character c = digits.charAt(index);
        String letter = map[c - '0'];
        for(int i = 0; i < letter.length(); i++){
            //排列组合
            backtrack(digits, index + 1, s + letter.charAt(i));
        }
        return;
    }
}

31:下一个排列(这题多看!!!一定要多看!!)

思路:下一个排列就是比当前输入的排列稍大的排列,所以主体思想是找两个差距最小的数且小的在前大的在后,进行交换,交换之后将换过去的大的数之后的数按从小到大排列。所以应该倒序查找,当输入数组是完全降序(也就是最大的时候),那就找不到较大和较小,直接将所有元素从小到大排序即可。

class Solution {
    public void nextPermutation(int[] nums) {
        int lower = nums.length - 2;
        //倒序查找较小数
        while(lower >= 0 && nums[lower] >= nums[lower + 1]){
            lower--;
        }
        if(lower >= 0){
            int higher = nums.length - 1;
            //找和较小数比起来稍大一点的数
            while(higher >= 0 && nums[higher] <= nums[lower]){
                higher--;
            }
            swap(nums, higher, lower);
        }
        //交换完较小和较大数后将后面的数颠倒,让后面那部分从小到大排列
        reverse(lower + 1, nums);
    }
    public void swap(int[] nums, int a, int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }

    public void reverse(int st, int[] nums){
        int ed = nums.length - 1;
        while(st < ed){
            swap(nums, st, ed);
            st++;
            ed--;
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值