LC669+670+857+394+337

本文介绍了四种不同的算法问题,包括如何修剪二叉搜索树以保持节点值在指定范围内,如何找到整数中的最大交换以获得最大值,如何以最低成本雇佣k名工人以及如何解码编码字符串。这些问题分别涉及到二叉树的递归操作、贪心策略和字符串处理技巧。
摘要由CSDN通过智能技术生成

修剪二叉树

给你二叉搜索树的根节点 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) return trimBST(root.right, low, high);
        if (root.val>high) return trimBST(root.left, low, high);
        root.left = trimBST(root.left, low, high);
        root.right = trimBST(root.right, low, high);
        return root;

    }
}

最大交换

给定一个非负整数,你至多可以交换一次数字中的任意两位。返回你能得到的最大值。

分析:

贪心算法。首先计算last[d] = i,最后一次出现的数字d(如果存在)的索引 i;然后,从左到右扫描数字时,如果将来有较大的数字,将用最大的数字交换;如果有多个这样的数字,将用最开始遇到的数字交换。

public class Solution {

    public int maximumSwap(int num) {
        String s = String.valueOf(num);
        int len = s.length();
        char[] charArray = s.toCharArray();

        // 记录每个数字出现的最后一次出现的下标
        int[] last = new int[10];
        for (int i = 0; i < len; i++) {
            last[charArray[i] - '0'] = i;
        }

        // 从左向右扫描,找到当前位置右边的最大的数字并交换
        for (int i = 0; i < len - 1; i++) {
            // 找最大,所以倒着找
            for (int d = 9; d > charArray[i] - '0'; d--) {
                if (last[d] > i) {
                    swap(charArray, i, last[d]);
                    // 只允许交换一次,因此直接返回
                    return Integer.parseInt(new String(charArray));
                }
            }
        }
        return num;
    }

    private void swap(char[] charArray, int index1, int index2) {
        char temp = charArray[index1];
        charArray[index1] = charArray[index2];
        charArray[index2] = temp;
    }
}

雇佣K名工人的最低成本

有 n 名工人。 给定两个数组 quality 和 wage ,其中,quality[i] 表示第 i 名工人的工作质量,其最低期望工资为 wage[i] 。

现在我们想雇佣 k 名工人组成一个工资组。在雇佣 一组 k 名工人时,我们必须按照下述规则向他们支付工资:

  • 对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。
  • 工资组中的每名工人至少应当得到他们的最低期望工资。

给定整数 k ,返回 组成满足上述条件的付费群体所需的最小金额 。在实际答案的 10-5 以内的答案将被接受。

分析:

首先假设我们已经选择了某一个工资组,组成成员为 [ h 1 , h 2 , . . . . , h k ] [h_1,h_2, .... , h_k] [h1,h2,....,hk],其中 h i h_i hi表示第 i 个工人,整个工作组的总工作质量为:totalq,总的支付金额为 totalc。那么按照题目的要求对于任意工人 h i h_i hi 需要满足: t o t a l c × q u a l i t y [ h i ] t o t a l q > = w a g e s [ h i ] totalc \times \frac{quality[h_i]}{totalq} >= wages[h_i] totalc×totalqquality[hi]>=wages[hi]; 即 t o t a l c > = w a g e s [ h i ] q u a l i t y [ h i ] × t o t a l q totalc >= \frac{wages[h_i]}{quality[h_i]} \times totalq totalc>=quality[hi]wages[hi]×totalq。所以当某一个工资组的总工作质量固定时,最少的付费金额只与工资组中最大的 w a g e s [ h i ] q u a l i t y [ h i ] \frac{wages[h_i]}{quality[h_i]} quality[hi]wages[hi]有关。
根据上面的分析故采用贪心的策略::设一个工人 i 在某一个工资组中的权重表示为 w a g e s [ h i ] q u a l i t y [ h i ] \frac{wages[h_i]}{quality[h_i]} quality[hi]wages[hi],那么当我们以某一个工人 x 作为一个工资组中权重最高时,工资组中其他人员只需要在权重小于工人 x 的集合中选择工作质量最少的 k−1 名工人来组成工资组即可,此时便能达到以工人 x 为权重最高的工资组的总工作量最小,从而达到以工人 x 为权重最高的工资组的最小工资开销。然后枚举以每一个能成为工资组中权重最大的工人来计算最小工资组开销,然后取其中的最小即可。在处理的过程中,可以先将工人按照权重进行升序排序,然后在遍历过程中可以用优先队列来维护之前工作质量最少的 k−1 名工人。

