珠宝最大财富

194 阅读6分钟

给我一些代码提示

好的,让我们来逐步分析这个问题,并给出一些代码提示。

问题理解

你需要帮助小青在 m 轮中从 stones 数组的两端选择珠宝,使得每轮选择的珠宝价值乘以对应的符文 c[i] 后的总和最大。

数据结构选择

我们可以使用动态规划来解决这个问题。定义一个二维数组 dp,其中 dp[i][j] 表示在第 i 轮选择后,从 stones 数组的前 j 个珠宝中可以获得的最大财富。

算法步骤

  1. 初始化:创建一个 dp 数组,大小为 m+1 x n+1,并初始化为 0
  2. 状态转移
    • 对于每一轮 i,从 stones 数组的两端选择珠宝。
    • 更新 dp[i][j] 为从 stones 数组的前 j 个珠宝中选择的最大值。
  3. 结果:最终结果为 dp[m][n],即在 m 轮后从 stones 数组的所有珠宝中选择的最大财富。

代码提示

以下是代码框架,其中关键步骤用注释标出:

public class Main {
    public static int solution(int[] stones, int[] c) {
        int n = stones.length;
        int m = c.length;
        
        // 创建 dp 数组
        int[][] dp = new int[m + 1][n + 1];
        
        // 初始化 dp 数组
        for (int i = 0; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                dp[i][j] = 0;
            }
        }
        
        // 状态转移
        for (int i = 1; i <= m; i++) {
            for (int j = i; j <= n; j++) {
                // 选择从左边取珠宝
                int left = /* 计算从左边取珠宝的值 */;
                // 选择从右边取珠宝
                int right = /* 计算从右边取珠宝的值 */;
                
                // 更新 dp[i][j]
                dp[i][j] = Math.max(left, right);
            }
        }
        
        // 返回最终结果
        return dp[m][n];
    }

    public static void main(String[] args) {
        System.out.println(solution(new int[]{-3, 4, 5}, new int[]{2, -1, 3}) == 25);
        System.out.println(solution(new int[]{6, -7, 1}, new int[]{4, -3}) == 45);
        System.out.println(solution(new int[]{3, 5, -2, 9}, new int[]{1, 3, -5}) == 40);
    }
}

关键步骤

  1. 计算从左边取珠宝的值left = dp[i-1][j-1] + stones[j-1] * c[i-1]
  2. 计算从右边取珠宝的值right = dp[i-1][j-1] + stones[n-j] * c[i-1]

你可以根据这些提示继续完善代码。如果有任何问题或需要进一步的帮助,请告诉我!

有没有效率更高的方法

当然,我们可以考虑使用更高效的算法来解决这个问题。动态规划虽然是一个很好的方法,但在某些情况下,我们可以通过优化状态转移和减少不必要的计算来提高效率。

优化思路

  1. 减少状态数量:我们可以只使用一维数组来存储状态,而不是二维数组。这样可以减少空间复杂度。
  2. 贪心策略:在每一轮中,我们可以直接选择当前能带来最大收益的珠宝,而不是遍历所有可能的选择。

优化算法步骤

  1. 初始化:创建一个一维数组 dp,大小为 n,并初始化为 0
  2. 状态转移
    • 对于每一轮 i,计算从左边和右边取珠宝的收益。
    • 更新 dp 数组,使其始终保持当前轮的最大收益。
  3. 结果:最终结果为 dp 数组中的最大值。

代码提示

以下是优化后的代码框架,其中关键步骤用注释标出:

public class Main {
    public static int solution(int[] stones, int[] c) {
        int n = stones.length;
        int m = c.length;
        
        // 创建 dp 数组
        int[] dp = new int[n];
        
        // 初始化 dp 数组
        for (int i = 0; i < n; i++) {
            dp[i] = 0;
        }
        
        // 状态转移
        for (int i = 0; i < m; i++) {
            int[] newDp = new int[n];
            for (int j = 0; j < n; j++) {
                // 选择从左边取珠宝
                int left = /* 计算从左边取珠宝的值 */;
                // 选择从右边取珠宝
                int right = /* 计算从右边取珠宝的值 */;
                
                // 更新 newDp[j]
                newDp[j] = Math.max(left, right);
            }
            dp = newDp;
        }
        
        // 返回最终结果
        int maxWealth = 0;
        for (int i = 0; i < n; i++) {
            maxWealth = Math.max(maxWealth, dp[i]);
        }
        return maxWealth;
    }

