【每日算法】每天五道算法,最经典 150 题,掌握面试所有知识点(20/150)

序号题目难度
16接雨水困难
17罗马数字转整数简单
18整数转罗马数字中等
19最后一个单词的长度简单
20最长公共前缀简单

16、接雨水

题目大意:

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

动态规划(Dynamic Programming)是一种通过将原问题拆解为子问题,并以一定的方式保存子问题的解来避免重复计算,从而解决复杂问题的方法。在动态规划中,我们通常会用一个数组或表格来保存子问题的解,以便后续利用这些已解决的子问题来求解更大规模的问题。

解题思路:
当解决这个问题时,我们需要找到一个方法来计算被柱子挡住的雨水量。我们可以利用两个数组 leftMax 和 rightMax 来分别存储每个位置左侧和右侧的最大高度,然后根据这些信息来计算每个位置上能容纳的雨水量。

这种方法之所以有效,是因为我们需要找到每个位置上能容纳的雨水量,而这个量取决于该位置左侧和右侧的最大高度。

解题步骤:
步骤如下:

  1. 首先检查输入的柱子高度数组是否为空,若为空则直接返回 0。
  2. 创建两个数组 leftMaxrightMax 分别用来存储每个位置左侧和右侧的最大高度。
  3. 遍历数组,分别计算每个位置左侧和右侧的最大高度,存储在对应的数组中。
  4. 再次遍历数组,根据每个位置的左侧和右侧最大高度,计算该位置上能容纳的雨水量,并累加到结果中。
  5. 返回最终的累积雨水量作为结果。

完整代码:
以下是一个 Java 实现,用于计算给定柱子高度图下雨后能接多少雨水的问题:

public class TrappingRainWater {
    public static void main(String[] args) {
        int[] height = {0,1,0,2,1,0,1,3,2,1,2,1};
        int result = trap(height);
        System.out.println("能接的雨水量为: " + result);
    }

    public static int trap(int[] height) {
        if (height == null || height.length == 0) {
            return 0;
        }
        
        int n = height.length;
        int[] leftMax = new int[n];
        int[] rightMax = new int[n];
        
        // 计算每个位置左侧的最大高度
        leftMax[0] = height[0];
        for (int i = 1; i < n; i++) {
        //对于每个位置 i,它的左侧最大高度要么是它左边相邻位置的左侧最大高度 ,要么就是当前位置高度
            leftMax[i] = Math.max(leftMax[i - 1], height[i]);
        }
        
        // 计算每个位置右侧的最大高度
        rightMax[n - 1] = height[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            rightMax[i] = Math.max(rightMax[i + 1], height[i]);
        }
        
        //计算接雨水的总量:
        int ans = 0;
        // 循环遍历每个位置,对于每个位置 i,计算其能容纳的雨水量
        for (int i = 0; i < n; i++) {
        
        // 即取其左侧最大高度和右侧最大高度中较小的值,减去当前位置的高度,得到该位置上的雨水量
            ans += Math.min(leftMax[i], rightMax[i]) - height[i];
        }
        
        return ans;
    }
}

这段代码实现了一个 trap 方法,接受一个整型数组 height 表示柱子高度图,返回下雨后能接的雨水量。

代码解析:

这段 Java 代码用于计算给定数组 height 表示的柱状图下雨后能接的雨水量。下面是对这段代码的解析:

  1. main 方法中初始化了一个示例数组 height,然后调用 trap 方法计算能接的雨水量,并将结果打印输出。

  2. trap 方法接收一个整型数组 height 作为参数,返回一个整数表示能接的雨水量。

  3. trap 方法中,首先进行了参数校验,如果输入数组为空或长度为0,则直接返回0。

  4. 创建了两个与输入数组等长的数组 leftMaxrightMax,用于存储每个位置左侧和右侧的最大高度。

  5. 遍历数组 height 计算每个位置左侧的最大高度,并将结果存储在 leftMax 数组中。

  6. 再次反向遍历数组 height 计算每个位置右侧的最大高度,并将结果存储在 rightMax 数组中。

  7. 最后,再次遍历数组 height,根据每个位置的左侧和右侧最大高度,计算该位置上能容纳的雨水量,并累加到 ans 变量中。

  8. 返回最终的累积雨水量 ans 作为结果。

这段代码利用了动态规划的思想,通过预先计算每个位置左侧和右侧的最大高度,然后再根据这些信息计算每个位置上能容纳的雨水量,从而实现了高效的解题方法。

