普通视图

发现新文章,点击刷新页面。
今天 — 2025年4月3日LeetCode 每日一题题解

每日一题-有序三元组中的最大值 II🟡

2025年4月3日 00:00

给你一个下标从 0 开始的整数数组 nums

请你从所有满足 i < j < k 的下标三元组 (i, j, k) 中,找出并返回下标三元组的最大值。如果所有满足条件的三元组的值都是负数,则返回 0

下标三元组 (i, j, k) 的值等于 (nums[i] - nums[j]) * nums[k]

 

示例 1:

输入:nums = [12,6,1,2,7]
输出:77
解释:下标三元组 (0, 2, 4) 的值是 (nums[0] - nums[2]) * nums[4] = 77 。
可以证明不存在值大于 77 的有序下标三元组。

示例 2:

输入:nums = [1,10,3,4,19]
输出:133
解释:下标三元组 (1, 2, 4) 的值是 (nums[1] - nums[2]) * nums[4] = 133 。
可以证明不存在值大于 133 的有序下标三元组。 

示例 3:

输入:nums = [1,2,3]
输出:0
解释:唯一的下标三元组 (0, 1, 2) 的值是一个负数,(nums[0] - nums[1]) * nums[2] = -3 。因此,答案是 0 。

 

提示:

  • 3 <= nums.length <= 105
  • 1 <= nums[i] <= 106

【小羊肖恩】从暴力到贪心:想清楚贪心的每一步,就可以顺序遍历啦~!

作者 Yawn_Sean
2023年10月1日 12:23

对于这个问题,数据范围是 $100$ 的情况,我们可以直接暴力枚举所有合法的 $(i, j, k)$ 三元组,看其值取最大即可。 注意要求如果是负值时,返回 $0$.

时间复杂度为 $\mathcal{O}(n^3)$.

具体代码如下——

###Python

class Solution:
    def maximumTripletValue(self, nums: List[int]) -> int:
        n = len(nums)
        ans = 0
        for k in range(n):
            for j in range(k):
                for i in range(j):
                    ans = max(ans, (nums[i] - nums[j]) * nums[k])
        return ans

接下来我们思考如何处理 $n \leq 10^5$ 的情况。

首先,输出的数值最小为 $0$,同时数组中每个元素均为正数,因此,我们要让 (nums[i] - nums[j]) * nums[k] 最大,只需要对于固定的 $k$ 找到其前面 nums[i] - nums[j] 的最大值。

为了使得 nums[i] - nums[j] 尽可能大,我们需要对于固定的 j 找到其前面最大的 nums[i] 再将两者相减。

以上逻辑可见代码注释。

因此我们只需要维护三个东西:到当前位置的最大值,到当前位置为止最大的 nums[i] - nums[j],到当前位置为止最大的 (nums[i] - nums[j]) * nums[k]. 而从上面的分析可以看出,这些都可以遍历过程中实现。

因此时间复杂度是 $\mathcal{O}(n)$ 的。

具体代码如下——

###Python

class Solution:
    def maximumTripletValue(self, nums: List[int]) -> int:
        # 当前最大值
        curr_max = 0
        # 当前最大的 nums[i] - nums[j]
        curr_v = 0
        # 当前最大的 (nums[i] - nums[j]) * nums[k]
        ans = 0
        n = len(nums)
        
        for i in range(n):
            # 答案的最大值根据最大的 nums[i] - nums[j] 和当前数值的乘积更新
            ans = max(ans, nums[i] * curr_v)
            # nums[i] - nums[j] 的最大值根据此前最大值减去当前数值更新
            curr_v = max(curr_v, curr_max - nums[i])
            # 更新前缀最大值
            curr_max = max(curr_max, nums[i])
        return ans

