力扣记录:Hot100(9)——337-448

本次题目

      • 337 打家劫舍 III
      • 338 比特位计数
      • 347 前 K 个高频元素
      • 394 字符串解码
      • 399 除法求值
      • 406 根据身高重建队列
      • 416 分割等和子集
      • 437 路径总和 III
      • 438 找到字符串中所有字母异位词
      • 448 找到所有数组中消失的数字

337 打家劫舍 III

  • 动态规划,之前做过,后序遍历,递归时计算当前节点的最大偷窃金额,偷当前节点则不能偷左右孩子,偷左右孩子则不能偷当前节点
    • 时间复杂度O(n),空间复杂度O(n)
class Solution {
    public int rob(TreeNode root) {
        //动态规划
        int[] res = dfs(root);
        return Math.max(res[0], res[1]);
    }
    //后序遍历
    //递归时计算当前节点的最大偷窃金额
    //偷当前节点则不能偷左右孩子1,偷左右孩子则不能偷当前节点0
    private int[] dfs(TreeNode root){
        //终止条件
        int[] res = new int[2];
        if(root == null) return res;
        int[] left = dfs(root.left);
        int[] right = dfs(root.right);
        res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        res[1] = root.val + left[0] + right[0];
        return res;
    }

}

338 比特位计数

  • Brian Kernighan 算法,参考JZ15 二进制中1的个数,使用n&(n-1)
    • 时间复杂度O(nlogn),空间复杂度O(1)
class Solution {
    public int[] countBits(int n) {
        int [] res = new int[n + 1];
        for(int i = 0; i <= n; i++){
            res[i] = count(i);
        }
        return res;
    }
    private int count(int n){
        int res = 0;
        while(n != 0){
            res++;
            //找到n最右边的1并将其改为0
            n &= (n - 1);
        }
        return res;
    }
}
  • 动态规划,x通过右移一位去掉最低位得到y,若x为偶数,则最低位为0,1比特位和y相同;若x为奇数,则最低位为1,1比特位比y多1。递推:bits[x]=bits[x>>1]+(x & 1)
    • 时间复杂度O(n),空间复杂度O(1)
class Solution {
    public int[] countBits(int n) {
        int [] res = new int[n + 1];
        for(int i = 0; i <= n; i++){
            res[i] = res[i >> 1] + (i & 1);
        }
        return res;
    }
}

347 前 K 个高频元素

  • 优先级队列(前k大用小顶堆,前k小用大顶堆),之前做过,先统计数组中的数出现频率,然后使用小顶堆,复习:手动实现优先级队列
    • 时间复杂度O(nlogk),空间复杂度O(n)
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //优先级队列(前k大用小顶堆,前k小用大顶堆)
        //先统计数组中的数出现频率,然后使用小顶堆
        Map<Integer, Integer> map = new HashMap<>();
        int[] res = new int[k];
        for(int num : nums){
            if(!map.containsKey(num)){
                map.put(num, 0);
            }
            map.put(num, map.get(num) + 1);
        }
        //优先级队列
        PriorityQueue<Map.Entry<Integer, Integer>> priorityQueue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
        for(Map.Entry<Integer, Integer> entry : map.entrySet()){
            if(priorityQueue.size() < k){
                priorityQueue.offer(entry);
            }else{
                if(priorityQueue.peek().getValue() < entry.getValue()){
                    priorityQueue.poll();
                    priorityQueue.offer(entry);
                }
            }
        }
        for(int i = 0; i < k; i++){
            res[i] = priorityQueue.poll().getKey();
        }
        return res;
    }
}

394 字符串解码

  • 考虑到嵌套问题使用栈,类似150 逆波兰表达式求值,定义两个栈,一个保存数字,一个保存字符串,遍历字符串,当为数字时计算其值(可能有两位数),当为字母时拼接字符串之后,当为左括号时将当前字符串和数字入栈,当为右括号时,数字和字符串出栈,当前字符串重复后拼接到字符串(上一个字符串)之后。
  • 注意:出栈时的字符串为上一字符串,当前未入栈的字符串和出栈的数字对应。
    • 时间复杂度O(n),空间复杂度O(n)