案例:
当我们有一个高度数组 height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] 时,我们可以通过一步一步计算来演示这段代码的执行过程。

  1. 计算每个位置左侧的最大高度:

    • 对于位置 0,leftMax[0] = height[0] = 0
    • 对于位置 1,leftMax[1] = max(leftMax[0], height[1]) = max(0, 1) = 1
    • 对于位置 2,leftMax[2] = max(leftMax[1], height[2]) = max(1, 0) = 1
    • 对于位置 3,leftMax[3] = max(leftMax[2], height[3]) = max(1, 2) = 2
    • 以此类推,计算得到 leftMax = [0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3]
  2. 计算每个位置右侧的最大高度:

    • 对于位置 11,rightMax[11] = height[11] = 1
    • 对于位置 10,rightMax[10] = max(rightMax[11], height[10]) = max(1, 2) = 2
    • 对于位置 9,rightMax[9] = max(rightMax[10], height[9]) = max(2, 1) = 2
    • 对于位置 8,rightMax[8] = max(rightMax[9], height[8]) = max(2, 3) = 3
    • 以此类推,计算得到 rightMax = [3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 1, 1]
  3. 计算接雨水的总量:

    • 对于位置 0,min(leftMax[0], rightMax[0]) - height[0] = min(0, 3) - 0 = 0
    • 对于位置 1,min(leftMax[1], rightMax[1]) - height[1] = min(1, 3) - 1 = 0
    • 对于位置 2,min(leftMax[2], rightMax[2]) - height[2] = min(1, 3) - 0 = 1
    • 对于位置 3,min(leftMax[3], rightMax[3]) - height[3] = min(2, 3) - 2 = 1
    • 以此类推,计算得到 ans = 6,即整个数组能容纳的雨水总量为 6。

通过以上步骤,我们可以清晰地看到这段代码是如何计算每个位置的左侧最大高度、右侧最大高度以及能容纳的雨水量的。

17、罗马数字转整数

题目大意:
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符    |   数值
----------------
I       |   1
V       |   5
X       |   10
L       |   50
C       |   100
D       |   500
M       |   1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。

解题步骤:
在Java中将给定的罗马数字转换成整数的解题步骤如下:

  1. 创建一个HashMap来存储罗马字符与对应的整数值的映射关系。
  2. 初始化一个变量 result 用于存储最终的整数结果。
  3. 遍历输入的罗马数字字符串,对于每一个字符:
    • 如果当前字符代表的数值比前一个字符代表的数值大,则需要减去前一个字符代表的数值的两倍,因为特殊情况需要减去前一个字符代表的数值;
    • 否则,直接加上当前字符代表的数值。
  4. 最后返回累加得到的 result 即为转换后的整数表示。

通过以上步骤,可以实现将给定的罗马数字转换成整数的功能。这种方法能够有效处理特殊情况,例如 IV、IX、XL、XC、CD、CM等情况。

完整代码:
Java实现:

import java.util.*;

public class RomanToInteger {
    public int romanToInt(String s) {
        Map<Character, Integer> map = new HashMap<>();
        map.put('I', 1);
        map.put('V', 5);
        map.put('X', 10);
        map.put('L', 50);
        map.put('C', 100);
        map.put('D', 500);
        map.put('M', 1000);

        int result = 0;
        for (int i = 0; i < s.length(); i++) {
            if (i > 0 && map.get(s.charAt(i)) > map.get(s.charAt(i - 1))) {
            //乘以 2 的目的是为了减去之前已经累加的前一个字符代表的数值。
                result += map.get(s.charAt(i)) - 2 * map.get(s.charAt(i - 1));
            } else {
            //第一个字符直接加进去的原因是因为在处理第一个字符时,并没有需要减去前一个字符代表的数值的情况。
                result += map.get(s.charAt(i));
            }
        }
        return result;
    }

    public static void main(String[] args) {
        RomanToInteger solution = new RomanToInteger();
        String romanNum = "XXVII";
        System.out.println("The integer representation of " + romanNum + " is: " + solution.romanToInt(romanNum));
    }
}

注意
这段代码:result += map.get(s.charAt(i)) - 2 * map.get(s.charAt(i - 1));
之前字符代表的数值已经被累加到 result 中了,所以我们需要用 2 倍的方式将它减去,从而得到正确的结果。

在这个例子中,我们定义了一个名为 romanToInt 的方法来实现罗马数字转整数的功能。我们使用了HashMap来存储罗马字符与相应整数的映射关系,并通过遍历输入的罗马数字字符串来计算最终的整数表示。

18、整数转罗马数字

题目大意:
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。

给你一个整数,将其转为罗马数字。