两种方法:枚举 j / 枚举 k(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2023年10月1日 12:10

方法一:枚举 j

枚举 $j$,为了让 $(\textit{nums}[i] - \textit{nums}[j]) * \textit{nums}[k]$ 尽量大,我们需要知道 $j$ 左侧元素的最大值(让 $\textit{nums}[i] - \textit{nums}[j]$ 尽量大),以及 $j$ 右侧元素的最大值(让乘积尽量大)。

也就是计算 $\textit{nums}$ 的前缀最大值 $\textit{preMax}$ 和后缀最大值 $\textit{sufMax}$,这可以用递推预处理:

  • $\textit{preMax}[i] = \max(\textit{preMax}[i-1], \textit{nums}[i])$
  • $\textit{sufMax}[i] = \max(\textit{sufMax}[i+1], \textit{nums}[i])$

代码实现时,可以只预处理 $\textit{sufMax}$ 数组,$\textit{preMax}$ 可以在计算答案的同时算出来。

视频讲解 第二题。

class Solution:
    def maximumTripletValue(self, nums: List[int]) -> int:
        n = len(nums)
        suf_max = [0] * (n + 1)
        for i in range(n - 1, 1, -1):
            suf_max[i] = max(suf_max[i + 1], nums[i])

        ans = pre_max = 0
        for j, x in enumerate(nums):
            ans = max(ans, (pre_max - x) * suf_max[j + 1])
            pre_max = max(pre_max, x)
        return ans
class Solution {
    public long maximumTripletValue(int[] nums) {
        int n = nums.length;
        int[] sufMax = new int[n + 1];
        for (int i = n - 1; i > 1; i--) {
            sufMax[i] = Math.max(sufMax[i + 1], nums[i]);
        }

        long ans = 0;
        int preMax = nums[0];
        for (int j = 1; j < n - 1; j++) {
            ans = Math.max(ans, (long) (preMax - nums[j]) * sufMax[j + 1]);
            preMax = Math.max(preMax, nums[j]);
        }
        return ans;
    }
}
class Solution {
public:
    long long maximumTripletValue(vector<int>& nums) {
        int n = nums.size();
        vector<int> suf_max(n + 1);
        for (int i = n - 1; i > 1; i--) {
            suf_max[i] = max(suf_max[i + 1], nums[i]);
        }

        long long ans = 0;
        int pre_max = nums[0];
        for (int j = 1; j < n - 1; j++) {
            ans = max(ans, 1LL * (pre_max - nums[j]) * suf_max[j + 1]);
            pre_max = max(pre_max, nums[j]);
        }
        return ans;
    }
};
#define MAX(a, b) ((b) > (a) ? (b) : (a))

long long maximumTripletValue(int* nums, int n) {
    int* suf_max = malloc(n * sizeof(int));
    suf_max[n - 1] = nums[n - 1];
    for (int i = n - 2; i > 1; i--) {
        suf_max[i] = MAX(suf_max[i + 1], nums[i]);
    }

    long long ans = 0;
    int pre_max = nums[0];
    for (int j = 1; j < n - 1; j++) {
        ans = MAX(ans, 1LL * (pre_max - nums[j]) * suf_max[j + 1]);
        pre_max = MAX(pre_max, nums[j]);
    }

    free(suf_max);
    return ans;
}
func maximumTripletValue(nums []int) int64 {
    ans := 0
    n := len(nums)
    sufMax := make([]int, n+1)
    for i := n - 1; i > 1; i-- {
        sufMax[i] = max(sufMax[i+1], nums[i])
    }

    preMax := 0
    for j, x := range nums {
        ans = max(ans, (preMax-x)*sufMax[j+1])
        preMax = max(preMax, x)
    }
    return int64(ans)
}
var maximumTripletValue = function(nums) {
    let n = nums.length;
    let sufMax = Array(n);
    sufMax[n - 1] = nums[n - 1];
    for (let i = n - 2; i > 1; i--) {
        sufMax[i] = Math.max(sufMax[i + 1], nums[i]);
    }

    let ans = 0;
    let preMax = nums[0];
    for (let j = 1; j < n - 1; j++) {
        ans = Math.max(ans, (preMax - nums[j]) * sufMax[j + 1]);
        preMax = Math.max(preMax, nums[j]);
    }
    return ans;
};
impl Solution {
    pub fn maximum_triplet_value(nums: Vec<i32>) -> i64 {
        let n = nums.len();
        let mut suf_max = vec![0; n + 1];
        for i in (2..n).rev() {
            suf_max[i] = suf_max[i + 1].max(nums[i]);
        }

        let mut ans = 0;
        let mut pre_max = nums[0];
        for j in 1..n - 1 {
            ans = ans.max((pre_max - nums[j]) as i64 * suf_max[j + 1] as i64);
            pre_max = pre_max.max(nums[j]);
        }
        ans
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 为 $\textit{nums}$ 的长度。
  • 空间复杂度:$\mathcal{O}(n)$。

方法二:枚举 k

枚举 $k$,我们需要知道 $k$ 左边 $\textit{nums}[i] - \textit{nums}[j]$ 的最大值。

类似 121. 买卖股票的最佳时机,为了计算 $\textit{nums}[i] - \textit{nums}[j]$ 的最大值,我们需要知道 $j$ 左边的 $\textit{nums}[i]$ 的最大值。

因此,在遍历的过程中:

  • 维护 $\textit{nums}[i]$ 的最大值 $\textit{preMax}$。
  • 维护 $\textit{preMax} - \textit{nums}[j]$ 的最大值 $\textit{maxDiff}$。
  • 计算 $\textit{maxDiff} \cdot \textit{nums}[k]$,更新答案的最大值。

代码实现时,要先更新 $\textit{ans}$,再更新 $\textit{maxDiff}$,最后更新 $\textit{preMax}$。为什么?

这个顺序是精心设置的:

  • 首先更新 $\textit{ans}$,此时 $\textit{maxDiff}$ 还没有更新,表示在当前元素左边的两个数的最大差值。
  • 然后更新 $\textit{maxDiff}$,此时 $\textit{preMax}$ 还没有更新,表示在当前元素左边的最大值。
  • 最后更新 $\textit{preMax}$。

能否修改更新顺序?

$\textit{ans}$ 依赖 $\textit{maxDiff}$,$\textit{maxDiff}$ 依赖 $\textit{preMax}$。如果修改更新顺序,那么 $\textit{maxDiff}$ 或者 $\textit{preMax}$ 会包含当前元素,就不是左边元素的计算结果了,这违反了题目 $i<j<k$ 的规定。

class Solution:
    def maximumTripletValue(self, nums: List[int]) -> int:
        ans = max_diff = pre_max = 0
        for x in nums:
            ans = max(ans, max_diff * x)
            max_diff = max(max_diff, pre_max - x)
            pre_max = max(pre_max, x)
        return ans
class Solution {
    public long maximumTripletValue(int[] nums) {
        long ans = 0;
        int maxDiff = 0;
        int preMax = 0;
        for (int x : nums) {
            ans = Math.max(ans, (long) maxDiff * x);
            maxDiff = Math.max(maxDiff, preMax - x);
            preMax = Math.max(preMax, x);
        }
        return ans;
    }
}
class Solution {
public:
    long long maximumTripletValue(vector<int>& nums) {
        long long ans = 0;
        int max_diff = 0, pre_max = 0;
        for (int x : nums) {
            ans = max(ans, 1LL * max_diff * x);
            max_diff = max(max_diff, pre_max - x);
            pre_max = max(pre_max, x);
        }
        return ans;
    }
};
#define MAX(a, b) ((b) > (a) ? (b) : (a))

long long maximumTripletValue(int* nums, int n) {
    long long ans = 0;
    int max_diff = 0, pre_max = 0;
    for (int i = 0; i < n; i++) {
        ans = MAX(ans, 1LL * max_diff * nums[i]);
        max_diff = MAX(max_diff, pre_max - nums[i]);
        pre_max = MAX(pre_max, nums[i]);
    }
    return ans;
}
func maximumTripletValue(nums []int) int64 {
    var ans, maxDiff, preMax int
    for _, x := range nums {
        ans = max(ans, maxDiff*x)
        maxDiff = max(maxDiff, preMax-x)
        preMax = max(preMax, x)
    }
    return int64(ans)
}
var maximumTripletValue = function(nums) {
    let ans = 0, maxDiff = 0, preMax = 0;
    for (const x of nums) {
        ans = Math.max(ans, maxDiff * x);
        maxDiff = Math.max(maxDiff, preMax - x);
        preMax = Math.max(preMax, x);
    }
    return ans;
};
impl Solution {
    pub fn maximum_triplet_value(nums: Vec<i32>) -> i64 {
        let mut ans = 0;
        let mut max_diff = 0;
        let mut pre_max = 0;
        for x in nums {
            ans = ans.max(max_diff as i64 * x as i64);
            max_diff = max_diff.max(pre_max - x);
            pre_max = pre_max.max(x);
        }
        ans
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 为 $\textit{nums}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

思考题

如果 $\textit{nums}$ 中有负数,要怎么做?

欢迎在评论区分享你的思路/代码。

更多相似题目,见下面数据结构题单中的「§0.1 枚举右,维护左」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 【本题相关】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

昨天 — 2025年4月2日LeetCode 每日一题题解

[Python3/Java/C++/Go/TypeScript] 一题一解:维护前缀最大值和最大差值(清晰题解)

作者 lcbin
2025年4月2日 06:21

方法一:维护前缀最大值和最大差值

我们用两个变量 $\textit{mx}$ 和 $\textit{mxDiff}$ 分别维护前缀最大值和最大差值,用一个变量 $\textit{ans}$ 维护答案。初始时,这些变量都为 $0$。

接下来,我们枚举数组的每个元素 $x$ 作为 $\textit{nums}[k]$,首先更新答案 $\textit{ans} = \max(\textit{ans}, \textit{mxDiff} \times x)$,然后我们更新最大差值 $\textit{mxDiff} = \max(\textit{mxDiff}, \textit{mx} - x)$,最后更新前缀最大值 $\textit{mx} = \max(\textit{mx}, x)$。

枚举完所有元素后,返回答案 $\textit{ans}$。

###python

class Solution:
    def maximumTripletValue(self, nums: List[int]) -> int:
        ans = mx = mx_diff = 0
        for x in nums:
            ans = max(ans, mx_diff * x)
            mx_diff = max(mx_diff, mx - x)
            mx = max(mx, x)
        return ans

###java

class Solution {
    public long maximumTripletValue(int[] nums) {
        long ans = 0, mxDiff = 0;
        int mx = 0;
        for (int x : nums) {
            ans = Math.max(ans, mxDiff * x);
            mxDiff = Math.max(mxDiff, mx - x);
            mx = Math.max(mx, x);
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    long long maximumTripletValue(vector<int>& nums) {
        long long ans = 0, mxDiff = 0;
        int mx = 0;
        for (int x : nums) {
            ans = max(ans, mxDiff * x);
            mxDiff = max(mxDiff, 1LL * mx - x);
            mx = max(mx, x);
        }
        return ans;
    }
};

###go

func maximumTripletValue(nums []int) int64 {
ans, mx, mxDiff := 0, 0, 0
for _, x := range nums {
ans = max(ans, mxDiff*x)
mxDiff = max(mxDiff, mx-x)
mx = max(mx, x)
}
return int64(ans)
}

###ts

function maximumTripletValue(nums: number[]): number {
    let [ans, mx, mxDiff] = [0, 0, 0];
    for (const x of nums) {
        ans = Math.max(ans, mxDiff * x);
        mxDiff = Math.max(mxDiff, mx - x);
        mx = Math.max(mx, x);
    }
    return ans;
}

###rust

impl Solution {
    pub fn maximum_triplet_value(nums: Vec<i32>) -> i64 {
        let mut ans: i64 = 0;
        let mut mx: i32 = 0;
        let mut mx_diff: i32 = 0;

        for &x in &nums {
            ans = ans.max(mx_diff as i64 * x as i64);
            mx_diff = mx_diff.max(mx - x);
            mx = mx.max(x);
        }

        ans
    }
}

时间复杂度 $O(n)$,其中 $n$ 是数组长度。空间复杂度 $O(1)$。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

有序三元组中的最大值 I

2025年3月14日 09:49

方法一:暴力枚举

枚举所有满足 $i \lt j \lt k$ 的三元组 $(i, j, k)$,返回所有值大于等于 $0$ 的三元组的最大值。

###C++

class Solution {
public:
    long long maximumTripletValue(vector<int>& nums) {
        int n = nums.size();
        long long res = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                for (int k = j + 1; k < n; k++) {
                    res = max(res, (long long)(nums[i] - nums[j]) * nums[k]);
                }
            }
        }
        return res;
    }
};

###Go

func maximumTripletValue(nums []int) int64 {
    n := len(nums)
    var res int64 = 0
    for i := 0; i < n; i++ {
        for j := i + 1; j < n; j++ {
            for k := j + 1; k < n; k++ {
                res = max(res, int64(nums[i] - nums[j]) * int64(nums[k]))
            }
        }
    }
    return res
}

###Python

class Solution:
    def maximumTripletValue(self, nums: List[int]) -> int:
        n = len(nums)
        res = 0
        for i in range(n):
            for j in range(i + 1, n):
                for k in range(j + 1, n):
                    res = max(res, (nums[i] - nums[j]) * nums[k])
        return res

###Java

class Solution {
    public long maximumTripletValue(int[] nums) {
        int n = nums.length;
        long res = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                for (int k = j + 1; k < n; k++) {
                    res = Math.max(res, (long) (nums[i] - nums[j]) * nums[k]);
                }
            }
        }
        return res;
    }
}

###JavaScript

var maximumTripletValue = function(nums) {
    let n = nums.length;
    let res = 0;
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            for (let k = j + 1; k < n; k++) {
                res = Math.max(res, (nums[i] - nums[j]) * nums[k]);
            }
        }
    }
    return res;
};

###TypeScript

function maximumTripletValue(nums: number[]): number {
    const n = nums.length;
    let res = 0;
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            for (let k = j + 1; k < n; k++) {
                res = Math.max(res, (nums[i] - nums[j]) * nums[k]);
            }
        }
    }
    return res;
}

###C#

