怒刷LeetCode的第5天(Java版)

目录

第一题

题目来源

题目内容

解决方法

方法一:动态规划

第二题

题目来源

题目内容

解决方法

方法一:双指针

方法二:动态规划

第三题

题目来源

题目内容

解决方法

方法一:贪心算法

方法二:枚举

方法三:硬编码数字


第一题

题目来源

337. 打家劫舍 III - 力扣(LeetCode)

题目内容

解决方法

方法一:动态规划

这是一个典型的树形动态规划问题,可以使用递归来解决。

对于每个节点,有两种选择:偷取该节点和不偷取该节点。如果偷取该节点,则不能偷取其子节点;如果不偷取该节点,则可以选择偷取其子节点。

定义一个递归函数 rob,它接收一个节点作为参数,并返回在该节点为根节点的子树中,小偷能够盗取的最高金额。递归函数的定义如下:

  • 如果节点为空,返回0。
  • 如果节点不为空,分别计算选择偷取该节点和不偷取该节点的情况下,能够盗取的最高金额。
    • 偷取该节点的情况:计算偷取该节点的金额,加上偷取其左子节点和右子节点的最高金额(因为不能偷取相邻节点)。
    • 不偷取该节点的情况:计算偷取其左子节点和右子节点的最高金额。

最后,在主函数中调用递归函数 rob,传入根节点,并返回结果。

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
    }
}

public class Solution {
    public int rob(TreeNode root) {
        int[] result = dfs(root);
        return Math.max(result[0], result[1]);
    }

    private int[] dfs(TreeNode node) {
        if (node == null) {
            return new int[]{0, 0};
        }

        int[] left = dfs(node.left);
        int[] right = dfs(node.right);

        int selected = node.val + left[1] + right[1];
        int notSelected = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);

        return new int[]{selected, notSelected};
    }
}

在这个解法中,使用一个长度为2的数组来存储两种情况下的最高金额。数组中的第一个元素表示选择偷取该节点时的最高金额,第二个元素表示不选择偷取该节点时的最高金额。

  • 时间复杂度:O(N),其中 N 是节点的数量,每个节点只需计算一次。
  • 空间复杂度:O(N),递归调用栈的深度为树的高度,最坏情况下为 O(N)。在堆栈中存储的状态也需要 O(N) 的空间。

LeetCode运行结果:

第二题

题目来源

11. 盛最多水的容器 - 力扣(LeetCode)

题目内容

解决方法

方法一:双指针

这道题可以使用双指针来解决。我们可以用两个指针分别指向数组的左端和右端,然后计算它们之间能够容纳的水量,并将当前计算出来的最大水量保存下来。

因为水的容量取决于容器的瓶颈,即容器底部的长度和高度较小的那条线段。因此,在计算当前指针对应的线段为底时,我们需要将另一个指针向中间移动来找到高度更高的线段,以获得更大的水量。

具体地,我们假设当前左指针所指的线段高度为 l,右指针所指的线段高度为 r,且 l<r。如果我们将左指针向右移动一位,那么新的左指针对应的线段高度为 l',右指针不变,那么新容器的底部长度为 r- (l'-l),高度为 min(l', r),因此容器容纳的水量为 (r-(l'-l)) * min(l', r)。假设新容器容纳的水量比原容器大,那么我们将当前容器容纳的水量更新为新容器的水量。

按照上述方式移动指针,并在每次移动后更新当前最大容量,直到两个指针相遇为止。在整个过程中,我们不断地维护最大的水量,并返回最终的最大值作为答案。

class Solution {
    public int maxArea(int[] height) {
    int left = 0, right = height.length - 1;
    int maxArea = 0;
    while (left < right) {
        int area = Math.min(height[left], height[right]) * (right - left);
        maxArea = Math.max(maxArea, area);
        if (height[left] <= height[right]) {
            ++left;
        } else {
            --right;
        }
    }
    return maxArea;
}
}

复杂度分析:

时间复杂度:O(n),其中 n 是数组的长度。在最坏的情况下,左指针和右指针分别移动了 n-1 次,因此总时间复杂度为 O(n)。

空间复杂度:O(1),只需要常数级别的额外空间来存储指针和变量。

LeetCode运行结果:

方法二:动态规划

除了双指针法,我们还可以尝试使用动态规划的方法解决这个问题。

  1. 我们定义一个二维数组 dp,其中 dp[i][j] 表示以第 i 条线和第 j 条线为边界的容器能够容纳的最大水量。
  2. 初始化 dp 数组的对角线元素为0,因为任意一条线段与自己构成的容器是无法容纳水的。
  3. 然后,我们从左到右、从下到上依次计算 dp 数组的其他元素。计算某个元素时,可以考虑当前两条线段的高度和它们之间的距离,并且选择高度较小的一条作为瓶颈,即容器的高度,并计算容器的底部长度,即线段的距离。根据容器容纳水的公式,我们可以得到 dp[i][j] = min(height[i], height[j]) * (j-i)。
  4. 最后,遍历整个 dp 数组,找到最大的水量,即 dp 数组的最大值。
class Solution {
    public int maxArea(int[] height) {
    int n = height.length;
    int[][] dp = new int[n][n];
    int maxArea = 0;
    for (int j = 1; j < n; j++) {
        for (int i = j - 1; i >= 0; i--) {
            int area = Math.min(height[i], height[j]) * (j - i);
            dp[i][j] = area;
            maxArea = Math.max(maxArea, area);
        }
    }
    return maxArea;
}
}

时间复杂度:O(n^2),其中 n 是数组的长度。需要遍历二维数组中的每个元素。

空间复杂度:O(n^2),需要一个二维数组来存储计算结果,空间复杂度与数组长度的平方成正比。

LeetCode运行结果:

总结:动态规划的方法在空间复杂度上较高。在面对大规模输入时,可能会超出内存限制。我们可以优先考虑双指针方法。

第三题

题目来源

12. 整数转罗马数字 - 力扣(LeetCode)

题目内容

解决方法

方法一:贪心算法

要将一个整数转换为罗马数字,我们可以使用贪心算法。首先,创建两个数组,一个存储罗马数字的符号,另一个存储对应的数值。然后,从大到小遍历符号数组,每次查看给定的整数中有多少个当前符号所对应的数值,将其加入结果字符串,并用给定的整数减去已经加过的数值。重复这个过程直到给定的整数为0。

class Solution {
public String intToRoman(int num) {
    int[] values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
    String[] symbols = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};

    StringBuilder sb = new StringBuilder();
    int i = 0;

    while (num > 0) {
        if (num >= values[i]) {
            sb.append(symbols[i]);
            num -= values[i];
        } else {
            i++;
        }
    }

    return sb.toString();
}

}
  • 时间复杂度:O(1),因为我们使用固定的数组来进行转换,遍历次数最多为常数。
  • 空间复杂度:O(1),只需要常数级别的额外空间来存储数组和变量。

