class071 子数组最大累加和问题与扩展-下【算法】

简介: class071 子数组最大累加和问题与扩展-下【算法】

class071 子数组最大累加和问题与扩展-下【算法】

code1 152. 乘积最大子数组

// 乘积最大子数组

// 给你一个整数数组 nums

// 请你找出数组中乘积最大的非空连续子数组

// 并返回该子数组所对应的乘积

// 测试链接 : https://leetcode.cn/problems/maximum-product-subarray/

因为有负数,并且有负负得正

dp[i]:[0…i]子数组最大成绩

  1. nums[i]
  2. nums[i]x最大[i-1]
  3. nums[i]x最小[i-1]
package class071;
// 乘积最大子数组
// 给你一个整数数组 nums
// 请你找出数组中乘积最大的非空连续子数组
// 并返回该子数组所对应的乘积
// 测试链接 : https://leetcode.cn/problems/maximum-product-subarray/
public class Code01_MaximumProductSubarray {
  // 本方法对于double类型的数组求最大累乘积也同样适用
  public static int maxProduct(int[] nums) {
    int ans = nums[0];
    for (int i = 1, min = nums[0], max = nums[0], curmin, curmax; i < nums.length; i++) {
      curmin = Math.min(nums[i], Math.min(min * nums[i], max * nums[i]));
      curmax = Math.max(nums[i], Math.max(min * nums[i], max * nums[i]));
      min = curmin;
      max = curmax;
      ans = Math.max(ans, max);
    }
    return ans;
  }
}

code2 子序列累加和必须被7整除的最大累加和

// 子序列累加和必须被7整除的最大累加和

// 给定一个非负数组nums,

// 可以任意选择数字组成子序列,但是子序列的累加和必须被7整除

// 返回最大累加和

// 对数器验证

dp[i][j]:前i个数,模7余j

不选当前数:dp[i-1][j]

选当前数:dp[i-1][need]+nums[i],need=(j+7-cur)%7

package class071;
// 子序列累加和必须被7整除的最大累加和
// 给定一个非负数组nums,
// 可以任意选择数字组成子序列,但是子序列的累加和必须被7整除
// 返回最大累加和
// 对数器验证
public class Code02_MaxSumDividedBy7 {
  // 暴力方法
  // 为了验证
  public static int maxSum1(int[] nums) {
    // nums形成的所有子序列的累加和都求出来
    // 其中%7==0的那些累加和中,返回最大的
    // 就是如下f函数的功能
    return f(nums, 0, 0);
  }
  public static int f(int[] nums, int i, int s) {
    if (i == nums.length) {
      return s % 7 == 0 ? s : 0;
    }
    return Math.max(f(nums, i + 1, s), f(nums, i + 1, s + nums[i]));
  }
  // 正式方法
  // 时间复杂度O(n)
  public static int maxSum2(int[] nums) {
    int n = nums.length;
    // dp[i][j] : nums[0...i-1]
    // nums前i个数形成的子序列一定要做到,子序列累加和%7 == j
    // 这样的子序列最大累加和是多少
    // 注意 : dp[i][j] == -1代表不存在这样的子序列
    int[][] dp = new int[n + 1][7];
    dp[0][0] = 0;
    for (int j = 1; j < 7; j++) {
      dp[0][j] = -1;
    }
    for (int i = 1, x, cur, need; i <= n; i++) {
      x = nums[i - 1];
      cur = nums[i - 1] % 7;
      for (int j = 0; j < 7; j++) {
        dp[i][j] = dp[i - 1][j];
        // 这里求need是核心
        need = cur <= j ? (j - cur) : (j - cur + 7);
        // 或者如下这种写法也对
        // need = (7 + j - cur) % 7;
        if (dp[i - 1][need] != -1) {
          dp[i][j] = Math.max(dp[i][j], dp[i - 1][need] + x);
        }
      }
    }
    return dp[n][0];
  }
  // 为了测试
  // 生成随机数组
  public static int[] randomArray(int n, int v) {
    int[] ans = new int[n];
    for (int i = 0; i < n; i++) {
      ans[i] = (int) (Math.random() * v);
    }
    return ans;
  }
  // 为了测试
  // 对数器
  public static void main(String[] args) {
    int n = 15;
    int v = 30;
    int testTime = 20000;
    System.out.println("测试开始");
    for (int i = 0; i < testTime; i++) {
      int len = (int) (Math.random() * n) + 1;
      int[] nums = randomArray(len, v);
      int ans1 = maxSum1(nums);
      int ans2 = maxSum2(nums);
      if (ans1 != ans2) {
        System.out.println("出错了!");
      }
    }
    System.out.println("测试结束");
  }
}