public class Solution {
    public long MaximumTripletValue(int[] nums) {
        int n = nums.Length;
        long res = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                for (int k = j + 1; k < n; k++) {
                    res = Math.Max(res, (long)(nums[i] - nums[j]) * nums[k]);
                }
            }
        }
        return res;
    }
}

###C

long long maximumTripletValue(int* nums, int numsSize) {
    long long res = 0;
    for (int i = 0; i < numsSize; i++) {
        for (int j = i + 1; j < numsSize; j++) {
            for (int k = j + 1; k < numsSize; k++) {
                long long value = (long long)(nums[i] - nums[j]) * nums[k];
                if (value > res) {
                    res = value;
                }
            }
        }
    }
    return res;
}

###Rust

impl Solution {
    pub fn maximum_triplet_value(nums: Vec<i32>) -> i64 {
        let n = nums.len();
        let mut res = 0;
        for i in 0..n {
            for j in i+1..n {
                for k in j+1..n {
                    res = res.max((nums[i] - nums[j]) as i64 * nums[k] as i64);
                }
            }
        }
        res
    }
}

复杂度分析

  • 时间复杂度:$O(n^3)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。

  • 空间复杂度:$O(1)$。

方法二:贪心

固定三元组 $(i, j, k)$ 的 $j$ 和 $k$ 时,由值公式 $(\textit{nums}[i] - \textit{nums}[j]) \times \textit{nums}[k]$ 可知,$\textit{nums}[i]$ 取区间 $[0, j)$ 内的最大值时,$(\textit{nums}[i] - \textit{nums}[j]) \times \textit{nums}[k]$ 最大。使用两层循环分别枚举 $k$ 和 $j$,同时使用 $m$ 维护 $[0, j)$ 的最大值,返回所有 $(m - \textit{nums}[j]) \times \textit{nums}[k]$ 的最大值(若所有值都为负数,则返回 $0$)。

###C++

class Solution {
public:
    long long maximumTripletValue(vector<int>& nums) {
        int n = nums.size();
        long long res = 0;
        for (int k = 2; k < n; k++) {
            int m = nums[0];
            for (int j = 1; j < k; j++) {
                res = max(res, (long long)(m - nums[j]) * nums[k]);
                m = max(m, nums[j]);
            }
        }
        return res;
    }
};

###Go

func maximumTripletValue(nums []int) int64 {
    n := len(nums)
    var res int64 = 0
    for k := 2; k < n; k++ {
        m := nums[0]
        for j := 1; j < k; j++ {
            res = max(res, int64(m - nums[j]) * int64(nums[k]))
            m = max(m, nums[j])
        }
    }
    return res
}

###Python

class Solution:
    def maximumTripletValue(self, nums: List[int]) -> int:
        n = len(nums)
        res = 0
        for k in range(2, n):
            m = nums[0]
            for j in range(1, k):
                res = max(res, (m - nums[j]) * nums[k])
                m = max(m, nums[j])
        return res

###Java

class Solution {
    public long maximumTripletValue(int[] nums) {
        int n = nums.length;
        long res = 0;
        for (int k = 2; k < n; k++) {
            int m = nums[0];
            for (int j = 1; j < k; j++) {
                res = Math.max(res, (long)(m - nums[j]) * nums[k]);
                m = Math.max(m, nums[j]);
            }
        }
        return res;
    }
}

###JavaScript

var maximumTripletValue = function(nums) {
    let n = nums.length;
    let res = 0;
    for (let k = 2; k < n; k++) {
        let m = nums[0];
        for (let j = 1; j < k; j++) {
            res = Math.max(res, (m - nums[j]) * nums[k]);
            m = Math.max(m, nums[j]);
        }
    }
    return res;
};

###TypeScript

function maximumTripletValue(nums: number[]): number {
    const n = nums.length;
    let res = 0;
    for (let k = 2; k < n; k++) {
        let m = nums[0];
        for (let j = 1; j < k; j++) {
            res = Math.max(res, (m - nums[j]) * nums[k]);
            m = Math.max(m, nums[j]);
        }
    }
    return res;
}

###C#

public class Solution {
    public long MaximumTripletValue(int[] nums) {
        int n = nums.Length;
        long res = 0;
        for (int k = 2; k < n; k++) {
            int m = nums[0];
            for (int j = 1; j < k; j++) {
                res = Math.Max(res, (long)(m - nums[j]) * nums[k]);
                m = Math.Max(m, nums[j]);
            }
        }
        return res;
    }
}

###C

long long maximumTripletValue(int* nums, int numsSize) {
    long long res = 0;
    for (int k = 2; k < numsSize; k++) {
        int m = nums[0];
        for (int j = 1; j < k; j++) {
            long long value = (long long)(m - nums[j]) * nums[k];
            if (value > res) {
                res = value;
            }
            if (nums[j] > m) {
                m = nums[j];
            }
        }
    }
    return res;
}

###Rust

impl Solution {
    pub fn maximum_triplet_value(nums: Vec<i32>) -> i64 {
        let n = nums.len();
        let mut res = 0;
        for k in 2..n {
            let mut m = nums[0];
            for j in 1..k {
                res = res.max((m - nums[j]) as i64 * nums[k] as i64);
                m = m.max(nums[j]);
            }
        }
        res
    }
}

复杂度分析

  • 时间复杂度:$O(n^2)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。

  • 空间复杂度:$O(1)$。

方法三:贪心 + 前后缀数组

令数组 $\textit{nums}$ 的长度为 $n$。根据值公式 $(\textit{nums}[i] - \textit{nums}[j]) \times \textit{nums}[k]$ 可知,当固定 $j$ 时,$\textit{nums}[i]$ 和 $\textit{nums}[k]$ 分别取 $[0, j)$ 和 $[j + 1, n)$ 的最大值时,三元组的值最大。我们使用 $\textit{leftMax}[j]$ 和 $\textit{rightMax}[j]$ 维护前缀 $[0, j)$ 最大值和后缀 $[j + 1, n)$ 最大值,依次枚举 $j$,计算值 $(\textit{leftMax}[j] - \textit{nums}[j]) \times \textit{rightMax}[j]$,返回最大值(若所有值都为负数,则返回 $0$)。

###C++

class Solution {
public:
    long long maximumTripletValue(vector<int>& nums) {
        int n = nums.size();
        vector<int> leftMax(n), rightMax(n);
        for (int i = 1; i < n; i++) {
            leftMax[i] = max(leftMax[i - 1], nums[i - 1]);
            rightMax[n - 1 - i] = max(rightMax[n - i], nums[n - i]);
        }
        long long res = 0;
        for (int j = 1; j < n - 1; j++) {
            res = max(res, (long long)(leftMax[j] - nums[j]) * rightMax[j]);
        }
        return res;
    }
};

###Go

func maximumTripletValue(nums []int) int64 {
    n := len(nums)
    leftMax := make([]int, n)
    rightMax := make([]int, n)
    for i := 1; i < n; i++ {
        leftMax[i] = max(leftMax[i - 1], nums[i - 1])
    }
    for i := 1; i < n; i++ {
        rightMax[n - 1 - i] = max(rightMax[n - i], nums[n - i])
    }
    var res int64 = 0
    for j := 1; j < n - 1; j++ {
        res = max(res, int64((leftMax[j] - nums[j]) * rightMax[j]))
    }
    return res
}

###Python

class Solution:
    def maximumTripletValue(self, nums: List[int]) -> int:
        n = len(nums)
        leftMax = [0] * n
        rightMax = [0] * n
        for i in range(1, n):
            leftMax[i] = max(leftMax[i - 1], nums[i - 1])
            rightMax[n - 1 - i] = max(rightMax[n - i], nums[n - i])
        res = 0
        for j in range(1, n - 1):
            res = max(res, (leftMax[j] - nums[j]) * rightMax[j])
        return res

###Java

public class Solution {
    public long maximumTripletValue(int[] nums) {
        int n = nums.length;
        int[] leftMax = new int[n];
        int[] rightMax = new int[n];
        for (int i = 1; i < n; i++) {
            leftMax[i] = Math.max(leftMax[i - 1], nums[i - 1]);
            rightMax[n - 1 - i] = Math.max(rightMax[n - i], nums[n - i]);
        }
        long res = 0;
        for (int j = 1; j < n - 1; j++) {
            res = Math.max(res, (long)(leftMax[j] - nums[j]) * rightMax[j]);
        }
        return res;
    }
}

###JavaScript

var maximumTripletValue = function(nums) {
    const n = nums.length;
    const leftMax = new Array(n).fill(0);
    const rightMax = new Array(n).fill(0);
    for (let i = 1; i < n; i++) {
        leftMax[i] = Math.max(leftMax[i - 1], nums[i - 1]);
        rightMax[n - 1 - i] = Math.max(rightMax[n - i], nums[n - i]);
    }
    let res = 0;
    for (let j = 1; j < n - 1; j++) {
        res = Math.max(res, (leftMax[j] - nums[j]) * rightMax[j]);
    }
    return res;
};

###TypeScript

