《小F的超市购物策略》和《环形数组最大子数组和的问题》| 豆包MarsCode AI刷题

193 阅读5分钟

今天我们将在豆包MarsCode AI刷题平台上,完成《小F的超市购物策略》与《环形数组最大子数组和的问题》这两个算法问题,通过这些练习提升用户解决此类问题的能力

《小F的超市购物策略》题面如下:

image.png

问题理解

题目要求在有限的预算 x 内,通过购买商品并利用超市的半价活动,最大化喜爱度总和。每个商品的价格是偶数,购买某件商品时,可以选择以原价购买,或者以半价购买下一件商品。

数据结构选择

使用了三维动态规划数组 f,其中:

  • f[i][j][k] 表示在前 i 个商品中,状态为 j 时,花费 k 元所能获得的最大喜爱度。

  • i 表示当前商品的索引。

  • j 表示当前的状态:

    • 0:当前商品以半价购买。
    • 1:当前商品以原价购买。
    • 2:当前商品不购买。
  • k 表示当前花费的金额。

算法步骤

  1. 初始化

    • 对于第一个商品,如果其价格 a[0] 小于等于预算 x,则可以以原价购买,状态为 1,花费为 a[0],喜爱度为 b[0]
  2. 状态转移

    • 对于每个商品 i 和每个可能的花费 j

      • 状态 0:如果前一个商品以原价购买(状态 1),并且当前商品可以以半价购买(即 j - a[i]/2 >= 0),则更新当前状态为 0
      • 状态 1:如果当前商品可以以原价购买(即 j - a[i] >= 0),则从前一个商品的任意状态(0, 1, 2)转移过来,更新当前状态为 1
      • 状态 2:不购买当前商品,直接从前一个商品的任意状态(0, 1, 2)转移过来。
  3. 结果计算

    • 遍历所有商品和所有可能的花费,取最大喜爱度作为结果。
#include <bits/stdc++.h>

using namespace std;

int solution(int n, int x, std::vector<int> a, std::vector<int> b) {
    int ret = 0;
    vector<vector<vector<int>>> f(n, vector<vector<int>>(3, vector<int>(x + 1, 0)));
    
    // 初始化第一个商品
    if (a[0] <= x) {
        f[0][1][a[0]] = b[0];
        ret = b[0];
    }
    
    // 状态转移
    for (int i = 1; i < n; i++) {
        for (int j = 0; j <= x; j++) {
            // 状态 0:当前商品以半价购买
            if (j - a[i] / 2 >= 0 && f[i - 1][1][j - a[i] / 2] > 0) {
                f[i][0][j] = f[i - 1][1][j - a[i] / 2] + b[i];
            }
            
            // 状态 1:当前商品以原价购买
            if (j - a[i] >= 0) {
                f[i][1][j] = max({f[i - 1][0][j - a[i]] + b[i], 
                                  f[i - 1][1][j - a[i]] + b[i], f[i - 1][2][j - a[i]] + b[i]});
            }
            
            // 状态 2:当前商品不购买
            f[i][2][j] = max({f[i - 1][2][j], f[i - 1][0][j], f[i - 1][1][j]});
            
            // 更新结果
            ret = max({ret, f[i][0][j], f[i][1][j], f[i][2][j]});
        }
    }
   
    return ret;
}

int main() {
    // 测试样例
    std::cout << (solution(4, 7, {2, 2, 6, 2}, {3, 4, 5, 1}) == 12) << std::endl;
    std::cout << (solution(3, 10, {4, 4, 4}, {2, 3, 5}) == 10) << std::endl;
    std::cout << (solution(5, 8, {2, 4, 4, 6, 2}, {1, 2, 3, 4, 5}) == 10) << std::endl;
    std::cout << (solution(13, 7, {28, 12, 10, 16, 12, 34, 32, 8, 12, 30, 12, 28, 22}, {26, 30, 12, 32, 14, 22, 20, 18, 14, 8, 6, 6, 2}) == 0) << std::endl;
    std::cout << (solution(17, 14, {32, 18, 32, 12, 10, 2, 12, 28, 22, 2, 18, 22, 16, 4, 4, 32, 2}, {16, 32, 4, 6, 24, 22, 32, 28, 32, 32, 30, 2, 34, 24, 16, 32, 6}) == 110) << std::endl;
}

Tip: 目前的实现可以继续使用滚动数组优化,感兴趣的朋友可以自己试一试

《环形数组最大子数组和的问题》题面如下:

image.png

题目理解

题目要求在一个环形数组中找到非空子数组的最大可能和。环形数组的特点是数组的末端和开头相连,这意味着子数组可以跨越数组的末端连接到开头。

解题思路

为了解决这个问题,我们可以将问题分解为两个部分:

  1. 普通子数组的最大和:即在非环形数组中找到的最大子数组和。
  2. 环形子数组的最大和:即跨越数组末端和开头的最大子数组和。

普通子数组的最大和

在遍历数组时维护两个变量:

  • preSum:开头位置到当前位置子数组的和。
  • preMinSum:前缀子数组的最小和。

以当前元素为终点的普通子数组的最大和等于preSum - preMinSum

环形子数组的最大和

环形子数组的最大和可以通过以下方式计算:

  • 计算整个数组的和allSum
  • preSum:开头位置到当前位置子数组的和。
  • preMaxSum:后缀子数组的最大和。

以当前元素为起点的环形子数组的最大和为allSum - preSum + preMaxSum

具体实现

int solution(std::vector<int>& nums) {
    // write code here
    int n = nums.size();
    int ret = INT_MIN;
    int allSum=0;
    for(int i : nums){
        allSum += i;
    }
    int preMinSum=0, preMaxSum=0, preSum=0;
    for(int i=0;i<n;i++){
        // 计算环形子数组的最大和
        ret = max(ret, allSum - preSum + preMaxSum);

        preSum += nums[i];
        // 计算普通子数组的最大和
        ret = max(ret, preSum - preMinSum);
        
        preMinSum = min(preMinSum, preSum);
        preMaxSum = max(preMaxSum, preSum);
        
    }

    return ret; // placeholder return
}

总体复杂度

  • 时间复杂度O(n),其中 n 是数组的长度。

  • 空间复杂度O(1)

借助豆包MarsCode AI刷题平台,我们不仅高效地解决了《小F的超市购物策略》和《环形数组最大子数组和的问题》,还加深了对相关算法和数据结构的理解,后续会借助豆包MarsCode AI给大家展示更多题目的解法。