code3 魔法卷轴

// 魔法卷轴

// 给定一个数组nums,其中可能有正、负、0

// 每个魔法卷轴可以把nums中连续的一段全变成0

// 你希望数组整体的累加和尽可能大

// 卷轴使不使用、使用多少随意,但一共只有2个魔法卷轴

// 请返回数组尽可能大的累加和

// 对数器验证

不使用魔法卷轴,整体累加和

使用1次魔法卷轴,prefix[i] 0~i累加和;i处不使用:prefix[i-1]+nums[i],i处使用:之前最大前缀和

使用2次魔法卷轴,划分中点,左右各使用一次魔法卷轴

suffix[i] i~n-1累加和,i处不使用:suffix[i]+nums[i+1],i处使用:之前最大后缀和

package class071;
// 魔法卷轴
// 给定一个数组nums,其中可能有正、负、0
// 每个魔法卷轴可以把nums中连续的一段全变成0
// 你希望数组整体的累加和尽可能大
// 卷轴使不使用、使用多少随意,但一共只有2个魔法卷轴
// 请返回数组尽可能大的累加和
// 对数器验证
public class Code03_MagicScrollProbelm {
  // 暴力方法
  // 为了测试
  public static int maxSum1(int[] nums) {
    int p1 = 0;
    for (int num : nums) {
      p1 += num;
    }
    int n = nums.length;
    int p2 = mustOneScroll(nums, 0, n - 1);
    int p3 = Integer.MIN_VALUE;
    for (int i = 1; i < n; i++) {
      p3 = Math.max(p3, mustOneScroll(nums, 0, i - 1) + mustOneScroll(nums, i, n - 1));
    }
    return Math.max(p1, Math.max(p2, p3));
  }
  // 暴力方法
  // 为了测试
  // nums[l...r]范围上一定要用一次卷轴情况下的最大累加和
  public static int mustOneScroll(int[] nums, int l, int r) {
    int ans = Integer.MIN_VALUE;
    // l...r范围上包含a...b范围
    // 如果a...b范围上的数字都变成0
    // 返回剩下数字的累加和
    // 所以枚举所有可能的a...b范围
    // 相当暴力,但是正确
    for (int a = l; a <= r; a++) {
      for (int b = a; b <= r; b++) {
        // l...a...b...r
        int curAns = 0;
        for (int i = l; i < a; i++) {
          curAns += nums[i];
        }
        for (int i = b + 1; i <= r; i++) {
          curAns += nums[i];
        }
        ans = Math.max(ans, curAns);
      }
    }
    return ans;
  }
  // 正式方法
  // 时间复杂度O(n)
  public static int maxSum2(int[] nums) {
    int n = nums.length;
    if (n == 0) {
      return 0;
    }
    // 情况1 : 完全不使用卷轴
    int p1 = 0;
    for (int num : nums) {
      p1 += num;
    }
    // prefix[i] : 0~i范围上一定要用1次卷轴的情况下,0~i范围上整体最大累加和多少
    int[] prefix = new int[n];
    // 每一步的前缀和
    int sum = nums[0];
    // maxPresum : 之前所有前缀和的最大值
    int maxPresum = Math.max(0, nums[0]);
    for (int i = 1; i < n; i++) {
      prefix[i] = Math.max(prefix[i - 1] + nums[i], maxPresum);
      sum += nums[i];
      maxPresum = Math.max(maxPresum, sum);
    }
    // 情况二 : 必须用1次卷轴
    int p2 = prefix[n - 1];
    // suffix[i] : i~n-1范围上一定要用1次卷轴的情况下,i~n-1范围上整体最大累加和多少
    int[] suffix = new int[n];
    sum = nums[n - 1];
    maxPresum = Math.max(0, sum);
    for (int i = n - 2; i >= 0; i--) {
      suffix[i] = Math.max(nums[i] + suffix[i + 1], maxPresum);
      sum += nums[i];
      maxPresum = Math.max(maxPresum, sum);
    }
    // 情况二 : 必须用2次卷轴
    int p3 = Integer.MIN_VALUE;
    for (int i = 1; i < n; i++) {
      // 枚举所有的划分点i
      // 0~i-1 左
      // i~n-1 右
      p3 = Math.max(p3, prefix[i - 1] + suffix[i]);
    }
    return Math.max(p1, Math.max(p2, p3));
  }
  // 为了测试
  public static int[] randomArray(int n, int v) {
    int[] ans = new int[n];
    for (int i = 0; i < n; i++) {
      ans[i] = (int) (Math.random() * (v * 2 + 1)) - v;
    }
    return ans;
  }
  // 为了测试
  public static void main(String[] args) {
    int n = 50;
    int v = 100;
    int testTime = 10000;
    System.out.println("测试开始");
    for (int i = 0; i < testTime; i++) {
      int len = (int) (Math.random() * n);
      int[] nums = randomArray(len, v);
      int ans1 = maxSum1(nums);
      int ans2 = maxSum2(nums);
      if (ans1 != ans2) {
        System.out.println("出错了!");
      }
    }
    System.out.println("测试结束");
  }
}

