力扣算法篇解题三

一、盛最多水的容器

这是一个寻找容器最大存水量的问题,涉及对数组中垂直线条的计算,找到形成最大面积的两条线。


1、数学分析

问题定义

  • 给定数组 height,每个元素表示垂直线的高度。
  • 选取两条线 ij,以 min(height[i], height[j]) 为高度,j - i 为宽度,计算形成的容器面积:
  [ {Area} = {min}(height[i], height[j]) times (j - i) ]
  • 找出最大面积。

解法

  1. 暴力解法:枚举所有可能的线对,计算面积,找到最大值。时间复杂度为 (O(n^2)),不符合效率要求。
  2. 双指针法:通过左右指针逐步收敛,动态优化面积。时间复杂度为 (O(n))。

2、解题思路

  1. 初始化双指针 leftright 指向数组两端。
  2. 计算当前面积,并与最大面积 max_area 比较。
  3. 移动指针:始终移动较短线对应的指针,以尝试增大面积。
  4. 重复步骤 2 和 3,直至 left >= right

3、C++ 实现

#include <iostream>
#include <vector>
using namespace std;

int maxArea(vector<int>& height) {
    int left = 0, right = height.size() - 1;
    int max_area = 0;

    while (left < right) {
        // 计算当前面积
        int current_area = min(height[left], height[right]) * (right - left);
        max_area = max(max_area, current_area);

        // 移动较短线的指针
        if (height[left] < height[right]) {
            ++left;
        } else {
            --right;
        }
    }
    return max_area;
}

4、Python 实现

from typing import List

def maxArea(height: List[int]) -> int:
    left, right = 0, len(height) - 1
    max_area = 0

    while left < right:
        # 计算当前面积
        current_area = min(height[left], height[right]) * (right - left)
        max_area = max(max_area, current_area)

        # 移动较短线的指针
        if height[left] < height[right]:
            left += 1
        else:
            right -= 1

    return max_area

5、复杂度分析

5.1、时间复杂度

  • O(n):双指针每次移动一步,共遍历整个数组一次。

5.2、空间复杂度

  • O(1):仅使用了常数额外空间。

二、整数转罗马数字

这是一个整数转罗马数字的问题,需要根据整数的值逐步匹配符号并生成对应的罗马数字字符串。


1、数学分析

罗马数字构造规则

  1. 使用预定义的罗马数字符号和值:
    • I (1), V (5), X (10), L (50), C (100), D (500), M (1000)
  2. 使用减法形式处理特定值:
    • 4 → IV, 9 → IX
    • 40 → XL, 90 → XC
    • 400 → CD, 900 → CM
  3. 按照从大到小的顺序匹配罗马数字。

解法

  1. 创建符号与值的映射表。
  2. 从最大值开始,依次匹配整数。
  3. 每次匹配后,减少当前数字,更新结果字符串。
  4. 重复直到数字为 0。

2、解题思路

  1. 准备映射表:
{values} = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
{symbols} = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
  1. 从高位开始遍历:
    • 如果当前值可以被减去,将对应符号加入结果,并减去值。
    • 重复直到数字小于当前值。
  2. 返回结果字符串。

3、C++ 实现

#include <iostream>
#include <vector>
#include <string>
using namespace std;

string intToRoman(int num) {
    // 罗马数字的值和符号
    vector<int> values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
    vector<string> symbols = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
    
    string result = "";

    // 遍历每个值
    for (int i = 0; i < values.size() && num > 0; ++i) {
        while (num >= values[i]) {
            result += symbols[i]; // 加入符号
            num -= values[i];     // 减去值
        }
    }

    return result;
}

4、Python 实现

def intToRoman(num: int) -> str:
    # 罗马数字的值和符号
    values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
    symbols = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]

    result = ""

    # 遍历每个值
    for i in range(len(values)):
        while num >= values[i]:
            result += symbols[i]  # 加入符号
            num -= values[i]      # 减去值

    return result

5、复杂度分析

5.1、时间复杂度

  • O(1):罗马数字最多包含 13 个符号。算法需要遍历固定的符号和值列表,对每个符号进行常数次操作。

5.2、空间复杂度

  • O(1):仅使用了常数空间存储结果和符号表。

三、罗马数字转整数