class Solution {
    public String decodeString(String s) {
        //定义两个栈,一个保存数字,一个保存字符串
        Deque<String> stackS = new LinkedList<>();
        Deque<Integer> stackN = new LinkedList<>();
        int num = 0;
        StringBuilder sb = new StringBuilder();
        //遍历字符串
        for(char c : s.toCharArray()){
            if(c >= '0' && c <= '9'){//当为数字时计算其值(可能有两位数)
                num = 10 * num + (c - '0');
            }else if(c == '['){//当为左括号时将当前字符串和数字入栈
                stackS.push(sb.toString());
                stackN.push(num);
                //清空后准备下一组
                sb = new StringBuilder();
                num = 0;
            }else if(c == ']'){//当为右括号时,数字和字符串出栈,当前字符串重复后拼接到字符串(上一个字符串)之后
                //注意:出栈时的字符串为上一字符串,当前未入栈的字符串和出栈的数字对应
                int cycleNum = stackN.pop();
                String preString = stackS.pop();
                StringBuilder temp = new StringBuilder();
                for(int i = 0; i < cycleNum; i++){
                    temp.append(sb);
                }
                sb = new StringBuilder(preString + temp);
            }else{//当为字母时拼接字符串之后
                sb.append(c);
            }
        }
        return sb.toString();
    }
}

399 除法求值

  • 图论问题,广度优先搜索,遍历输入数组,使用哈希表将点映射为正整数标号(key:点对应字符串,value:正整数递增标号),定义边数组存储每个点到其连接的点的标号及权重(下标位置为起点标号)。然后遍历查询数组,从哈希表中查询点对应标号,若不存在标号,则为-1;若存在标号,则再根据标号到边数组中查询,一一对比得到查询两点的比值。
    • 时间复杂度O((|输入方程长度|+|查询方程长度|)*|方查询次数| + 输入方程长度 * 查询方程长度),空间复杂度O(|方程中不同字符的个数|)
class Solution {
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        //图论问题,广度优先搜索
        Map<String, Integer> nodeMap = new HashMap<>();
        int count = 0;
        //遍历输入数组,使用哈希表将点映射为正整数标号(key:点对应字符串,value:正整数递增标号)
        for(List<String> equ : equations){
            if(!nodeMap.containsKey(equ.get(0))){
                nodeMap.put(equ.get(0), count++);
            }
            if(!nodeMap.containsKey(equ.get(1))){
                nodeMap.put(equ.get(1), count++);
            }
        }
        //定义边数组存储每个点到其连接的点的标号及权重(下标位置为起点标号)
        //数组第i个位置:[[标号int,权重double],[标号int,权重double],...]
        //每个位置上都是一个list,list里面可能有多条边。新建Pair类方便存放
        List<Pair>[] edgeList = new List[count];
        for(int i = 0; i < count; i++){//初始化
            edgeList[i] = new ArrayList<Pair>();
        }
        for(int i = 0; i < equations.size(); i++){
            int src = nodeMap.get(equations.get(i).get(0));
            int dst = nodeMap.get(equations.get(i).get(1));
            edgeList[src].add(new Pair(dst, values[i]));
            edgeList[dst].add(new Pair(src, 1.0 / values[i]));
        }
        //然后遍历查询数组,从哈希表中查询点对应标号,若不存在标号,则为-1
        //若存在标号,则再根据标号到边数组中查询,一一对比得到查询两点的比值
        double[] result = new double[queries.size()];
        Arrays.fill(result, -1.0);
        for(int i = 0; i < queries.size(); i++){
            List<String> que = queries.get(i);
            double v = -1.0;
            if(nodeMap.containsKey(que.get(0)) && nodeMap.containsKey(que.get(1))){
                int src = nodeMap.get(que.get(0));
                int dst = nodeMap.get(que.get(1));
                //这里可以判断两点是否相同提前返回,剪枝
				//if(src == dst)  result[i] = 1.0;
                //使用队列逐个判断,将src连接的点依次放进队列
                //同时定义比值数组表示src到数组下标位置的比值
                Queue<Integer> queue = new LinkedList<>();
                queue.offer(src);
                double[] target = new double[count];
                Arrays.fill(target, -1.0);
                target[src] = 1.0;
                //若队列为空则说明src连接的边遍历完了
                //若比值数组dst位置大于0说明找到对应关系
                while(!queue.isEmpty() && target[dst] < 0){
                    int cur = queue.poll();
                    for(Pair pair : edgeList[cur]){
                        int next = pair.dst;
                        double val = pair.value;
                        //只有未走过的边才赋值,防止在环中死循环
                        //如输入[["a","b"],["c","d"]],查询[["a","c"]]
                        if(target[next] < 0){
                            target[next] = target[cur] * val;
                            queue.offer(next);
                        }
                    }
                }
                result[i] = target[dst];
            }
        }
        return result;
    }
    class Pair {
        int dst;
        double value;
        Pair(int dst, double value){
            this.dst = dst;
            this.value = value;
        }
    }
}
  • floyd算法*
  • 带权并查集*
    • 时间复杂度O((|输入方程长度|+|查询方程长度|)*log|方程中不同字符的个数|),空间复杂度O(|方程中不同字符的个数|)