code4 689. 三个无重叠子数组的最大和

// 三个无重叠子数组的最大和

// 给你一个整数数组 nums 和一个整数 k

// 找出三个长度为 k 、互不重叠、且全部数字和(3 * k 项)最大的子数组

// 并返回这三个子数组

// 以下标的数组形式返回结果,数组中的每一项分别指示每个子数组的起始位置

// 如果有多个结果,返回字典序最小的一个

// 测试链接 : https://leetcode.cn/problems/maximum-sum-of-3-non-overlapping-subarrays/

(1)sums[i]:i… k长度的sum

(2)prefix[i]:0…i范围上所有长度为k的子数组中最大sum的子数组的开头

(3)suffix[i]:i…n-1范围上所有长度为k的子数组中最大sum的子数组的开头

枚举中间的子数组

prefix[i-1] i…j (k个) suffix[j+1]

最好开头k i开头 最好开头s

package class071;
// 三个无重叠子数组的最大和
// 给你一个整数数组 nums 和一个整数 k
// 找出三个长度为 k 、互不重叠、且全部数字和(3 * k 项)最大的子数组
// 并返回这三个子数组
// 以下标的数组形式返回结果,数组中的每一项分别指示每个子数组的起始位置
// 如果有多个结果,返回字典序最小的一个
// 测试链接 : https://leetcode.cn/problems/maximum-sum-of-3-non-overlapping-subarrays/
public class Code04_MaximumSum3UnoverlappingSubarrays {
  public static int[] maxSumOfThreeSubarrays(int[] nums, int k) {
    int n = nums.length;
    // sums[i] : 以i开头并且长度为k的子数组的累加和
    int[] sums = new int[n];
    for (int l = 0, r = 0, sum = 0; r < n; r++) {
      // l....r
      sum += nums[r];
      if (r - l + 1 == k) {
        sums[l] = sum;
        sum -= nums[l];
        l++;
      }
    }
    // prefix[i] :
    // 0~i范围上所有长度为k的子数组中,拥有最大累加和的子数组,是以什么位置开头的
    int[] prefix = new int[n];
    for (int l = 1, r = k; r < n; l++, r++) {
      if (sums[l] > sums[prefix[r - 1]]) {
        // 注意>,为了同样最大累加和的情况下,最小的字典序
        prefix[r] = l;
      } else {
        prefix[r] = prefix[r - 1];
      }
    }
    // suffix[i] :
    // i~n-1范围上所有长度为k的子数组中,拥有最大累加和的子数组,是以什么位置开头的
    int[] suffix = new int[n];
    suffix[n - k] = n - k;
    for (int l = n - k - 1; l >= 0; l--) {
      if (sums[l] >= sums[suffix[l + 1]]) {
        // 注意>=,为了同样最大累加和的情况下,最小的字典序
        suffix[l] = l;
      } else {
        suffix[l] = suffix[l + 1];
      }
    }
    int a = 0, b = 0, c = 0, max = 0;
    // 0...i-1    i...j    j+1...n-1
    //   左     中(长度为k)     右
    for (int p, s, i = k, j = 2 * k - 1, sum; j < n - k; i++, j++) {
      // 0.....i-1   i.....j  j+1.....n-1
      // 最好开头p      i开头     最好开头s
      p = prefix[i - 1];
      s = suffix[j + 1];
      sum = sums[p] + sums[i] + sums[s];
      if (sum > max) {
        // 注意>,为了同样最大累加和的情况下,最小的字典序
        max = sum;
        a = p;
        b = i;
        c = s;
      }
    }
    return new int[] { a, b, c };
  }
}