LeetCode运行结果:

方法二:枚举

除了贪心算法外,我们还可以使用枚举类型来存储每种符号的信息,使得代码更加模块化和可维护。

首先,创建一个枚举类型Symbol,用来存储每种符号的信息,包括符号、数值、表示方式(在左边还是右边)以及特殊情况(是否需要减去另一个符号所代表的数值)。

然后,我们将所有符号按照数值从大到小排序,遍历符号数组,每次查看给定的整数中有多少个当前符号所对应的数值,将其加入结果字符串,并用给定的整数减去已经加过的数值。在遍历时还需要判断是否存在特殊情况,如果存在,则需要进行相应的处理。

public class Solution {
    enum Symbol {
        M(1000, "M", true),
        CM(900, "CM", false),
        D(500, "D", true),
        CD(400, "CD", false),
        C(100, "C", true),
        XC(90, "XC", false),
        L(50, "L", true),
        XL(40, "XL", false),
        X(10, "X", true),
        IX(9, "IX", false),
        V(5, "V", true),
        IV(4, "IV", false),
        I(1, "I", true);

        int value;
        String symbol;
        boolean canRepeat;

        Symbol(int value, String symbol, boolean canRepeat) {
            this.value = value;
            this.symbol = symbol;
            this.canRepeat = canRepeat;
        }
    }

    public String intToRoman(int num) {
        StringBuilder sb = new StringBuilder();

        for (Symbol s : Symbol.values()) {
            while (num >= s.value) {
                if (!s.canRepeat && sb.length() > 0 && sb.substring(sb.length() - 1).equals(s.symbol)) {
                    // 处理特殊情况
                    sb.deleteCharAt(sb.length() - 1);
                    sb.append(getSymbol(s, Symbol.values()));
                } else {
                    // 添加普通符号
                    sb.append(s.symbol);
                }
                num -= s.value;
            }
        }

        return sb.toString();
    }

    private String getSymbol(Symbol s, Symbol[] symbols) {
        for (int i = 0; i < symbols.length - 1; i++) {
            if (symbols[i] == s) {
                return symbols[i+1].symbol;
            }
        }
        return "";
    }
}
  • 时间复杂度:O(1),因为我们使用固定的枚举类型进行转换,遍历次数最多为常数。
  • 空间复杂度:O(1),只需要常数级别的额外空间来存储枚举类型、字符串和变量。

LeetCode运行结果:

方法三:硬编码数字

使用硬编码的方法实现整数转罗马数字可以通过以下步骤进行:

  1. 定义四个数组thousands、hundreds、tens和ones,分别表示千位、百位、十位和个位上对应的罗马数字。
  2. 根据给定的整数,依次取出千位、百位、十位和个位上的数值。可以通过整数除以相应的倍数来获取:千位上的数值为num / 1000,百位上的数值为(num % 1000) / 100,十位上的数值为(num % 100) / 10,个位上的数值为num % 10。
  3. 根据取出的数值,从数组中获取相应的罗马数字。例如,千位上的数值为3,则对应的罗马数字为thousands[3]。
  4. 将获取到的罗马数字依次拼接到结果字符串中。
  5. 返回结果字符串作为整数转换后的罗马数字表示。
public class Solution {
    public String intToRoman(int num) {
        String[] thousands = {"", "M", "MM", "MMM"};
        String[] hundreds = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
        String[] tens = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
        String[] ones = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};

        StringBuilder sb = new StringBuilder();
        sb.append(thousands[num / 1000]);// 千位上的数值
        sb.append(hundreds[(num % 1000) / 100]);// 百位上的数值
        sb.append(tens[(num % 100) / 10]);// 十位上的数值
        sb.append(ones[num % 10]);// 个位上的数值

        return sb.toString();
    }
}
  • 时间复杂度:O(1),因为我们进行固定次数的操作,与输入的整数大小无关。
  • 空间复杂度:O(1),只需要常数级别的额外空间来存储数组、字符串和变量。

LeetCode运行结果:

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

正在奋斗的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值