    public static void main(String[] args) {
        System.out.println(solution(new int[]{-3, 4, 5}, new int[]{2, -1, 3}) == 25);
        System.out.println(solution(new int[]{6, -7, 1}, new int[]{4, -3}) == 45);
        System.out.println(solution(new int[]{3, 5, -2, 9}, new int[]{1, 3, -5}) == 40);
    }
}

关键步骤

  1. 计算从左边取珠宝的值left = (j > 0 ? dp[j-1] : 0) + stones[j] * c[i]
  2. 计算从右边取珠宝的值right = (j < n-1 ? dp[j+1] : 0) + stones[n-1-j] * c[i]

优化点

  1. 减少空间复杂度:使用一维数组 dp 来存储状态,而不是二维数组。
  2. 减少时间复杂度:在每一轮中,直接选择当前能带来最大收益的珠宝,而不是遍历所有可能的选择。

你可以根据这些提示继续完善代码。如果有任何问题或需要进一步的帮助,请告诉我!

还有没有更高效的实现

当然,我们可以进一步优化算法,使用贪心策略和优先队列(堆)来提高效率。这种方法可以在每一轮中快速找到当前能带来最大收益的珠宝。

优化思路

  1. 使用优先队列:我们可以使用两个优先队列(最大堆)来分别存储从左边和右边取珠宝的收益。
  2. 贪心策略:在每一轮中,从两个优先队列中选择当前能带来最大收益的珠宝。

优化算法步骤

  1. 初始化:创建两个优先队列,分别存储从左边和右边取珠宝的收益。
  2. 状态转移
    • 对于每一轮 i,从两个优先队列中选择当前能带来最大收益的珠宝。
    • 更新总财富。
  3. 结果:最终结果为总财富。

代码提示

以下是优化后的代码框架,其中关键步骤用注释标出:

import java.util.PriorityQueue;

public class Main {
    public static int solution(int[] stones, int[] c) {
        int n = stones.length;
        int m = c.length;
        
        // 创建两个优先队列
        PriorityQueue<Integer> leftHeap = new PriorityQueue<>((a, b) -> b - a);
        PriorityQueue<Integer> rightHeap = new PriorityQueue<>((a, b) -> b - a);
        
        // 初始化优先队列
        for (int i = 0; i < n; i++) {
            leftHeap.offer(stones[i] * c[0]);
            rightHeap.offer(stones[n - 1 - i] * c[0]);
        }
        
        // 总财富
        int totalWealth = 0;
        
        // 状态转移
        for (int i = 0; i < m; i++) {
            // 从左边取珠宝
            int leftMax = leftHeap.peek();
            // 从右边取珠宝
            int rightMax = rightHeap.peek();
            
            // 选择当前能带来最大收益的珠宝
            if (leftMax >= rightMax) {
                totalWealth += leftHeap.poll();
                if (i < m - 1) {
                    leftHeap.offer(stones[leftHeap.size()] * c[i + 1]);
                }
            } else {
                totalWealth += rightHeap.poll();
                if (i < m - 1) {
                    rightHeap.offer(stones[n - 1 - rightHeap.size()] * c[i + 1]);
                }
            }
        }
        
        // 返回最终结果
        return totalWealth;
    }

    public static void main(String[] args) {
        System.out.println(solution(new int[]{-3, 4, 5}, new int[]{2, -1, 3}) == 25);
        System.out.println(solution(new int[]{6, -7, 1}, new int[]{4, -3}) == 45);
        System.out.println(solution(new int[]{3, 5, -2, 9}, new int[]{1, 3, -5}) == 40);
    }
}

关键步骤

  1. 初始化优先队列:将所有珠宝的初始收益(乘以第一个符文)放入两个优先队列中。
  2. 状态转移:在每一轮中,从两个优先队列中选择当前能带来最大收益的珠宝,并更新总财富。
  3. 更新优先队列:在每一轮中,从被选择的优先队列中移除最大值,并添加下一个珠宝的收益(乘以下一个符文)。

优化点

  1. 减少时间复杂度:使用优先队列可以在 O(log n) 时间内找到最大值,从而减少每一轮的时间复杂度。
  2. 减少空间复杂度:使用两个优先队列而不是二维数组,减少了空间复杂度。

你可以根据这些提示继续完善代码。如果有任何问题或需要进一步的帮助,请告诉我!