code5 可以翻转1次的情况下子数组最大累加和

// 可以翻转1次的情况下子数组最大累加和

// 给定一个数组nums,

// 现在允许你随意选择数组连续一段进行翻转,也就是子数组逆序的调整

// 比如翻转[1,2,3,4,5,6]的[2~4]范围,得到的是[1,2,5,4,3,6]

// 返回必须随意翻转1次之后,子数组的最大累加和

// 对数器验证

start[i]:i开头,往右延伸子数组的最大和

①nums[i];②nums[i]+start[i+1]

end[i]:0…i结尾,最大累加和

①nums[i];②nums[i]+end[i-1]

i之前最大累加和maxEnd+往右延伸子数组的最大和

ans=maxEnd+start[i]

package class071;
// 可以翻转1次的情况下子数组最大累加和
// 给定一个数组nums,
// 现在允许你随意选择数组连续一段进行翻转,也就是子数组逆序的调整
// 比如翻转[1,2,3,4,5,6]的[2~4]范围,得到的是[1,2,5,4,3,6]
// 返回必须随意翻转1次之后,子数组的最大累加和
// 对数器验证
public class Code05_ReverseArraySubarrayMaxSum {
  // 暴力方法
  // 为了验证
  public static int maxSumReverse1(int[] nums) {
    int ans = Integer.MIN_VALUE;
    for (int l = 0; l < nums.length; l++) {
      for (int r = l; r < nums.length; r++) {
        reverse(nums, l, r);
        ans = Math.max(ans, maxSum(nums));
        reverse(nums, l, r);
      }
    }
    return ans;
  }
  // nums[l...r]范围上的数字进行逆序调整
  public static void reverse(int[] nums, int l, int r) {
    while (l < r) {
      int tmp = nums[l];
      nums[l++] = nums[r];
      nums[r--] = tmp;
    }
  }
  // 返回子数组最大累加和
  public static int maxSum(int[] nums) {
    int n = nums.length;
    int ans = nums[0];
    for (int i = 1, pre = nums[0]; i < n; i++) {
      pre = Math.max(nums[i], pre + nums[i]);
      ans = Math.max(ans, pre);
    }
    return ans;
  }
  // 正式方法
  // 时间复杂度O(n)
  public static int maxSumReverse2(int[] nums) {
    int n = nums.length;
    // start[i] : 所有必须以i开头的子数组中,最大累加和是多少
    int[] start = new int[n];
    start[n - 1] = nums[n - 1];
    for (int i = n - 2; i >= 0; i--) {
      // nums[i]
      // nums[i] + start[i+1]
      start[i] = Math.max(nums[i], nums[i] + start[i + 1]);
    }
    int ans = start[0];
    // end : 子数组必须以i-1结尾,其中的最大累加和
    int end = nums[0];
    // maxEnd :
    // 0~i-1范围上,
    // 子数组必须以0结尾,其中的最大累加和
    // 子数组必须以1结尾,其中的最大累加和
    // ...
    // 子数组必须以i-1结尾,其中的最大累加和
    // 所有情况中,最大的那个累加和就是maxEnd
    int maxEnd = nums[0];
    for (int i = 1; i < n; i++) {
      // maxend   i....
      // 枚举划分点 i...
      ans = Math.max(ans, maxEnd + start[i]);
      // 子数组必须以i结尾,其中的最大累加和
      end = Math.max(nums[i], end + nums[i]);
      maxEnd = Math.max(maxEnd, end);
    }
    ans = Math.max(ans, maxEnd);
    return ans;
  }
  // 为了测试
  // 生成随机数组
  public static int[] randomArray(int n, int v) {
    int[] ans = new int[n];
    for (int i = 0; i < n; i++) {
      ans[i] = (int) (Math.random() * (v * 2 + 1)) - v;
    }
    return ans;
  }
  // 为了测试
  // 对数器
  public static void main(String[] args) {
    int n = 50;
    int v = 200;
    int testTime = 20000;
    System.out.println("测试开始");
    for (int i = 0; i < testTime; i++) {
      int len = (int) (Math.random() * n) + 1;
      int[] arr = randomArray(len, v);
      int ans1 = maxSumReverse1(arr);
      int ans2 = maxSumReverse2(arr);
      if (ans1 != ans2) {
        System.out.println("出错了!");
      }
    }
    System.out.println("测试结束");
  }
}

