收集巧克力(迭代,差分)

2023-12-30 08:08:05 浏览数 (2)

1.题目:收集巧克力问题

给你一个长度为 n 、下标从 0 开始的整数数组 nums ,表示收集不同巧克力的成本。每个巧克力都对应一个不同的类型,最初,位于下标 i 的巧克力就对应第 i 个类型。

在一步操作中,你可以用成本 x 执行下述行为:

  • 同时修改所有巧克力的类型,将巧克力的类型 i 修改为类型 ((i 1) mod n)

假设你可以执行任意次操作,请返回收集所有类型巧克力所需的最小成本。

示例 1:

代码语言:javascript复制
输入:nums = [20,1,15], x = 5
输出:13
解释:最开始,巧克力的类型分别是 [0,1,2] 。我们可以用成本 1 购买第 1 个类型的巧克力。
接着,我们用成本 5 执行一次操作,巧克力的类型变更为 [1,2,0] 。我们可以用成本 1 购买第 2 个类型的巧克力。
然后,我们用成本 5 执行一次操作,巧克力的类型变更为 [2,0,1] 。我们可以用成本 1 购买第 0 个类型的巧克力。
因此,收集所有类型的巧克力需要的总成本是 (1   5   1   5   1) = 13 。可以证明这是一种最优方案。

示例 2:

代码语言:javascript复制
输入:nums = [1,2,3], x = 4
输出:6
解释:我们将会按最初的成本收集全部三个类型的巧克力,而不需执行任何操作。因此,收集所有类型的巧克力需要的总成本是 1   2   3 = 6 。

提示:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 109
  • 1 <= x <= 109

2.题解

递推公式
代码语言:javascript复制
对于初始类型为i的巧克力来说,假设一共进行了k次操作
1.f(i,0)=nums[i]
2.f(i,k)=min{f(i,k-1),nums[(i k) mod n]}
则最终结果为

k · turnCost f(0,k) f(1,k) f(2,k) … f(n-1,k);

这是我写的代码O(n^2)
代码语言:javascript复制
// 假设做了k此操作,对于第i个类型的巧克力,它的最低购买成本有如下迭代公式
// f(i,0)=nums[i]   f(i,k)=min{f(i,k-1),kx nums[(i k)mod n]}
// result = f(0,k) f(1,k) ... f(n,k)
public static int minCost(int[] nums, int x) {
	int result = 0;
	int min=0;
	int loc = 0;
	for(int i = 0;i<nums.length;i  ) {
		min = nums[i];
    	for(int k = 0;k <nums.length;k  ) {
    		 loc = func(i,k,nums,x);
    		 if(loc < min) {
    			 min = loc;
    		 }
    	}
    	result  = min;
	}

	return result ;
}

public static int func(int i,int k,int[] nums,int x) {
	if(k==0) {
		return nums[i];
	}
	else {
		return Math.min(func(i,k-1,nums,x), k*x nums[(i k) % nums.length]);
	}
	
}
这是平台的示例答案 O(n^2)
代码语言:javascript复制
class Solution{
    public long minCost(int[] nums, int x){
        int n = nums.length;
        int[] f = new int[n];
        System.arraycopy(nums,0,f,0,n);
        long ans = getSum(f);
        for(int k=1;k<n;  k){
            for(int i=0;i<n;  i){
                f[i] = Math.min(f[i],nums[(i k)%n]);
            }
            ans = Math.min(ans,(long) k * x  getSum(f));
        }
        return ans;
    }
    
    public long getSum(int[] f){
        long sum = 0;
        for(int num : f){
            sum  = num;
        }
        return sum;
    }
}
还有一种二次差分的解法,时间复杂度为O(n)
代码语言:javascript复制
class Solution {
    public long minCost(int[] nums, int x) {
        int n = nums.length;
        // 找出 nums 中最小的元素,并用其为首元素构造一个新的数组
        int minIdx = 0;
        for (int i = 1; i < n;   i) {
            if (nums[i] < nums[minIdx]) {
                minIdx = i;
            }
        }
        int[] tmp = new int[n];
        for (int i = 0; i < n;   i) {
            tmp[i] = nums[(minIdx   i) % n];
        }
        System.arraycopy(tmp, 0, nums, 0, n);

        int[] L = new int[n];
        int[] R = new int[n];
        L[0] = n - 1;
        // 循环来看,右侧 nums[0] 是更小的元素,但不一定是第一个更小的元素,需要用单调栈计算得到
        for (int i = 0; i < n;   i) {
            R[i] = n - i - 1;
        }
        Deque<Integer> stack = new ArrayDeque<Integer>();
        stack.push(0);
        for (int i = 1; i < n;   i) {
            while (!stack.isEmpty() && nums[i] < nums[stack.peek()]) {
                R[stack.peek()] = i - stack.peek() - 1;
                stack.pop();
            }
            L[i] = i - stack.peek() - 1;
            stack.push(i);
        }

        long[] F = new long[n];

        // 进行操作需要的成本
        diffTwice(F, 0, n - 1, x, 0);

        for (int i = 0; i < n;   i) {
            int minv = Math.min(L[i], R[i]);
            int maxv = Math.max(L[i], R[i]);
            // 第一种情况,窗口数量 k 1,总和 nums[i] * k   nums[i]
            diffTwice(F, 0, minv, nums[i], nums[i]);
            // 第二种情况,窗口数量 minv 1,总和 0 * k   nums[i] * (minv   1)
            diffTwice(F, minv   1, maxv, 0, (long) nums[i] * (minv   1));
            // 第三种情况,窗口数量 L[i] R[i]-k 1,总和 -nums[i] * k   nums[i] * (L[i]   R[i]   1)
            diffTwice(F, maxv   1, L[i]   R[i], -nums[i], (long) nums[i] * (L[i]   R[i]   1));
        }

        // 计算两次前缀和
        for (int i = 0; i < 2;   i) {
            long[] G = new long[n];
            G[0] = F[0];
            for (int j = 1; j < n;   j) {
                G[j] = G[j - 1]   F[j];
            }
            System.arraycopy(G, 0, F, 0, n);
        }

        long minimum = Long.MAX_VALUE;
        for (long num : F) {
            minimum = Math.min(minimum, num);
        }
        return minimum;
    }

    // 辅助函数,一次差分,将 F[l..r] 都增加 d
    public void diffOnce(long[] F, int l, int r, long d) {
        if (l > r) {
            return;
        }
        int n = F.length;
        if (l < n) {
            F[l]  = d;
        }
        if (r   1 < n) {
            F[r   1] -= d;
        }
    }

    
    // 辅助函数,二次差分,将 F[l..r] 增加 ki   b,i 是下标
    public void diffTwice(long[] F, int l, int r, long k, long b) {
        if (l > r) {
            return;
        }
        diffOnce(F, l, l, k * l   b);
        diffOnce(F, l   1, r, k);
        diffOnce(F, r   1, r   1, -(k * r   b));
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/collecting-chocolates/solutions/2580148/shou-ji-qiao-ke-li-by-leetcode-solution-bjyp/
来源:力扣(LeetCode)

0 人点赞