解题步骤:
要将一个整数转换为罗马数字,可以按照以下步骤进行:

  1. 定义一个 HashMap,将每个特殊情况(如4、9、40、90等)和对应的罗马数字字符存储起来。
  2. 定义一个数组,按照从大到小的顺序存储所有可能的数值(1000、900、500、400等)。
  3. 创建一个 StringBuilder 对象,用于存储最终的罗马数字结果。
  4. 遍历数组中的每个数值,检查当前整数是否大于等于该数值,如果是,则将对应的罗马数字字符添加到 StringBuilder 中,并从整数中减去该数值。
  5. 重复上述步骤直到整数变为0。
  6. 最后,返回 StringBuilder 中存储的字符串作为最终的罗马数字表示。

完整代码:
以下是一个 Java 程序,用于将给定的整数转换为对应的罗马数字:

import java.util.HashMap;

public class Solution {
    public String intToRoman(int num) {
        // 定义罗马数字字符和对应数值的映射关系
        HashMap<Integer, String> map = new HashMap<>();
        map.put(1, "I");
        map.put(4, "IV");
        map.put(5, "V");
        map.put(9, "IX");
        map.put(10, "X");
        map.put(40, "XL");
        map.put(50, "L");
        map.put(90, "XC");
        map.put(100, "C");
        map.put(400, "CD");
        map.put(500, "D");
        map.put(900, "CM");
        map.put(1000, "M");

        // 定义对应数值的数组,按从大到小的顺序排列
        // 将对应数值的数组按从大到小的顺序排列的目的是为了在转换过程中优先处理大的数值。
        int[] values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};

        StringBuilder roman = new StringBuilder();

        // 从最大的数值开始逐步减去并拼接对应的罗马数字字符
        for (int i = 0; i < values.length; i++) {
            while (num >= values[i]) {
                num -= values[i];
                roman.append(map.get(values[i]));
            }
        }

        return roman.toString();
    }
}

19、最后一个单词的长度

题目大意:
给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。

单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。
解题步骤:
题目要求我们找出给定字符串中最后一个单词的长度。下面是用 Java 实现的解题步骤:

  1. 首先,我们需要去除字符串末尾的空格,因为题目中要求返回最后一个单词的长度。
  2. 然后,从字符串末尾开始向前遍历,直到找到第一个非空格字符为止,这个位置就是最后一个单词的最后一个字符。
  3. 继续向前遍历,直到找到第一个空格字符或者到达字符串的开头,这样就可以确定最后一个单词的起始位置。
  4. 最后,计算最后一个单词的长度(结束位置减去起始位置加一)并返回即可。

完整代码:

下面是用 Java 编写的代码示例:

public int lengthOfLastWord(String s) {
    // 去除末尾空格
    s = s.trim();
    
    int end = s.length() - 1;
    int start = end;
    
    // 找到最后一个单词的起始位置
    while (start >= 0 && s.charAt(start) != ' ') {
        start--;
    }
    
    // 计算最后一个单词的长度
    return end - start;
}

20、最长公共前缀

题目大意:
编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

解题步骤:

  1. 首先,我们可以将数组中的第一个字符串作为初始的公共前缀 prefix
  2. 然后,我们遍历数组中的每个字符串,不断更新 prefix 以确保它是所有字符串的公共前缀。
  3. 对于数组中的每个字符串,我们从头开始逐个字符与 prefix 进行比较,直到遇到不匹配的字符或者达到某个字符串的末尾。
  4. 如果在遍历中发现了不匹配的字符,我们就将 prefix 更新为该位置之前的子串,因为这是所有字符串的公共前缀。
  5. 最后,当遍历完所有字符串后,prefix 就是数组中所有字符串的最长公共前缀。

完整代码:
下面是用 Java 编写的代码示例:

public String longestCommonPrefix(String[] strs) {
    if (strs == null || strs.length == 0) {
        return "";
    }
    
    String prefix = strs[0];
    
    for (int i = 1; i < strs.length; i++) {
    //判断字符串 strs[i] 是否以 prefix 开头 
    //indexOf 方法用于查找一个字符串在另一个字符串中第一次出现的位置
        while (strs[i].indexOf(prefix) != 0) {
	
	//substring(int beginIndex, int endIndex):返回从 beginIndex 开始到 endIndex - 1 结束的子字符串。
            prefix = prefix.substring(0, prefix.length() - 1);
            if (prefix.isEmpty()) {
                return "";
            }
        }
    }
    
    return prefix;
}

以上是使用 Java 实现的解题步骤和代码示例。这种方法的时间复杂度取决于最长公共前缀的长度,因为我们需要遍历整个数组,对于长度为 n 的字符串数组,最坏情况下的时间复杂度为 O(S),其中 S 是所有字符串的长度之和。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值