function maximumTripletValue(nums: number[]): number {
    const n = nums.length;
    const leftMax: number[] = new Array(n).fill(0);
    const rightMax: number[] = new Array(n).fill(0);
    for (let i = 1; i < n; i++) {
        leftMax[i] = Math.max(leftMax[i - 1], nums[i - 1]);
        rightMax[n - 1 - i] = Math.max(rightMax[n - i], nums[n - i]);
    }
    let res = 0;
    for (let j = 1; j < n - 1; j++) {
        res = Math.max(res, (leftMax[j] - nums[j]) * rightMax[j]);
    }
    return res;
}

###C#

public class Solution {
    public long MaximumTripletValue(int[] nums) {
        int n = nums.Length;
        int[] leftMax = new int[n];
        int[] rightMax = new int[n];
        for (int i = 1; i < n; i++) {
            leftMax[i] = Math.Max(leftMax[i - 1], nums[i - 1]);
            rightMax[n - 1 - i] = Math.Max(rightMax[n - i], nums[n - i]);
        }
        long res = 0;
        for (int j = 1; j < n - 1; j++) {
            res = Math.Max(res, (long)(leftMax[j] - nums[j]) * rightMax[j]);
        }
        return res;
    }
}

###C

long long maximumTripletValue(int *nums, int numsSize) {
    int leftMax[numsSize], rightMax[numsSize];
    leftMax[0] = 0;
    rightMax[numsSize - 1] = 0;
    for (int i = 1; i < numsSize; i++) {
        leftMax[i] = fmax(leftMax[i - 1], nums[i - 1]);
        rightMax[numsSize - 1 - i] = fmax(rightMax[numsSize - i], nums[numsSize - i]);
    }
    long long res = 0;
    for (int j = 1; j < numsSize - 1; j++) {
        long long temp = (long long)(leftMax[j] - nums[j]) * rightMax[j];
        res = fmax(res, temp);
    }
    return res;
}

###Rust

impl Solution {
    pub fn maximum_triplet_value(nums: Vec<i32>) -> i64 {
        let n = nums.len();
        let mut left_max = vec![0; n];
        let mut right_max = vec![0; n];
        for i in 1..n {
            left_max[i] = left_max[i - 1].max(nums[i - 1]);
            right_max[n - i - 1] = right_max[n - i].max(nums[n - i]);
        }
        let mut res = 0;
        for j in 1..n - 1 {
            res = res.max((left_max[j] - nums[j]) as i64 * right_max[j] as i64);
        }
        res
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。

  • 空间复杂度:$O(n)$。

方法四:贪心

类似于方法三,我们固定 $k$,那么当 $\textit{nums}[i] - \textit{nums}[j]$ 取最大值时,三元组的值最大。我们可以用 $\textit{imax}$ 维护 $\textit{nums}[i]$ 的最大值,$\textit{dmax}$ 维护 $\textit{nums}[i] - \textit{nums}[j]$ 的最大值,在枚举 $k$ 的过程中,更新 $\textit{dmax}$ 和 $\textit{imax}$。

###C++

class Solution {
public:
    long long maximumTripletValue(vector<int>& nums) {
        int n = nums.size();
        long long res = 0, imax = 0, dmax = 0;
        for (int k = 0; k < n; k++) {
            res = max(res, dmax * nums[k]);
            dmax = max(dmax, imax - nums[k]);
            imax = max(imax, static_cast<long long>(nums[k]));
        }
        return res;
    }
};

###Java

class Solution {
    public long maximumTripletValue(int[] nums) {
        int n = nums.length;
        long res = 0, imax = 0, dmax = 0;
        for (int k = 0; k < n; k++) {
            res = Math.max(res, dmax * nums[k]);
            dmax = Math.max(dmax, imax - nums[k]);
            imax = Math.max(imax, nums[k]);
        }
        return res;
    }
}

###C#

public class Solution {
    public long MaximumTripletValue(int[] nums) {
        int n = nums.Length;
        long res = 0, imax = 0, dmax = 0;
        for (int k = 0; k < n; k++) {
            res = Math.Max(res, dmax * nums[k]);
            dmax = Math.Max(dmax, imax - nums[k]);
            imax = Math.Max(imax, nums[k]);
        }
        return res;
    }
}

###Python

class Solution:
    def maximumTripletValue(self, nums: List[int]) -> int:
        n = len(nums)
        res, imax, dmax = 0, 0, 0
        for k in range(n):
            res = max(res, dmax * nums[k])
            dmax = max(dmax, imax - nums[k])
            imax = max(imax, nums[k])
        return res

###C

long long maximumTripletValue(int* nums, int numsSize) {
    long long res = 0, imax = 0, dmax = 0;
    for (int k = 0; k < numsSize; k++) {
        res = fmax(res, dmax * nums[k]);
        dmax = fmax(dmax, imax - nums[k]);
        imax = fmax(imax, nums[k]);
    }
    return res;
}

###Go

func maximumTripletValue(nums []int) int64 {
    n := len(nums)
    var res, imax, dmax int64 = 0, 0, 0
    for k := 0; k < n; k++ {
        res = max(res, dmax * int64(nums[k]))
        dmax = max(dmax, imax - int64(nums[k]))
        imax = max(imax, int64(nums[k]))
    }
    return res
}

###JavaScript

var maximumTripletValue = function(nums) {
    const n = nums.length;
    let res = 0, imax = 0, dmax = 0;
    for (let k = 0; k < n; k++) {
        res = Math.max(res, dmax * nums[k]);
        dmax = Math.max(dmax, imax - nums[k]);
        imax = Math.max(imax, nums[k]);
    }
    return res;
};

###TypeScript

function maximumTripletValue(nums: number[]): number {
    const n: number = nums.length;
    let res: number = 0, imax: number = 0, dmax: number = 0;
    for (let k = 0; k < n; k++) {
        res = Math.max(res, dmax * nums[k]);
        dmax = Math.max(dmax, imax - nums[k]);
        imax = Math.max(imax, nums[k]);
    }
    return res;
}

###Rust

impl Solution {
    pub fn maximum_triplet_value(nums: Vec<i32>) -> i64 {
        let mut res = 0;
        let mut imax = 0;
        let mut dmax = 0;
        for &num in nums.iter() {
            res = res.max(dmax * num as i64);
            dmax = dmax.max(imax - num as i64);
            imax = imax.max(num as i64);
        }
        res
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。

  • 空间复杂度:$O(1)$。

每日一题-有序三元组中的最大值 I🟢

2025年4月2日 00:00

给你一个下标从 0 开始的整数数组 nums

请你从所有满足 i < j < k 的下标三元组 (i, j, k) 中,找出并返回下标三元组的最大值。如果所有满足条件的三元组的值都是负数,则返回 0

下标三元组 (i, j, k) 的值等于 (nums[i] - nums[j]) * nums[k]

 

示例 1:

输入:nums = [12,6,1,2,7]
输出:77
解释:下标三元组 (0, 2, 4) 的值是 (nums[0] - nums[2]) * nums[4] = 77 。
可以证明不存在值大于 77 的有序下标三元组。

示例 2:

输入:nums = [1,10,3,4,19]
输出:133
解释:下标三元组 (1, 2, 4) 的值是 (nums[1] - nums[2]) * nums[4] = 133 。
可以证明不存在值大于 133 的有序下标三元组。 

示例 3:

输入:nums = [1,2,3]
输出:0
解释:唯一的下标三元组 (0, 1, 2) 的值是一个负数,(nums[0] - nums[1]) * nums[2] = -3 。因此,答案是 0 。

 

提示:

  • 3 <= nums.length <= 100
  • 1 <= nums[i] <= 106

2873. 有序三元组中的最大值 I

作者 stormsunshine
2023年12月13日 21:05

解法一

思路和算法

直观的思路是遍历每个下标三元组 $(i, j, k)$ 计算 $(\textit{nums}[i] - \textit{nums}[j]) \times \textit{nums}[k]$ 的值,并得到最大值。

用 $\textit{maxValue}$ 表示下标三元组的最大值,初始时 $\textit{maxValue} = 0$,遍历过程中如果遇到一个下标三元组的值大于 $\textit{maxValue}$,则用该下标三元组的值更新 $\textit{maxValue}$。遍历结束之后,$\textit{maxValue}$ 即为下标三元组的最大值。

根据题目要求,如果所有下标三元组的值都是负数则返回 $0$。由于 $\textit{maxValue}$ 的初始值是 $0$,因此如果所有下标三元组的值都是负数,则 $\textit{maxValue}$ 的值不会更新,符合题目要求。

代码

###Java

class Solution {
    public long maximumTripletValue(int[] nums) {
        long maxValue = 0;
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                for (int k = j + 1; k < n; k++) {
                    long value = (long) (nums[i] - nums[j]) * nums[k];
                    maxValue = Math.max(maxValue, value);
                }
            }
        }
        return maxValue;
    }
}

###C#

public class Solution {
    public long MaximumTripletValue(int[] nums) {
        long maxValue = 0;
        int n = nums.Length;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                for (int k = j + 1; k < n; k++) {
                    long value = (long) (nums[i] - nums[j]) * nums[k];
                    maxValue = Math.Max(maxValue, value);
                }
            }
        }
        return maxValue;
    }
}

