Medium

题目描述

给你一个长度为 n 的循环数组 balance,其中 balance[i] 是第 i 个人的净余额。

在一次移动中,一个人可以向其左邻居或右邻居转移恰好 1 个单位的余额。

返回使每个人都有非负余额所需的最少移动次数。如果不可能,返回 -1。

注意:保证最初最多只有 1 个索引的余额为负数。

示例 1:

输入:balance = [5,1,-4]
输出:4
解释:
一个最优的移动序列是:
- 从 i = 1 向 i = 2 转移 1 个单位,得到 balance = [5, 0, -3]
- 从 i = 0 向 i = 2 转移 1 个单位,得到 balance = [4, 0, -2]
- 从 i = 0 向 i = 2 转移 1 个单位,得到 balance = [3, 0, -1]
- 从 i = 0 向 i = 2 转移 1 个单位,得到 balance = [2, 0, 0]
因此,所需的最少移动次数是 4。

示例 2:

输入:balance = [1,2,-5,2]
输出:6

示例 3:

输入:balance = [-3,2]
输出:-1
解释:
对于 balance = [-3, 2],不可能使所有余额都非负,所以答案是 -1。

约束:

  • 1 <= n == balance.length <= 10^5
  • -10^9 <= balance[i] <= 10^9
  • 最初 balance 中最多有一个负值

解题思路

这道题的关键是理解循环数组中的余额转移过程。

首先需要考虑特殊情况:

  1. 如果没有负数,直接返回 0
  2. 如果总和小于 0,说明正数余额不足以补充负数,返回 -1

对于一般情况,由于最多只有一个负数位置,我们需要将其他位置的正余额转移过来。在循环数组中,从位置 i 到位置 j 的最短距离有两种路径:顺时针和逆时针。

核心思路是贪心算法:

  1. 找到负数位置
  2. 按距离负数位置的远近对正数进行排序
  3. 贪心地选择距离最近的正数来补充负数

距离的计算需要考虑循环特性,从位置 i 到位置 j 的最短距离为: min((j - i + n) % n, (i - j + n) % n)

排序后,依次使用正数余额来补充负数,每次转移的代价就是距离乘以转移的单位数。需要注意的是,如果某个正数余额过大,可能只需要部分转移。

代码实现

class Solution {
public:
    long long minMoves(vector<int>& balance) {
        int n = balance.size();
        long long total = 0;
        int negIdx = -1;
        
        for (int i = 0; i < n; i++) {
            total += balance[i];
            if (balance[i] < 0) {
                negIdx = i;
            }
        }
        
        if (negIdx == -1) return 0;
        if (total < 0) return -1;
        
        vector<pair<int, int>> positive;
        for (int i = 0; i < n; i++) {
            if (balance[i] > 0) {
                int dist = min((i - negIdx + n) % n, (negIdx - i + n) % n);
                positive.push_back({dist, balance[i]});
            }
        }
        
        sort(positive.begin(), positive.end());
        
        long long moves = 0;
        long long need = -balance[negIdx];
        
        for (auto& p : positive) {
            int dist = p.first;
            int amount = p.second;
            
            long long use = min((long long)amount, need);
            moves += use * dist;
            need -= use;
            
            if (need == 0) break;
        }
        
        return moves;
    }
};
class Solution:
    def minMoves(self, balance: List[int]) -> int:
        n = len(balance)
        total = sum(balance)
        neg_idx = -1
        
        for i in range(n):
            if balance[i] < 0:
                neg_idx = i
                break
        
        if neg_idx == -1:
            return 0
        if total < 0:
            return -1
        
        positive = []
        for i in range(n):
            if balance[i] > 0:
                dist = min((i - neg_idx + n) % n, (neg_idx - i + n) % n)
                positive.append((dist, balance[i]))
        
        positive.sort()
        
        moves = 0
        need = -balance[neg_idx]
        
        for dist, amount in positive:
            use = min(amount, need)
            moves += use * dist
            need -= use
            
            if need == 0:
                break
        
        return moves
public class Solution {
    public long MinMoves(int[] balance) {
        int n = balance.Length;
        long total = 0;
        int negIdx = -1;
        
        for (int i = 0; i < n; i++) {
            total += balance[i];
            if (balance[i] < 0) {
                negIdx = i;
            }
        }
        
        if (negIdx == -1) return 0;
        if (total < 0) return -1;
        
        var positive = new List<(int dist, int amount)>();
        for (int i = 0; i < n; i++) {
            if (balance[i] > 0) {
                int dist = Math.Min((i - negIdx + n) % n, (negIdx - i + n) % n);
                positive.Add((dist, balance[i]));
            }
        }
        
        positive.Sort();
        
        long moves = 0;
        long need = -balance[negIdx];
        
        foreach (var (dist, amount) in positive) {
            long use = Math.Min(amount, need);
            moves += use * dist;
            need -= use;
            
            if (need == 0) break;
        }
        
        return moves;
    }
}
var minMoves = function(balance) {
    const n = balance.length;
    const total = balance.reduce((sum, val) => sum + val, 0);
    
    if (total < 0) return -1;
    
    let negIndex = -1;
    for (let i = 0; i < n; i++) {
        if (balance[i] < 0) {
            negIndex = i;
            break;
        }
    }
    
    if (negIndex === -1) return 0;
    
    const deficit = -balance[negIndex];
    let minMoves = Infinity;
    
    // Try going left
    let moves = 0;
    let collected = 0;
    for (let i = 1; i <= n - 1; i++) {
        const leftIdx = (negIndex - i + n) % n;
        const available = balance[leftIdx];
        const take = Math.min(available, deficit - collected);
        moves += take * i;
        collected += take;
        if (collected >= deficit) {
            minMoves = Math.min(minMoves, moves);
            break;
        }
    }
    
    // Try going right
    moves = 0;
    collected = 0;
    for (let i = 1; i <= n - 1; i++) {
        const rightIdx = (negIndex + i) % n;
        const available = balance[rightIdx];
        const take = Math.min(available, deficit - collected);
        moves += take * i;
        collected += take;
        if (collected >= deficit) {
            minMoves = Math.min(minMoves, moves);
            break;
        }
    }
    
    // Try both directions
    for (let leftDist = 1; leftDist < n; leftDist++) {
        let tempMoves = 0;
        let tempCollected = 0;
        
        // Collect from left
        for (let i = 1; i <= leftDist; i++) {
            const leftIdx = (negIndex - i + n) % n;
            const available = balance[leftIdx];
            const take = Math.min(available, deficit - tempCollected);
            tempMoves += take * i;
            tempCollected += take;
            if (tempCollected >= deficit) break;
        }
        
        if (tempCollected >= deficit) {
            minMoves = Math.min(minMoves, tempMoves);
            continue;
        }
        
        // Collect from right
        const remaining = deficit - tempCollected;
        for (let i = 1; i < n - leftDist; i++) {
            const rightIdx = (negIndex + i) % n;
            const available = balance[rightIdx];
            const take = Math.min(available, remaining - (tempCollected - (deficit - remaining)));
            if (take > 0) {
                tempMoves += take * i;
                tempCollected += take;
                if (tempCollected >= deficit) {
                    minMoves = Math.min(minMoves, tempMoves);
                    break;
                }
            }
        }
    }
    
    return minMoves === Infinity ? -1 : minMoves;
};

复杂度分析

复杂度时间复杂度空间复杂度
时间O(n log n)O(n)
空间O(n)O(n)

时间复杂度主要来自对正数位置按距离排序的过程,空间复杂度来自存储正数位置和距离的数组。