class Solution {
    public double mincostToHireWorkers(int[] quality, int[] wage, int k) {
        int n = quality.length;
        Integer[] id = new Integer[n];//Integer才能用Arrays.sort扩展
        for (int i = 0; i < n; i++) {// 工人的身份ID 方便排序和计算
            id[i] = i;
        }
        Arrays.sort(id, (a, b) -> (quality[b] * wage[a] - quality[a] * wage[b]));// 对工人id 按照 单位工作质量最低薪资 升序排序
        double minTotal = 1e9;//最小的k组工人雇佣金 题目最大是1e8
        double totalq = 0.0;// k组工人的总工作时间(质量)
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>((a, b) -> b - a);//用最大堆留下工作时间(质量)最小的k个人
        for (int i = 0; i < k - 1; i++) {//最大堆先选进k-1个
            totalq += quality[id[i]];
            pq.offer(quality[id[i]]);
        }
        for (int i = k - 1; i < n; i++) {//循环打擂维持k个,这k个是当前最高时薪下“quality[ID[i]] / wage[ID[i]]”的工作时间最小的k个
            int idx = id[i];
            totalq += quality[idx];// 累加总工作时间(质量);最高时薪就是当前升序遍历到的,最小的总工作时间就是当前最大堆内的k个人总工作时间
            pq.offer(quality[idx]);//按照升序时薪遍历所有工人,每个工人都加入最大堆,每次加入的工人是最高时薪,说明每个时薪都会遍历到。
            double totalc = ((double) wage[idx] / quality[idx]) * totalq;//计算当前堆内k个人的雇佣金
            minTotal = Math.min(minTotal, totalc);// 选出最小的总雇佣金
            totalq -= pq.poll();// 减去弹出堆的工人的工作时间(质量)
        }
        return minTotal;
    }
}

字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

分析:

使用辅助栈来模拟。遍历编码的字符串,当遇到数字、字母和“[”时,这些字符都要入栈s,当遇到"]“时开始出栈,利用另一个栈s1来接收当前出栈的字符直到当前栈顶元素为”[",此时s的栈顶元素一定为数字num,表示当前s1中连接起来的字符串重复的次数。利用一个辅助函数将s1中的各个字符连接起来为字符串,然后再将其重复num次后加入到s中。直至遍历到字符串最后一个字符,将s中的所有字符反转连接成字符串。

class Solution {
    int index;
    public String decodeString(String s) {
        Stack<String> stack = new Stack<>();
        index = 0;
        while (index<s.length()){
            if (Character.isDigit(s.charAt(index))){
                stack.push(getDigits(s));
            }
            else if (Character.isLetter(s.charAt(index)) || s.charAt(index) == '['){
                stack.push(String.valueOf(s.charAt(index)));
                index++;
            }
            else{
                Stack<String> tmp = new Stack<>();
                while (!stack.peek().equals("[")){
                    tmp.push(stack.pop());
                }
                stack.pop();
                int repTime = Integer.parseInt(stack.pop());
                String one = getS(tmp);
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < repTime; i++) {
                    sb.append(one);
                }
                stack.push(sb.toString());
                index++;

            }
        }

        return getRes(stack);

    }
    public String getDigits(String s){
        int start = index;
        while (Character.isDigit(s.charAt(index))) index++;
        return s.substring(start, index);
    }
    public String getS(Stack<String> stack){
        StringBuilder sb = new StringBuilder();
        while (!stack.isEmpty()){
            sb.append(stack.pop());
        }
        return sb.toString();
    }
    public String getRes(Stack<String> stack){
        List<String> list = new ArrayList<>();
        while (!stack.isEmpty()){
            list.add(stack.pop());
        }
        StringBuilder res = new StringBuilder();
        for (int i = list.size()-1;i >= 0 ; i--) {
            res.append(list.get(i));
        }
        return res.toString();
    }
}

打家劫舍III

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

这里是引用
输入: root = [3,2,3,null,3,null,1]
输出: 7
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7

分析:

每个节点可选择偷或者不偷两种状态,根据题目意思,相连节点不能一起偷。

  • 当前节点选择偷时,那么两个孩子节点就不能选择偷了
  • 当前节点选择不偷时,两个孩子节点只需要拿最多的钱出来就行(两个孩子节点偷不偷没关系)

使用一个大小为 2 的数组来表示 int[] res = new int[2] 0 代表不偷,1 代表偷;任何一个节点能偷到的最大钱的状态可以定义为:

  1. 当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
  2. 当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数
public int rob(TreeNode root) {
    int[] result = robInternal(root);
    return Math.max(result[0], result[1]);
}

public int[] robInternal(TreeNode root) {
    if (root == null) return new int[2];
    int[] result = new int[2];

    int[] left = robInternal(root.left);
    int[] right = robInternal(root.right);

    result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
    result[1] = left[0] + right[0] + root.val;

    return result;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值