复杂度分析

  • 时间复杂度:$O(n^3)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。需要使用三层循环遍历所有的下标三元组并计算值。

  • 空间复杂度:$O(1)$。

解法二

思路和算法

下标三元组 $(i, j, k)$ 满足 $i < j < k$。当下标 $j$ 确定时,为了使下标三元组的值最大,应使 $\textit{nums}[i]$ 和 $\textit{nums}[k]$ 最大。用 $n$ 表示数组 $\textit{nums}$ 的长度,则 $1 \le j \le n - 2$,当下标 $j$ 确定时,$\textit{nums}[i]$ 应取数组 $\textit{nums}$ 的下标范围 $[0, j - 1]$ 中的最大值,$\textit{nums}[k]$ 应取数组 $\textit{nums}$ 的下标范围 $[j + 1, n - 1]$ 中的最大值,因此需要维护数组 $\textit{nums}$ 的每个前缀与后缀的最大值。

创建长度为 $n$ 的数组 $\textit{leftMax}$ 和 $\textit{rightMax}$,其中 $\textit{leftMax}[i]$ 表示数组 $\textit{nums}$ 的下标范围 $[0, i]$ 中的最大值,$\textit{rightMax}[i]$ 表示数组 $\textit{nums}$ 的下标范围 $[i, n - 1]$ 中的最大值。数组 $\textit{leftMax}$ 和 $\textit{rightMax}$ 的元素值计算如下。

  1. 当 $i = 0$ 时,$\textit{leftMax}[i] = \textit{nums}[i]$;当 $i = n - 1$ 时,$\textit{rightMax}[i] = \textit{nums}[i]$。

  2. 当 $i > 0$ 时,$\textit{leftMax}[i] = \max(\textit{leftMax}[i - 1], \textit{nums}[i])$;当 $i < n - 1$ 时,$\textit{rightMax}[i] = \max(\textit{rightMax}[i + 1], \textit{nums}[i])$。

计算得到数组 $\textit{leftMax}$ 和 $\textit{rightMax}$ 的值之后,即可计算下标三元组的最大值。对于每个 $1 \le j \le n - 2$,当下标 $j$ 固定时,下标三元组 $(i, j, k)$ 的最大值是 $(\textit{leftMax}[j - 1] - \textit{nums}[j]) \times \textit{rightMax}[j + 1]$。遍历所有可能的下标 $j$ 之后即可得到下标三元组的最大值。

特别地,当所有下标三元组的值都是负数时,返回 $0$。

代码

###Java

class Solution {
    public long maximumTripletValue(int[] nums) {
        long maxValue = 0;
        int n = nums.length;
        int[] leftMax = new int[n];
        leftMax[0] = nums[0];
        for (int i = 1; i < n; i++) {
            leftMax[i] = Math.max(leftMax[i - 1], nums[i]);
        }
        int[] rightMax = new int[n];
        rightMax[n - 1] = nums[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            rightMax[i] = Math.max(rightMax[i + 1], nums[i]);
        }
        for (int j = 1; j < n - 1; j++) {
            long value = (long) (leftMax[j - 1] - nums[j]) * (rightMax[j + 1]);
            maxValue = Math.max(maxValue, value);
        }
        return maxValue;
    }
}

###C#

public class Solution {
    public long MaximumTripletValue(int[] nums) {
        long maxValue = 0;
        int n = nums.Length;
        int[] leftMax = new int[n];
        leftMax[0] = nums[0];
        for (int i = 1; i < n; i++) {
            leftMax[i] = Math.Max(leftMax[i - 1], nums[i]);
        }
        int[] rightMax = new int[n];
        rightMax[n - 1] = nums[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            rightMax[i] = Math.Max(rightMax[i + 1], nums[i]);
        }
        for (int j = 1; j < n - 1; j++) {
            long value = (long) (leftMax[j - 1] - nums[j]) * (rightMax[j + 1]);
            maxValue = Math.Max(maxValue, value);
        }
        return maxValue;
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。计算前缀最大值数组与后缀最大值数组的时间是 $O(n)$,计算三元组最大值的时间是 $O(n)$。

  • 空间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。需要创建两个长度为 $n$ 的数组。

解法三

思路和算法

下标三元组 $(i, j, k)$ 满足 $i < j < k$。可以从左到右遍历数组 $\textit{nums}$,对于每个下标 $k$ 计算 $\textit{nums}[i] - \textit{nums}[j]$ 的最大值,得到下标 $k$ 确定时的下标三元组的最大值。

遍历过程中需要维护已经遍历过的元素的最大差值 $\textit{maxDiff}$ 和最大元素值 $\textit{maxNum}$,初始时 $\textit{maxDiff} = \textit{maxNum} = 0$。当遍历到元素 $\textit{num}$ 时,执行如下操作。

  1. 将当前元素之前的最大差值 $\textit{maxDiff}$ 作为 $\textit{nums}[i] - \textit{nums}[j]$ 的最大值,将当前元素 $\textit{num}$ 作为 $\textit{nums}[k]$,计算下标三元组的值 $\textit{maxDiff} \times \textit{num}$,使用该下标三元组的值更新结果。

  2. 计算当前元素之前的最大元素值与当前元素值之差 $\textit{maxNum} - \textit{num}$,并更新 $\textit{maxDiff}$ 的值。

  3. 使用当前元素值更新最大元素值 $\textit{maxNum}$。

上述操作中,每次计算下标三元组的值时,$\textit{maxDiff}$ 一定是当前元素之前的两个元素 $\textit{nums}[i]$ 与 $\textit{nums}[j]$ 之差且一定有 $i < j$,因此可以确保得到正确的结果。

代码

###Java

class Solution {
    public long maximumTripletValue(int[] nums) {
        long maxValue = 0;
        int maxDiff = 0;
        int maxNum = 0;
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            maxValue = Math.max(maxValue, (long) maxDiff * num);
            maxDiff = Math.max(maxDiff, maxNum - num);
            maxNum = Math.max(maxNum, num);
        }
        return maxValue;
    }
}

###C#

public class Solution {
    public long MaximumTripletValue(int[] nums) {
        long maxValue = 0;
        int maxDiff = 0;
        int maxNum = 0;
        int n = nums.Length;
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            maxValue = Math.Max(maxValue, (long) maxDiff * num);
            maxDiff = Math.Max(maxDiff, maxNum - num);
            maxNum = Math.Max(maxNum, num);
        }
        return maxValue;
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。需要遍历数组一次。

  • 空间复杂度:$O(1)$。

前缀后缀最大

作者 simidagogogo
2023年10月1日 18:38

前缀后缀最大,时间复杂度O(n),空间复杂度O(n)

###cpp

class Solution {
public:
    long long maximumTripletValue(vector<int>& nums) {
        int n = nums.size();
        
        // 记录每个元素左边最大和右边最大
        vector<pair<long long, long long>> vec(n);
        
        int left = 0, right = 0;
        for (int i = n - 1; i >= 0; --i) {
            vec[i].second = right;
            right = std::max(nums[i], right);
        }
        
        for (int i = 0; i < n; ++i) {
            vec[i].first = left;
            left = std::max(nums[i], left);
        }
        
        long long res = 0;
        for (int i = 0; i < n; ++i) {
            long long num = (vec[i].first - nums[i]) * (vec[i].second);
            if (res < num) res = num;
        }
        return res;
    }
};
昨天以前LeetCode 每日一题题解

有关官方解答方法1中正向枚举的疑问与补充

作者 xiongxyowo
2023年7月27日 10:08

这里首先复述下官解方法1的做法。用dp[i]来表示枚举到第i个题目时,可以获得的最大分数。