406 根据身高重建队列

  • 贪心,之前做过,使用list,身高从高到底排列,身高相同的前面人数更少的优先,然后按顺序插入队列中,插入时根据前面人数进行插入即可(队列中的都是比当前高的)
    • 时间复杂度O(n^2),空间复杂度O(logn)
class Solution {
    public int[][] reconstructQueue(int[][] people) {
        //贪心,使用list
        ArrayList<int[]> res = new ArrayList<>();
        //身高从高到底排列,身高相同的前面人数更少的优先
        // Arrays.sort(people, new Comparator() {
        //     public int compare(int[] person1, int[] person2) {
        //         if (person1[0] != person2[0]) {
        //             return person2[0] - person1[0];
        //         } else {
        //             return person1[1] - person2[1];
        //         }
        //     }
        // });
        Arrays.sort(people, (o1, o2) -> {
            if(o1[0] == o2[0]) return o1[1] - o2[1];
            return o2[0] - o1[0];
        });
        for(int[] p : people){
            res.add(p[1], p);
        }
        return res.toArray(new int[people.length][]);
    }
}

416 分割等和子集

  • 动态规划,01背包,之前做过,相当于target为数组总和的一半,物品和能否达到target。定义dp数组dp[i]表示容量为i的背包放入数之和的最大值(数的重量=其值)。注意:一维滚动数组优化先物品后背包(大到小)
    • 时间复杂度O(n*target),空间复杂度O(target)