这是一个罗马数字转整数的问题,需要根据罗马数字字符的规则依次解析并累加数值,同时注意处理特殊的减法规则。


1、数学分析

罗马数字解析规则

  1. 每个字符对应一个整数值:
    • I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000
  2. 小数值在大数值左边时,表示减法:
    • IV = 4, IX = 9
    • XL = 40, XC = 90
    • CD = 400, CM = 900
  3. 小数值在大数值右边时,表示加法。

解法

  1. 从左到右遍历字符串,根据规则逐步解析。
  2. 若当前字符代表的数值小于下一字符,使用减法;否则使用加法。
  3. 最终累加结果即为整数值。

2、解题思路

  1. 准备映射表:
{mapping} = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
  1. 遍历罗马数字字符串:
    • 若当前字符对应值小于下一字符,累加负值。
    • 否则累加正值。
  2. 返回累加结果。

3、C++ 实现

#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;

int romanToInt(string s) {
    unordered_map<char, int> mapping = {
        {'I', 1}, {'V', 5}, {'X', 10}, 
        {'L', 50}, {'C', 100}, {'D', 500}, {'M', 1000}
    };

    int result = 0;
    for (int i = 0; i < s.size(); ++i) {
        int current = mapping[s[i]];
        int next = (i < s.size() - 1) ? mapping[s[i + 1]] : 0;
        
        // 判断加法或减法
        if (current < next) {
            result -= current;
        } else {
            result += current;
        }
    }

    return result;
}

4、Python 实现

def romanToInt(s: str) -> int:
    mapping = {
        'I': 1, 'V': 5, 'X': 10, 
        'L': 50, 'C': 100, 'D': 500, 'M': 1000
    }

    result = 0
    for i in range(len(s)):
        current = mapping[s[i]]
        next_value = mapping[s[i + 1]] if i < len(s) - 1 else 0
        
        # 判断加法或减法
        if current < next_value:
            result -= current
        else:
            result += current

    return result

5、复杂度分析

5.1、时间复杂度

  • O(n):其中 (n) 是罗马数字字符串的长度,每个字符处理一次。

5.2、空间复杂度

  • O(1):仅使用了常量级的存储空间。

四、最长公共前缀

这是一个最长公共前缀的问题,需要在一个字符串数组中找到所有字符串开头的最长公共部分。


1、数学分析

问题特点

  1. 公共前缀是每个字符串开头的相同部分。
  2. 如果任意两个字符串的某个位置字符不同,则最长公共前缀到此为止。

解法

  1. 逐字符比较
    • 假设第一个字符串为基准,从左到右逐字符与其他字符串对应位置比较。
    • 若发现不同或基准字符串已遍历完毕,则停止,当前已比较的部分即为最长公共前缀。
  2. 分治法
    • 将数组分成两部分,递归查找两部分的最长公共前缀。
    • 合并两部分的公共前缀即为整体公共前缀。
  3. 排序法
    • 先对数组排序,最长公共前缀必定是第一个字符串与最后一个字符串的公共前缀。
    • 比较两个字符串,逐字符找到公共部分。

2、解题思路

选择逐字符比较法实现:

  1. 如果数组为空,直接返回空字符串。
  2. 取第一个字符串为基准,与其他字符串逐一比较。
  3. 比较时,逐字符对比每个字符串的对应位置,若不同或到达末尾,则停止。
  4. 返回公共前缀部分。

3、C++ 实现

#include <iostream>
#include <vector>
#include <string>
using namespace std;

string longestCommonPrefix(vector<string>& strs) {
    if (strs.empty()) return "";

    // 以第一个字符串为基准
    string prefix = strs[0];
    for (int i = 1; i < strs.size(); ++i) {
        int j = 0;
        // 比较当前字符串与基准的公共部分
        while (j < prefix.size() && j < strs[i].size() && prefix[j] == strs[i][j]) {
            ++j;
        }
        // 更新公共前缀
        prefix = prefix.substr(0, j);
        // 如果公共前缀为空,直接返回
        if (prefix.empty()) return "";
    }

    return prefix;
}

4、Python 实现