code6 删掉1个数字后长度为k的子数组最大累加和

// 删掉1个数字后长度为k的子数组最大累加和

// 给定一个数组nums,求必须删除一个数字后的新数组中

// 长度为k的子数组最大累加和,删除哪个数字随意

// 对数器验证

原数组选择k+1长度的子数组

删除最小的累加和

package class071;
// 删掉1个数字后长度为k的子数组最大累加和
// 给定一个数组nums,求必须删除一个数字后的新数组中
// 长度为k的子数组最大累加和,删除哪个数字随意
// 对数器验证
public class Code06_DeleteOneNumberLengthKMaxSum {
  // 暴力方法
  // 为了测试
  public static int maxSum1(int[] nums, int k) {
    int n = nums.length;
    if (n <= k) {
      return 0;
    }
    int ans = Integer.MIN_VALUE;
    for (int i = 0; i < n; i++) {
      int[] rest = delete(nums, i);
      ans = Math.max(ans, lenKmaxSum(rest, k));
    }
    return ans;
  }
  // 暴力方法
  // 为了测试
  // 删掉index位置的元素,然后返回新数组
  public static int[] delete(int[] nums, int index) {
    int len = nums.length - 1;
    int[] ans = new int[len];
    int i = 0;
    for (int j = 0; j < nums.length; j++) {
      if (j != index) {
        ans[i++] = nums[j];
      }
    }
    return ans;
  }
  // 暴力方法
  // 为了测试
  // 枚举每一个子数组找到最大累加和
  public static int lenKmaxSum(int[] nums, int k) {
    int n = nums.length;
    int ans = Integer.MIN_VALUE;
    for (int i = 0; i <= n - k; i++) {
      int cur = 0;
      for (int j = i, cnt = 0; cnt < k; j++, cnt++) {
        cur += nums[j];
      }
      ans = Math.max(ans, cur);
    }
    return ans;
  }
  // 正式方法
  // 时间复杂度O(N)
  public static int maxSum2(int[] nums, int k) {
    int n = nums.length;
    if (n <= k) {
      return 0;
    }
    // 单调队列 : 维持窗口内最小值的更新结构,讲解054的内容
    int[] window = new int[n];
    int l = 0;
    int r = 0;
    // 窗口累加和
    long sum = 0;
    int ans = Integer.MIN_VALUE;
    for (int i = 0; i < n; i++) {
      // 单调队列 : i位置进入单调队列
      while (l < r && nums[window[r - 1]] >= nums[i]) {
        r--;
      }
      window[r++] = i;
      sum += nums[i];
      if (i >= k) {
        ans = Math.max(ans, (int) (sum - nums[window[l]]));
        if (window[l] == i - k) {
          // 单调队列 : 如果单调队列最左侧的位置过期了,从队列中弹出
          l++;
        }
        sum -= nums[i - k];
      }
    }
    return ans;
  }
  // 为了测试
  // 生成长度为n,值在[-v, +v]之间的随机数组
  public static int[] randomArray(int n, int v) {
    int[] ans = new int[n];
    for (int i = 0; i < n; i++) {
      ans[i] = (int) (Math.random() * (2 * v + 1)) - v;
    }
    return ans;
  }
  // 为了测试
  // 对数器
  public static void main(String[] args) {
    int n = 200;
    int v = 1000;
    int testTimes = 10000;
    System.out.println("测试开始");
    for (int i = 0; i < testTimes; i++) {
      int len = (int) (Math.random() * n) + 1;
      int[] nums = randomArray(len, v);
      int k = (int) (Math.random() * n) + 1;
      int ans1 = maxSum1(nums, k);
      int ans2 = maxSum2(nums, k);
      if (ans1 != ans2) {
        System.out.println("出错了!");
      }
    }
    System.out.println("测试结束");
  }
}