  • 如果不选第i个题,有dp[i]=dp[i−1]
  • 如果选第i个题,需要遍历j$\in$[0, i-1],找到满足j + brainpower[j] < i的值最大的dp[j],从其转移而来,有dp[i] = dp[j] + points[i]

最终dp[i]的结果则为dp[i] = max(dp[i - 1], dp[j] + points[i]),代码实现如下:

class Solution:
    def mostPoints(self, questions: List[List[int]]) -> int:
        n = len(questions)
        dp = [0] * n
        dp[0] = questions[0][0]
        for i in range(1, n):
            not_select = dp[i - 1]  # 不解决
            select = questions[i][0]  # 解决
            plus = 0  # 如果从上一个已解决的题转化而来,可获得的额外分数
            for j in range(0, i):
                if j + questions[j][1] < i:
                    plus = max(plus, dp[j])
            dp[i] = max(not_select, select + plus)
        return dp[n - 1]

而以上解法是错误的(并非溢出问题),只能通过一半的用例,一个较短的错误用例如下:

[[21,2],[1,2],[12,5],[7,2],[35,3],[32,2],[80,2],[91,5],[92,3],[27,3],[19,1],[37,3],[85,2],[33,4],[25,1],[91,4],[44,3],[93,3],[65,4],[82,3],[85,5],[81,3],[29,2],[25,1],[74,2],[58,1],[85,1],[84,2],[27,2],[47,5],[48,4],[3,2],[44,3],[60,5],[19,2],[9,4],[29,5],[15,3],[1,3],[60,2],[63,3],[79,3],[19,1],[7,1],[35,1],[55,4],[1,4],[41,1],[58,5]]

预计输出为:

781

实际输出为:

805

可以发现实际输出更大了,也就是多贪心了状态。那么问题出在哪呢?

这里举一个更简单的例子:

[[100,5],[1,1],[1,2],[1,1]]

预计输出应为100,也就是只选第0个问题回答,而以上代码的输出为101。将此时的代码的dp数组输出,有:

[100, 100, 100, 101]

其计算逻辑为:

dp[0] = points[0] = 100
dp[1] = dp[0] = 100
dp[2] = dp[1] = 100
dp[3] = dp[1] + points[3] = 101 (因为dp[1]的brainpower[i]为1,与答第2题冲突,但与答第3题不冲突)

可以发现,问题出在dp[1]上。因为dp[1]是由dp[0]转移而来,隐式的选择了dp[0],因此其实际的"脑力恢复期"为max(0 + brainpower[0], 1 + brainbrainpower[1]) = 5,而非1 + brainbrainpower[1] = 2。也就是说,判断dp[j]是否能由dp[i]转化而来,不能只依靠:

if j + questions[j][1] < i:
    plus = max(plus, dp[j])

因为当遍历到第j个元素时,某个小于j的已选元素k的"脑力恢复期"可能仍未结束,因此不能只考虑选择j所造成的"脑力恢复期"。

每日一题-解决智力问题🟡

2025年4月1日 00:00

给你一个下标从 0 开始的二维整数数组 questions ,其中 questions[i] = [pointsi, brainpoweri] 。

这个数组表示一场考试里的一系列题目,你需要 按顺序 (也就是从问题 0 开始依次解决),针对每个问题选择 解决 或者 跳过 操作。解决问题 i 将让你 获得  pointsi 的分数,但是你将 无法 解决接下来的 brainpoweri 个问题(即只能跳过接下来的 brainpoweri 个问题)。如果你跳过问题 i ,你可以对下一个问题决定使用哪种操作。

  • 比方说,给你 questions = [[3, 2], [4, 3], [4, 4], [2, 5]] :
    • 如果问题 0 被解决了, 那么你可以获得 3 分,但你不能解决问题 1 和 2 。
    • 如果你跳过问题 0 ,且解决问题 1 ,你将获得 4 分但是不能解决问题 2 和 3 。

请你返回这场考试里你能获得的 最高 分数。

 

示例 1:

输入:questions = [[3,2],[4,3],[4,4],[2,5]]
输出:5
解释:解决问题 0 和 3 得到最高分。
- 解决问题 0 :获得 3 分,但接下来 2 个问题都不能解决。
- 不能解决问题 1 和 2
- 解决问题 3 :获得 2 分
总得分为:3 + 2 = 5 。没有别的办法获得 5 分或者多于 5 分。

示例 2:

输入:questions = [[1,1],[2,2],[3,3],[4,4],[5,5]]
输出:7
解释:解决问题 1 和 4 得到最高分。
- 跳过问题 0
- 解决问题 1 :获得 2 分,但接下来 2 个问题都不能解决。
- 不能解决问题 2 和 3
- 解决问题 4 :获得 5 分
总得分为:2 + 5 = 7 。没有别的办法获得 7 分或者多于 7 分。

 

提示:

  • 1 <= questions.length <= 105
  • questions[i].length == 2
  • 1 <= pointsi, brainpoweri <= 105

解决智力问题

2022年1月26日 12:25

方法一:动态规划

提示 $1$

我们可以尝试用 $\textit{dp}[i]$ 来表示解决前 $i$ 道题目可以获得的最高分数。根据是否选择解决第 $i$ 道题目,会有以下两种情况:

  • 不解决第 $i$ 道题目,此时 $\textit{dp}[i] = \textit{dp}[i-1]$;
  • 解决第 $i$ 道题目,此时要么前面的题目都未解决,要么上一道解决的题目对应的冷冻期已经结束。具体而言:

$$
\textit{dp}[i] = \textit{points}[i] + \max(0, \max_{j \in [0, i - 1], j + \textit{brainpower}[j] < i} dp[j]).
$$

由于每一道题对应的冷冻期都不一样,因此我们很难在不通过遍历 $[0, i - 1]$ 闭区间内的全部下标,以判断对应的冷冻期是否结束的情况下更新 $\textit{dp}[i]$。我们假设题目的总数为 $n$,这样的时间复杂度为 $O(n^2)$,不符合题目要求。

提示 $2$

我们可以从无后效性的角度考虑动态规划「状态」的定义。对于每一道题目,解决与否会影响到后面一定数量题目的结果,但不会影响到前面题目的解决。因此我们可以考虑从反方向定义「状态」,即考虑解决每道题本身及以后的题目可以获得的最高分数。

思路与算法

根据提示 $2$,我们用 $\textit{dp}[i]$ 来表示解决第 $i$ 道题目及以后的题目可以获得的最高分数。同时,我们从后往前遍历题目,并更新 $\textit{dp}$ 数组。类似地,根据是否选择解决第 $i$ 道题目,会有以下两种情况:

  • 不解决第 $i$ 道题目,此时 $\textit{dp}[i] = \textit{dp}[i+1]$;
  • 解决第 $i$ 道题目,我们只能解决下标大于 $i + \textit{brainpower}[i]$ 的题目,而此时根据 $\textit{dp}$ 数组的定义,解决这些题目的最高分数为 $dp[i + \textit{brainpower}[i] + 1]$(当 $i \ge n$ 的情况下,我们认为 $dp[i] = 0$)。因此,我们有:

$$
\textit{dp}[i] = \textit{points}[i] + dp[i + \textit{brainpower}[i] + 1].
$$

综合上述两种情况,我们就得出了 $\textit{dp}[i]$ 的状态转移方程:

$$
\textit{dp}[i] = \max(\textit{dp}[i+1], \textit{points}[i] + dp[i + \textit{brainpower}[i] + 1]).
$$

在实际计算中,考虑到 $i \ge n$ 的边界条件,我们在定义 $\textit{dp}$ 数组时,可以预留 $dp[n] = 0$ 用来表示没有做任何题目的分数。那么上面的转移方程变为:

$$
\textit{dp}[i] = \max(\textit{dp}[i+1], \textit{points}[i] + dp[\min(n, i + \textit{brainpower}[i] + 1)]).
$$

最终,$dp[0]$ 即为考试中可以获得的最高分数,我们返回该数值作为答案。

细节

在计算 $\textit{dp}$ 数组时,数组元素的大小有可能超过 $32$ 为有符号整数的上界,因此对于 $\texttt{C++}$ 等语言,我们需要用 $64$ 位整数来存储 $\textit{dp}$ 数组。

代码

###C++

class Solution {
public:
    long long mostPoints(vector<vector<int>>& questions) {
        int n = questions.size();
        vector<long long> dp(n + 1);   // 解决每道题及以后题目的最高分数
        for (int i = n - 1; i >= 0; --i) {
            dp[i] = max(dp[i + 1], questions[i][0] + dp[min(n, i + questions[i][1] + 1)]);
        }
        return dp[0];
    }
};

###Python

class Solution:
    def mostPoints(self, questions: List[List[int]]) -> int:
        n = len(questions)
        dp = [0] * (n + 1)  # 解决每道题及以后题目的最高分数
        for i in range(n - 1, -1, -1):
            dp[i] = max(
                dp[i + 1], questions[i][0] + dp[min(n, i + questions[i][1] + 1)]
            )
        return dp[0]

###Java

class Solution {
    public long mostPoints(int[][] questions) {
        int n = questions.length;
        long[] dp = new long[n + 1]; // 解决每道题及以后题目的最高分数
        for (int i = n - 1; i >= 0; i--) {
            dp[i] = Math.max(dp[i + 1], questions[i][0] + dp[Math.min(n, i + questions[i][1] + 1)]);
        }
        return dp[0];
    }
}

###C#

public class Solution {
    public long MostPoints(int[][] questions) {
        int n = questions.Length;
        long[] dp = new long[n + 1]; // 解决每道题及以后题目的最高分数
        for (int i = n - 1; i >= 0; i--) {
            dp[i] = Math.Max(dp[i + 1], questions[i][0] + dp[Math.Min(n, i + questions[i][1] + 1)]);
        }
        return dp[0];
    }
}

###Go

func mostPoints(questions [][]int) int64 {
    n := len(questions)
    dp := make([]int64, n + 1) // 解决每道题及以后题目的最高分数
    for i := n - 1; i >= 0; i-- {
        dp[i] = max(dp[i + 1], int64(questions[i][0]) + dp[min(n, i + questions[i][1] + 1)])
    }
    return dp[0]
}

###C

long long max(long long a, long long b) {
    return a > b ? a : b;
}

long long min(long long a, long long b) {
    return a < b ? a : b;
}

long long mostPoints(int** questions, int questionsSize, int* questionsColSize) {
    long long dp[questionsSize + 1]; // 解决每道题及以后题目的最高分数
    memset(dp, 0, sizeof(dp));
    for (int i = questionsSize - 1; i >= 0; --i) {
        dp[i] = max(dp[i + 1], questions[i][0] + dp[min(questionsSize, i + questions[i][1] + 1)]);
    }
    long long result = dp[0];
    return result;
}

###JavaScript

var mostPoints = function(questions) {
    const n = questions.length;
    const dp = new Array(n + 1).fill(0); // 解决每道题及以后题目的最高分数
    for (let i = n - 1; i >= 0; i--) {
        dp[i] = Math.max(dp[i + 1], questions[i][0] + dp[Math.min(n, i + questions[i][1] + 1)]);
    }
    return dp[0];
};

###JavaScript

function mostPoints(questions: number[][]): number {
    const n = questions.length;
    const dp: number[] = new Array(n + 1).fill(0); // 解决每道题及以后题目的最高分数
    for (let i = n - 1; i >= 0; i--) {
        dp[i] = Math.max(dp[i + 1], questions[i][0] + dp[Math.min(n, i + questions[i][1] + 1)]);
    }
    return dp[0];
};

###Rust

impl Solution {
    pub fn most_points(questions: Vec<Vec<i32>>) -> i64 {
        let n = questions.len();
        let mut dp = vec![0i64; n + 1]; // 解决每道题及以后题目的最高分数
        for i in (0..n).rev() {
            dp[i] = dp[i + 1].max(questions[i][0] as i64 + dp[(n).min(i + questions[i][1] as usize + 1)]);
        }
        dp[0]
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 为 $\textit{questions}$ 数组的长度。即为动态规划计算可以获得的最高分数的时间复杂度。

  • 空间复杂度:$O(n)$,即为动态规划数组的空间开销。

【解决智力问题】正向 DP

作者 ikaruga
2022年1月16日 13:03

思路

  1. 动态规划
  2. 定义 vector<long long> dp 表示做到第 i 题的时候最高的得分
  3. 状态转移
  4. 选择跳过,不加此题得分,后续的题继承得分
  5. 选择做题,加上此题得分,跳过若干题后的下一题继承得分
  6. 答案为所有可能得分的最大值

答题

class Solution {
public:
    long long mostPoints(vector<vector<int>>& questions) {
        vector<long long> dp(questions.size(), 0);
        for (int i = 0; i < dp.size(); i++) {
            int next = i + 1;
            if (next < dp.size()) {
                dp[next] = max(dp[next], dp[i]);
            }

            next = i + questions[i][1] + 1;
            dp[i] += questions[i][0];
            if (next < dp.size()) {
                dp[next] = max(dp[next], dp[i]);
            }
        }
        auto ans = *max_element(dp.begin(), dp.end());
        return ans;
    }
};

致谢

感谢您的观看,希望对您有帮助,欢迎热烈的交流!

如果感觉还不错就点个赞吧~

我的力扣个人主页 中有我使用的做题助手项目链接,帮助我收集整理题目,可以方便的 visual studio 调试,欢迎关注,star

打家劫舍变形题,查表法 / 刷表法(Python/Java/C++/Go)

作者 endlesscheng
2022年1月16日 12:07

前言

本题其实是 198. 打家劫舍 的变形题:如果选 $\textit{questions}[i]$,那么接下来的 $\textit{brainpower}_i$ 个问题都不能选。打家劫舍那题相当于 $\textit{brainpower}_i=1$。

一、寻找子问题

在示例 1 中,我们要解决的问题(原问题)是:

  • 剩余问题的下标为 $[0,3]$,计算从这些问题中可以获得的最大分数。

讨论 $\textit{questions}[0]$ 选或不选

  • 如果不选,子问题为:剩余问题的下标为 $[1,3]$,计算从这些问题中可以获得的最大分数。
  • 如果选,接下来的 $2$ 个问题都不能选,子问题为:剩余问题的下标为 $[3,3]$,计算从这些问题中可以获得的最大分数。

由于选或不选都会把原问题变成一个和原问题相似的、规模更小的子问题,所以可以用递归解决。

二、状态定义与状态转移方程

根据上面的讨论,定义状态为 $\textit{dfs}(i)$,表示剩余问题的下标为 $[i,n-1]$,计算从这些问题中可以获得的最大分数。其中 $n$ 是 $\textit{questions}$ 的长度。

讨论 $\textit{questions}[i]$ 选或不选

  • 如果不选,子问题为:剩余问题的下标为 $[i+1,n-1]$,计算从这些问题中可以获得的最大分数,即 $\textit{dfs}(i+1)$。
  • 如果选,接下来的 $\textit{brainpower}_i$ 个问题都不能选,子问题为:剩余问题的下标为 $[i+\textit{brainpower}_i+1,n-1]$,计算从这些问题中可以获得的最大分数,即 $\textit{dfs}(i+\textit{brainpower}_i+1)$。

这两种情况取最大值,就得到了 $\textit{dfs}(i)$,即

$$
\textit{dfs}(i) = \max(\textit{dfs}(i+1),\textit{dfs}(i+\textit{brainpower}_i+1)+\textit{points}_i)
$$

对比一下,打家劫舍是 $\textit{dfs}(i) = \max(\textit{dfs}(i+1),\textit{dfs}(i+2)+\textit{nums}_i)$

递归边界:如果 $i\ge n$,那么 $\textit{dfs}(i)=0$。此时没有问题需要解决。

递归入口:$\textit{dfs}(0)$,这是原问题,也是答案。

三、递归搜索 + 保存递归返回值 = 记忆化搜索

考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:

  • 如果一个状态(递归入参)是第一次遇到,那么可以在返回前,把状态及其结果记到一个 $\textit{memo}$ 数组中。
  • 如果一个状态不是第一次遇到($\textit{memo}$ 中保存的结果不等于 $\textit{memo}$ 的初始值),那么可以直接返回 $\textit{memo}$ 中保存的结果。

注意:$\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\textit{dfs}(i)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$。本题由于 $\textit{points}_i > 0$,所以也可以把初始值设置为 $0$。

Python 用户可以无视上面这段,直接用 @cache 装饰器。

具体请看视频讲解 动态规划入门:从记忆化搜索到递推【基础算法精讲 17】,其中包含把记忆化搜索 1:1 翻译成递推的技巧。

class Solution:
    def mostPoints(self, questions: List[List[int]]) -> int:
        @cache  # 缓存装饰器,避免重复计算 dfs(一行代码实现记忆化)
        def dfs(i: int) -> int:
            if i >= len(questions):
                return 0
            return max(dfs(i + 1), dfs(i + questions[i][1] + 1) + questions[i][0])
        return dfs(0)
class Solution {
    public long mostPoints(int[][] questions) {
        long[] memo = new long[questions.length];
        return dfs(0, questions, memo);
    }

    private long dfs(int i, int[][] questions, long[] memo) {
        if (i >= memo.length) {
            return 0;
        }
        if (memo[i] > 0) { // 之前计算过
            return memo[i];
        }
        long notChoose = dfs(i + 1, questions, memo);
        long choose = dfs(i + questions[i][1] + 1, questions, memo) + questions[i][0];
        return memo[i] = Math.max(notChoose, choose); // 记忆化
    }
}
class Solution {
public:
    long long mostPoints(vector<vector<int>>& questions) {
        int n = questions.size();
        vector<long long> memo(n);
        auto dfs = [&](this auto&& dfs, int i) -> long long {
            if (i >= n) {
                return 0;
            }
            long long& res = memo[i]; // 注意这里是引用
            if (res) { // 之前计算过
                return res;
            }
            return res = max(dfs(i + 1), dfs(i + questions[i][1] + 1) + questions[i][0]);
        };
        return dfs(0);
    }
};
func mostPoints(questions [][]int) int64 {
    n := len(questions)
    memo := make([]int64, n)
    var dfs func(int) int64
    dfs = func(i int) int64 {
        if i >= n {
            return 0
        }
        p := &memo[i]
        if *p == 0 {
            q := questions[i]
            *p = max(dfs(i+1), dfs(i+q[1]+1)+int64(q[0]))
        }
        return *p
    }
    return dfs(0)
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{questions}$ 的长度。由于每个状态只会计算一次,动态规划的时间复杂度 $=$ 状态个数 $\times$ 单个状态的计算时间。本题状态个数等于 $\mathcal{O}(n)$,单个状态的计算时间为 $\mathcal{O}(1)$,所以总的时间复杂度为 $\mathcal{O}(n)$。
  • 空间复杂度:$\mathcal{O}(n)$。保存多少状态,就需要多少空间。

四、1:1 翻译成递推

我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。

具体来说,$f[i]$ 的定义和 $\textit{dfs}(i)$ 的定义是完全一样的,都表示剩余问题的下标为 $[i,n-1]$,计算从这些问题中可以获得的最大分数。

相应的递推式(状态转移方程)也和 $\textit{dfs}$ 一样:

$$
f[i] = \max(f[i+1], f[j] + \textit{points}_i)
$$

其中 $j = \min(i+\textit{brainpower}_i+1, n)$,这里把 $i>n$ 的状态都视作 $i=n$。

初始值:$f[n] = 0$,翻译自递归边界 $\textit{dfs}(i\ge n)=0$。

答案为 $f[0]$,翻译自递归入口 $\textit{dfs}(0)$。

答疑

:如何思考循环顺序?什么时候要正序枚举,什么时候要倒序枚举?

:这里有一个通用的做法:盯着状态转移方程,想一想,要计算 $f[i]$,必须先把 $f[i+1]$ 和 $f[i+\textit{brainpower}_i+1]$ 算出来,那么只有 $i$ 从大到小枚举才能做到。

class Solution:
    def mostPoints(self, questions: List[List[int]]) -> int:
        n = len(questions)
        f = [0] * (n + 1)
        for i in range(n - 1, -1, -1):
            j = min(i + questions[i][1] + 1, n)
            f[i] = max(f[i + 1], f[j] + questions[i][0])
        return f[0]
class Solution {
    public long mostPoints(int[][] questions) {
        int n = questions.length;
        long[] f = new long[n + 1];
        for (int i = n - 1; i >= 0; i--) {
            int j = Math.min(i + questions[i][1] + 1, n);
            f[i] = Math.max(f[i + 1], f[j] + questions[i][0]);
        }
        return f[0];
    }
}
class Solution {
public:
    long long mostPoints(vector<vector<int>>& questions) {
        int n = questions.size();
        vector<long long> f(n + 1);
        for (int i = n - 1; i >= 0; i--) {
            int j = min(i + questions[i][1] + 1, n);
            f[i] = max(f[i + 1], f[j] + questions[i][0]);
        }
        return f[0];
    }
};
func mostPoints(questions [][]int) int64 {
    n := len(questions)
    f := make([]int64, n+1)
    for i, q := range slices.Backward(questions) {
        j := min(i+q[1]+1, n)
        f[i] = max(f[i+1], f[j]+int64(q[0]))
    }
    return f[0]
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{questions}$ 的长度。
  • 空间复杂度:$\mathcal{O}(n)$。

五、另一种做法:从左往右递推

分析

从左往右递推的困难之处在于,并不好确定该从哪里转移过来:已知当前可以解决的问题是 $i$,那么上一个可以解决的问题是什么?

回顾记忆化搜索的过程:

  • 如果可以解决问题 $i$,但不去解决它,那么下一个可以解决的问题是 $i+1$,即 $i\to i+1$。
  • 如果可以解决问题 $i$,并且去解决它,那么下一个可以解决的问题是 $i+\textit{brainpower}_i+1$,即 $i\to i+\textit{brainpower}_i+1$。

换句话说,已知当前可以解决的问题是 $i$,那么下一个可以解决的问题是容易知道的,有两个。

但反过来,已知当前可以解决的问题是 $i$,那么上一个可以解决的问题有哪些?可能有很多,并不好处理(除非建图,预处理所有转移来源)。

思路

对于这种知道该去哪,但不好知道该从哪来的 DP,可以用刷表法:用当前状态,更新未来的(右边的)状态。

与之对比,上面的做法叫查表法:用之前的(右边的)状态,计算当前状态。

定义 $f[i]$ 表示在解决问题 $i$ 时,解决区间 $[0,i-1]$ 内的问题可以获得的最高分数。

对于问题 $i$:

  • 如果不解决(不选),那么问题 $i+1$ 是能解决的,用 $f[i]$ 更新 $f[i+1]$ 的最大值。
  • 如果解决(选),设 $j=\min(i+\textit{brainpower}_i+1,n)$,问题 $j$ 是能解决的,用 $f[i]+\textit{point}_i$ 更新 $f[j]$ 的最大值。

初始值 $f[0]=0$。区间 $[0,-1]$ 是空的,没有问题,得分为 $0$。

答案为 $f[n]$。(把 $n$ 当作一个虚拟的问题)

class Solution:
    def mostPoints(self, questions: List[List[int]]) -> int:
        n = len(questions)
        f = [0] * (n + 1)
        for i, (point, brainpower) in enumerate(questions):
            f[i + 1] = max(f[i + 1], f[i])
            j = min(i + brainpower + 1, n)
            f[j] = max(f[j], f[i] + point)
        return f[n]
class Solution {
    public long mostPoints(int[][] questions) {
        int n = questions.length;
        long[] f = new long[n + 1];
        for (int i = 0; i < n; i++) {
            f[i + 1] = Math.max(f[i + 1], f[i]);
            int[] q = questions[i];
            int j = Math.min(i + q[1] + 1, n);
            f[j] = Math.max(f[j], f[i] + q[0]);
        }
        return f[n];
    }
}
class Solution {
public:
    long long mostPoints(vector<vector<int>>& questions) {
        int n = questions.size();
        vector<long long> f(n + 1);
        for (int i = 0; i < n; i++) {
            f[i + 1] = max(f[i + 1], f[i]);
            auto& q = questions[i];
            int j = min(i + q[1] + 1, n);
            f[j] = max(f[j], f[i] + q[0]);
        }
        return f[n];
    }
};
func mostPoints(questions [][]int) int64 {
    n := len(questions)
    f := make([]int64, n+1)
    for i, q := range questions {
        f[i+1] = max(f[i+1], f[i])
        j := min(i+q[1]+1, n)
        f[j] = max(f[j], f[i]+int64(q[0]))
    }
    return f[n]
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{questions}$ 的长度。
  • 空间复杂度:$\mathcal{O}(n)$。

更多相似题目,见 动态规划题单 中的「§7.1 一维 DP」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 【本题相关】动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

[Python3/Java/C++/Go/TypeScript] 一题一解:计数(清晰题解)

作者 lcbin
2025年3月31日 06:51

方法一:计数

我们可以遍历字符串 $\textit{s}$,统计其中等于 $\textit{letter}$ 的字符的个数,然后根据公式 $\textit{count} \times 100 , / , \textit{len}(\textit{s})$ 计算百分比。

###python

class Solution:
    def percentageLetter(self, s: str, letter: str) -> int:
        return s.count(letter) * 100 // len(s)

###java

class Solution {
    public int percentageLetter(String s, char letter) {
        int cnt = 0;
        for (char c : s.toCharArray()) {
            if (c == letter) {
                ++cnt;
            }
        }
        return cnt * 100 / s.length();
    }
}

###cpp

class Solution {
public:
    int percentageLetter(string s, char letter) {
        return 100 * ranges::count(s, letter) / s.size();
    }
};

###go

func percentageLetter(s string, letter byte) int {
return strings.Count(s, string(letter)) * 100 / len(s)
}

###ts

function percentageLetter(s: string, letter: string): number {
    const count = s.split('').filter(c => c === letter).length;
    return Math.floor((100 * count) / s.length);
}

###rust

impl Solution {
    pub fn percentage_letter(s: String, letter: char) -> i32 {
        let count = s.chars().filter(|&c| c == letter).count();
        (100 * count as i32 / s.len() as i32) as i32
    }
}

时间复杂度 $O(n)$,其中 $n$ 为字符串 $\textit{s}$ 的长度。空间复杂度 $O(1)$。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

每日一题-字母在字符串中的百分比🟢

2025年3月31日 00:00

给你一个字符串 s 和一个字符 letter ,返回在 s 中等于 letter 字符所占的 百分比 ,向下取整到最接近的百分比。

 

示例 1:

输入:s = "foobar", letter = "o"
输出:33
解释:
等于字母 'o' 的字符在 s 中占到的百分比是 2 / 6 * 100% = 33% ,向下取整,所以返回 33 。

示例 2:

输入:s = "jjjj", letter = "k"
输出:0
解释:
等于字母 'k' 的字符在 s 中占到的百分比是 0% ,所以返回 0 。

 

提示:

  • 1 <= s.length <= 100
  • s 由小写英文字母组成
  • letter 是一个小写英文字母

库函数(Python/Java/C++/Go/JS/Rust)

作者 endlesscheng
2022年5月22日 12:12

统计 $s$ 中 $\textit{letter}$ 的个数,记作 $c$。

根据题意,答案为

$$
\left\lfloor\dfrac{100c}{n}\right\rfloor
$$

其中 $n$ 是 $s$ 的长度。

class Solution:
    def percentageLetter(self, s: str, letter: str) -> int:
        return s.count(letter) * 100 // len(s)
class Solution {
    public int percentageLetter(String s, char letter) {
        return (int) s.chars().filter(c -> c == letter).count() * 100 / s.length();
    }
}
class Solution {
public:
    int percentageLetter(string s, char letter) {
        return ranges::count(s, letter) * 100 / s.size();
    }
};
func percentageLetter(s string, letter byte) int {
    return strings.Count(s, string(letter)) * 100 / len(s)
}
var percentageLetter = function(s, letter) {
    return Math.floor((s.split(letter).length - 1) * 100 / s.length);
};
impl Solution {
    pub fn percentage_letter(s: String, letter: char) -> i32 {
        (s.bytes().filter(|&c| c == letter as u8).count() * 100 / s.len()) as _
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $s$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

❌
❌