53.最大子数组和

dp[i] 表示以 nums[i - 1] 结尾的子数组的最大和
dp[i + 1] = max(nums[i], dp[i] + nums[i])

class Solution {
    public int maxSubArray(int[] nums) {
        int pre = 0, res = nums[0];
        for (int x : nums){
            pre = Math.max(x, pre + x);
            // pre = pre < 0 ? x : pre + x;
            res = Math.max(res, pre);
        }
        return res;
    }
}

记忆化搜索

class Solution {    
    int INF = -0x3f3f3f3f;
    int[] nums;
    int[][] memo;
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        this.nums = nums;
        memo = new int[n][2];
        for (int[] a : memo) Arrays.fill(a, INF);
        // 选与不选 1/0
        return Math.max(dfs(n - 1, 0), dfs(n - 1, 1));
    }
    
    int dfs(int i, int j) {
        if (i < 0) return INF;
        if (memo[i][j] != INF) return memo[i][j];
        int res = INF;
        // 选 i
        if (j == 1) res = Math.max(nums[i], dfs(i - 1, 1) + nums[i]);
        else res = Math.max(dfs(i - 1, 1), dfs(i - 1, 0));
        return memo[i][j] = res;
    }
}

918.环形子数组的最大和

premax/premin:前 i 个元素的子数组的和的最大/小值
maxsum/minsum:数组中子数组和的最大/小值

无环,即为 53. 最大子数组和
有环,分首尾两段,要使两端之和最大,必须让中间的子数组和最小,即最大子数组和为:sum(nums) - minsum,问题转换为求最小子数组和。

class Solution {
    public int maxSubarraySumCircular(int[] nums) {
        int n = nums.length, sum = 0, maxsum = nums[0], minsum = nums[0], premax = 0, premin = 0;        
        for (int x : nums){
            premax = Math.max(x, premax + x);
            premin = Math.min(x, premin + x);
            maxsum = Math.max(maxsum, premax);
            minsum = Math.min(minsum, premin);
            sum += x;
        }
        return Math.max(maxsum, sum != minsum ? sum - minsum : maxsum);
        // [-3,-2,-3] 最小和 = 和 的情况
    }
}

前缀和、单调队列,滑动窗口。

class Solution {
    public int maxSubarraySumCircular(int[] nums) {
        int n = nums.length;
        int[] acc = new int[2 * n];
        acc[0] = nums[0];
        int res = nums[0];
        Deque<Integer> q = new ArrayDeque<>();
        q.add(0);
        for (int i = 1; i < 2*n; i++){
            acc[i] = acc[i - 1] + nums[i % n];
            if (i - q.peek() > n) q.poll();
            res = Math.max(res, acc[i] - acc[q.peek()]); // 栈顶尽可能小
            while (!q.isEmpty() && acc[i] <= acc[q.peekLast()]) q.pollLast();
            q.add(i);
        }
        return res;
    }
}

2606. 找到最大开销的子字符串

class Solution {
    public int maximumCostSubstring(String s, String chars, int[] vals) {
        var v = new int[26];
        for (int i = 0; i < 26; ++i)
            v[i] = i + 1;
        for (int i = 0; i < vals.length; ++i)
            v[chars.charAt(i) - 'a'] = vals[i];
        // 最大子段和(允许子数组为空)
        int ans = 0, f = 0;
        for (char c : s.toCharArray()) {
            f = Math.max(f, 0) + v[c - 'a'];
            ans = Math.max(ans, f);
        }
        return ans;
    }
}

2321. 拼接数组的最大分数

class Solution {
    public int maximumsSplicedArray(int[] nums1, int[] nums2) {
        return Math.max(solve(nums1, nums2), solve(nums2, nums1));
    }

    int solve(int[] nums1, int[] nums2) {
        int sum = 0, maxSum = 0;
        for (int i = 0, s = 0; i < nums1.length; ++i) {
            sum += nums1[i];
            s = Math.max(s + nums2[i] - nums1[i], 0);
            maxSum = Math.max(maxSum, s);
        }
        return sum + maxSum;
    }
}

1186. 删除一次得到子数组最大和

class Solution {
    int[] arr;
    int[][] memo;
    int INF = -0x3f3f3f3f;
    public int maximumSum(int[] arr) {
        this.arr = arr;
        int n = arr.length, res = INF;
        memo = new int[n][2];
        for (int[] a : memo) Arrays.fill(a, INF);
        for (int i = 0; i < n; i++) 
            res = Math.max(res, Math.max(dfs(i, 0), dfs(i, 1)));
        return res;
    }
    int dfs(int i, int j) {
        if (i < 0) return INF;
        if (memo[i][j] != INF) return memo[i][j];
        if (j == 0) return memo[i][j] = Math.max(dfs(i - 1, 0), 0) + arr[i];
        return memo[i][j] = Math.max(dfs(i - 1, 1) + arr[i], dfs(i - 1, 0));
    } 
}
class Solution {
    public int maximumSum(int[] arr) {
        int ans = Integer.MIN_VALUE, n = arr.length;
        var f = new int[n + 1][2];
        Arrays.fill(f[0], Integer.MIN_VALUE / 2); // 除 2 防止负数相加溢出
        for (int i = 0; i < n; i++) {
            f[i + 1][0] = Math.max(f[i][0], 0) + arr[i];
            f[i + 1][1] = Math.max(f[i][1] + arr[i], f[i][0]);
            ans = Math.max(ans, Math.max(f[i + 1][0], f[i + 1][1]));
        }
        return ans;
    }
}

363. 矩形区域不超过 K 的最大数值和