2023-11-09 17:23:58

相关文章
|
6月前
|
设计模式 算法 Java
【数据结构和算法】删掉一个元素以后全为 1 的最长子数组
这是力扣的 1493 题,难度为中等,解题方案有很多种,本文讲解我认为最奇妙的一种。又又又是一道滑动窗口的典型例题,可以帮助我们巩固滑动窗口算法。这道题很活灵活现,需要加深对题意的变相理解。给你一个二进制数组nums,你需要从中删掉一个元素。 请你在删掉元素的结果数组中,返回最长的且只包含 1 的非空子数组的长度。 如果不存在这样的子数组,请返回 0 。
110 1
|
6月前
|
算法 数据处理 C++
【C++ 20 新特性 算法和迭代器库的扩展和泛化 Ranges】深入浅出C++ Ranges库 (Exploring the C++ Ranges Library)
【C++ 20 新特性 算法和迭代器库的扩展和泛化 Ranges】深入浅出C++ Ranges库 (Exploring the C++ Ranges Library)
788 1
|
3月前
|
算法
【算法】滑动窗口——长度最小的子数组
【算法】滑动窗口——长度最小的子数组
|
5月前
|
机器学习/深度学习 算法 搜索推荐
【初阶算法4】——归并排序的详解,及其归并排序的扩展
【初阶算法4】——归并排序的详解,及其归并排序的扩展
【初阶算法4】——归并排序的详解,及其归并排序的扩展
|
2月前
|
资源调度 算法
基于迭代扩展卡尔曼滤波算法的倒立摆控制系统matlab仿真
本课题研究基于迭代扩展卡尔曼滤波算法的倒立摆控制系统,并对比UKF、EKF、迭代UKF和迭代EKF的控制效果。倒立摆作为典型的非线性系统,适用于评估不同滤波方法的性能。UKF采用无迹变换逼近非线性函数,避免了EKF中的截断误差;EKF则通过泰勒级数展开近似非线性函数;迭代EKF和迭代UKF通过多次迭代提高状态估计精度。系统使用MATLAB 2022a进行仿真和分析,结果显示UKF和迭代UKF在非线性强的系统中表现更佳,但计算复杂度较高;EKF和迭代EKF则更适合维数较高或计算受限的场景。
|
6月前
|
算法
|
3月前
|
算法 C++
【算法】前缀和算法——和可被K整除的子数组
【算法】前缀和算法——和可被K整除的子数组
|
3月前
|
算法
【算法】前缀和算法——和为k的子数组之和
【算法】前缀和算法——和为k的子数组之和
|
3月前
|
算法 Java
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
42 0
|
6月前
|
设计模式 算法 Java
【数据结构和算法】子数组最大平均数 I
​ 原题链接:力扣 643 题 子数组最大平均数 I 给你一个由n个元素组成的整数数组nums和一个整数k。 请你找出平均数最大且长度为k的连续子数组,并输出该最大平均数。 任何误差小于10-5的答案都将被视为正确答案。 ​
70 3