def longestCommonPrefix(strs: list[str]) -> str:
    if not strs:
        return ""

    # 以第一个字符串为基准
    prefix = strs[0]
    for i in range(1, len(strs)):
        j = 0
        # 比较当前字符串与基准的公共部分
        while j < len(prefix) and j < len(strs[i]) and prefix[j] == strs[i][j]:
            j += 1
        # 更新公共前缀
        prefix = prefix[:j]
        # 如果公共前缀为空,直接返回
        if not prefix:
            return ""

    return prefix

5、复杂度分析

5.1、时间复杂度

  • O(S):其中 (S) 是所有字符串中字符总数。最坏情况下,需要对每个字符进行一次比较。

5.2、空间复杂度

  • O(1):仅使用了常量空间存储临时变量。

五、三数之和

这是一个数组与双指针问题,需要在数组中寻找所有满足条件的三元组,同时避免重复的结果。


1、数学分析

题目解析
我们需要找到三元组 ([a, b, c]) 满足:

  1. ( i ≠ j ≠ k ) , 且 ( n u m s [ i ] + n u m s [ j ] + n u m s [ k ] = 0 ) (i \neq j \neq k),且 (nums[i] + nums[j] + nums[k] = 0) (i=j=k),(nums[i]+nums[j]+nums[k]=0)

  2. 结果中不能有重复的三元组。

解法思路
3. 对数组进行排序,使得可以使用双指针法进行查找。
4. 对每个元素作为三元组的第一个元素,使用双指针找到剩余两个元素满足条件:

  • 左指针指向当前元素右侧起点,右指针指向数组末尾。
  • 如果当前三数之和为 0,则记录结果,同时跳过重复元素。
  • 如果和小于 0,则移动左指针以增加和。
  • 如果和大于 0,则移动右指针以减小和。
  1. 遍历完成后返回结果。

2、解题思路

  1. 对数组排序,时间复杂度为 ( O ( n log ⁡ n ) ) (O(n \log n)) (O(nlogn))
  2. 遍历数组,以当前元素作为第一个数。
  3. 对于每个第一个数,使用双指针从剩余部分寻找两个数的和为目标值。
  4. 跳过重复元素,避免结果中有重复的三元组。
  5. 返回所有找到的三元组。

3、C++ 实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> result;
    sort(nums.begin(), nums.end()); // 排序

    for (int i = 0; i < nums.size(); ++i) {
        if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复元素

        int target = -nums[i];
        int left = i + 1, right = nums.size() - 1;

        while (left < right) {
            int sum = nums[left] + nums[right];
            if (sum == target) {
                result.push_back({nums[i], nums[left], nums[right]});

                // 跳过重复元素
                while (left < right && nums[left] == nums[left + 1]) ++left;
                while (left < right && nums[right] == nums[right - 1]) --right;

                ++left;
                --right;
            } else if (sum < target) {
                ++left;
            } else {
                --right;
            }
        }
    }

    return result;
}

4、Python 实现

def threeSum(nums: list[int]) -> list[list[int]]:
    nums.sort()  # 排序
    result = []

    for i in range(len(nums)):
        if i > 0 and nums[i] == nums[i - 1]:  # 跳过重复元素
            continue

        target = -nums[i]
        left, right = i + 1, len(nums) - 1

        while left < right:
            total = nums[left] + nums[right]
            if total == target:
                result.append([nums[i], nums[left], nums[right]])

                # 跳过重复元素
                while left < right and nums[left] == nums[left + 1]:
                    left += 1
                while left < right and nums[right] == nums[right - 1]:
                    right -= 1

                left += 1
                right -= 1
            elif total < target:
                left += 1
            else:
                right -= 1

    return result

5、复杂度分析

5.1、时间复杂度

  • 排序复杂度为 ( O ( n log ⁡ n ) ) (O(n \log n)) (O(nlogn))
  • 遍历数组时每个元素都使用双指针,复杂度为 (O(n^2))。
  • 总时间复杂度为 (O(n^2))

5.2、空间复杂度

  • 排序使用 ( O ( log ⁡ n ) ) (O(\log n)) (O(logn)) 的额外空间。
  • 结果存储使用 (O(k)),其中 (k) 为结果中三元组的数量。
  • 总空间复杂度为 (O(k))

【注】:刚学会使用数学公式输入,前面的有空再调整,后续文章会注意数学公式输入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

人间酒中仙

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

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

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

打赏作者

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

抵扣说明:

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

余额充值