class Solution {
    public boolean canPartition(int[] nums) {
        //动态规划,01背包
        if(nums.length == 1) return false;
        //相当于target为数组总和的一半,物品和能否达到target
        int sum = 0;
        for(int num : nums){
            sum += num;
        }
        if(sum % 2 == 1) return false;
        int target = sum / 2;
        //定义dp数组dp[i]表示容量为i的背包放入数之和的最大值(数的重量=其值)
        int[] dp = new int[target + 1];
        //一维滚动数组优化,先物品后背包(大到小)
        for(int i = 0; i < nums.length; i++){
            for(int j = target; j >= nums[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        return dp[target] == target;
    }
}

437 路径总和 III

  • 递归同时计算路径和,参考112 路径总和、113 路径总和II,从每个节点出发进行前序遍历
    • 时间复杂度O(n^2),空间复杂度O(n)
class Solution {
    public int pathSum(TreeNode root, int targetSum) {
        //递归
        if(root == null) return 0;
        int res = preOrder(root, targetSum);
        res += pathSum(root.left, targetSum);
        res += pathSum(root.right, targetSum);
        return res;
    }
    //递归函数
    private int preOrder(TreeNode root, int targetSum){
        //终止条件
        if(root == null) return 0;
        int res = 0;
        if(root.val == targetSum){
            res++;
        }
        res += preOrder(root.left, targetSum - root.val);
        res += preOrder(root.right, targetSum - root.val);
        return res;
    }
}
  • 前缀和,定义hashmap保存root到当前节点的前缀和,遍历到当前节点时判断是否存在cur - target的前缀和,相当于以当前节点为终点,向上查找起点。补充:getOrDefault()可以自动判断map是否存在key
    • 时间复杂度O(n),空间复杂度O(n)
class Solution {
    public int pathSum(TreeNode root, int targetSum) {
        //递归
        if(root == null) return 0;
        //保存前缀和
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);  //初始化
        return preOrder(root, targetSum, map, 0);
    }
    //递归函数,输入当前节点,目标值,前缀表,当前路径和
    private int preOrder(TreeNode root, int targetSum, Map<Integer, Integer> map, int cur){
        //终止条件
        if(root == null) return 0;
        int res = 0;
        cur += root.val;    //更新当前路径和
        //根据前缀和计算有多少个起点
        if(map.containsKey(cur - targetSum)){
            res += map.get(cur - targetSum);
        }
        //存入前缀和
        if(!map.containsKey(cur)){
            map.put(cur, 0);
        }
        map.put(cur, map.get(cur) + 1);
        //递归,注意此时目标值不需要递减,计算的直接是总和
        res += preOrder(root.left, targetSum, map, cur);
        res += preOrder(root.right, targetSum, map, cur);
        //回溯
        map.put(cur, map.get(cur) - 1);
        return res;
    }
}

438 找到字符串中所有字母异位词

  • 滑动窗口,之前做过,设置滑动窗口长度与p长度相同,不断移动窗口,定义数组作为哈希表(一共26个字母)存储字符出现次数,若窗口内字符出现次数符合条件,则记录窗口左下标。
    • 时间复杂度O(n),空间复杂度O(1)
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        //滑动窗口
        List<Integer> res = new ArrayList<>();
        if(s.length() < p.length()) return res;
        //定义数组作为哈希表(一共26个字母)存储字符出现次数
        int[] fre = new int[26];
        for(char c : p.toCharArray()){
            fre[c - 'a']++;
        }
        //设置滑动窗口长度与p长度相同
        for(int i = 0; i < s.length() - p.length() + 1; i++){
            int[] fre2 = new int[26];
            //不断移动窗口
            for(int j = i; j < i + p.length(); j++){
                fre2[s.charAt(j) - 'a']++;
            }
            //若窗口内字符出现次数符合条件,则记录窗口左下标
            if(Arrays.equals(fre, fre2)){
                res.add(i);
            }
        }
        return res;
    }
}

448 找到所有数组中消失的数字

  • 将原数组当作哈希表,遍历一遍数组,将当前数对应位置上的数加n,第二次遍历时若某个位置上的数不大于n,则说明该数未出现过。
    • 时间复杂度O(n),空间复杂度O(1)
class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        //将原数组当作哈希表
        //遍历一遍数组,将当前数对应位置上的数加n
        List<Integer> res = new ArrayList<>();
        int n = nums.length;
        for(int num : nums){
            int i = (num - 1) % n;
            nums[i] += n;
        }
        //第二次遍历时若某个位置上的数不大于n,则说明该数未出现过
        for(int i = 0; i < n; i++){
            if(nums[i] <= n){
                res.add(i + 1);
            }
        }
        return res;
    }
}

你可能感兴趣的:(Hot100,LeetCode,leetcode,算法,深度优先)