- 题目
- 一个数组 nums 长度为 n, 每个元素都是整数
- 选择一个下标 i (0<=i<n)和一个任意数 x,使得 nums[i] = x
- 请问最少进行多少次改变,能够使得 nums 的任意一个长度大于 1 的子数组都是非负数:sum(subNums)>=0,subNums 为数组中连续元素,len(subNums) > 1
- 例子:
- [1,4,-7,-1,3]
- 1
- 解释:
- nums[2] = 1000000, nums = [1,4,1000000,-1,3]
- 假设 nums 数组任意子数组元素和均在 int 范围内
- 解法一:
- 二分答案 + dfs 暴力校验:
- 如果 res 此改变可以满足,那么 res 次改变以上也可以满足,同时最多修改的个数是两种情况的最小值、负数的个数(负数全部修改为正数) 或 总个数除以 2(从第二个元素开始每隔一个元素修改为正数、这样每个规定的子数组一定包含此正数),因此可以二分(0,min(负数个数,总个数/2)]
- 验证修改 res 个元素是否可行,先使用 dfs 修改 res 个元素为 int 最大值,再判断每种修改后的情况、判断每个子数组是否非负,只要有一种符合代表结果可行;dfs 修改时从第 2 个元素开始修改(第 1 个元素要形成规定子数组一定需要第 2 个元素),并且不需要修改连续两个元素,需要判断剩下的元素是否足够修改,同时如果不修改当前节点还需要满足以当前元素为后缀起点的长度大于 1 的最小值为非负数
- 代码一:
private int solution2(int[] nums) {
if (nums == null || nums.length < 2) {
return 0;
}
int negativeNum = getNegativeNum(nums);
System.out.println("negativeNum:" + negativeNum);
int minNum = 0;
int maxNum = Math.min(negativeNum, nums.length >> 1);
if (maxNum == 0) {
return 0;
}
System.out.println("minNum:" + minNum + " maxNum:" + maxNum);
return binarySearchAnswer(minNum, maxNum, nums);
}
private int getNegativeNum(int[] nums) {
int negativeNum = 0;
for (int num : nums) {
if (num < 0) {
negativeNum++;
}
}
return negativeNum;
}
private int binarySearchAnswer(int left, int right, int[] nums) {
int res = left;
while(left <= right) {
int mid = ((right - left) >> 1) + left;
if (checkPositiveSubArray(nums, mid)) {
res = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return res;
}
private boolean checkPositiveSubArray(int[] nums, int res) {
int len = nums.length;
boolean flag = recursionNums(nums, 1, len, res);
System.out.println(res + ":" + flag);
return flag;
}
private boolean recursionNums(int[] nums, int index, int len, int res) {
if (index >= len) {
if (res > 0) {
return false;
}
return checkAllSubArray(nums, len);
}
boolean flag = false;
if (res > 0 && ((len - index - 1) >> 1) >= res - 1) {
int tmp = nums[index];
nums[index] = Integer.MAX_VALUE;
flag = recursionNums(nums, index + 2, len, res - 1);
nums[index] = tmp;
}
if (!flag && ((len - index) >> 1) >= res && getSuffixMin(nums, index) >= 0) {
flag = recursionNums(nums, index + 1, len, res);
}
return flag;
}
private long getSuffixMin(int[] nums, int index) {
long suffixMin = nums[index] + nums[index - 1];
long suffix = nums[index];
for (int i = index - 1; i >= 0; i--) {
suffix += nums[i];
suffixMin = Math.min(suffixMin, suffix);
}
return suffixMin;
}
private boolean checkAllSubArray(int[] nums, int len) {
for (int i = 0; i < len; i++) {
long prefix = nums[i];
for (int j = i + 1; j < len; j++) {
prefix += nums[j];
if (prefix < 0) {
return false;
}
}
}
return true;
}
- 解法二:
- 动态规划
- 第一步:从第二个元素开始,以当前元素为后缀起点,获取长度大于一的后缀和的最小值,动态规划方程式:dp[i]=min(dp[i-1],nums[i-1])+nums[i]
- 第二步:往后遍历,直到 dp[i] 小于 0,则将 nums[i] 置为 inf,此时从 i+1 位置为第一位回到第一步(如果 i+1 是最后一个元素,则结束),因为 i 设置为 inf 后无论任何数加上它都是正数
- 结果就是置为 inf 的个数
- 空间压缩:dp 中 i 仅仅与 i-1 相关,同时只有一重循环,因此只需要一个变量 dp 覆盖即可
- 时间复杂度:O(n),空间复杂度:O(1)
- 代码二:
private int solution(int[] nums) {
if (nums == null || nums.length < 2) {
return 0;
}
int dp = nums[0];
int len = nums.length;
int res = 0;
for (int i = 1; i < len; i++) {
dp = Math.min(dp, nums[i - 1]) + nums[i];
if (dp < 0) {
res++;
i++;
if (i >= len -1) {
break;
}
dp = nums[i];
}
}
return res;
}