编程之美——2.13 子数组的最大乘积

/**
 * 给定一个长度为N的整数数组, 只允许用乘法, 不能用除法, 计算任意的组合中乘积最大的一组, 并写出算法的时间复杂度.
 */


#include<stdio.h>
#include<stdlib.h>
#include<math.h>


#define N 10


// 以下方法的时间复杂度为O(3*N), 需要遍历数组三次, 额外的空间复杂度为O(1), 只需要常数个变量
int max1(int number[]) {
int product = 1;
int i = 0;
int temp;
for(i = 0; i < N - 1; ++i) {
product *= number[i];
}
temp = -1;
if (product > 0 && number[N - 1] == 0)
return product;
if (product <= 0 && number[N - 1] == 0)
return 0;
// 假设, 一个数字的影响因子表示这个数字对乘积的贡献度, 影响因子越大, 则表示贡献度越高, 此时乘积越大.
// product为前N-1个数字的乘积, 现在用第N个数字与前面的N-1个数字打擂台
// 若是第N个数字对乘积的影响因子比前面的N-1个数字中的第i个数字大, 则说明第N个数字应该位于乘积所需的N-1个数字之中.
// 然后用变量temp记录下影响因子小于第N个数字的数字的下标.
// 如果有多个数字的影响因子小于第N个数字, 则同样采取打擂台的方式, 选取出影响因子最小的一个数字.
// 当product为正数时, 则要替换的数字与第N个数字必须符号相同, 否则替换后结果变为负号, 负数永远小于正数; 两数符号相同, 绝对值越大, 影响因子越大
// 当product为负数时, 则进行两种讨论:
// 1. number[N-1]与number[i]符号相同, 则绝对值越小的数字影响因子越大; 
// 1.1 number[temp]与number[N-1]符号相同, 即三数符号相同, 则number[temp]与number[i]中绝对值较小的数字影响因子较大
// 1.2 number[temp]与number[N-1]符号不同时, 表明此时将number[temp]剔除之后, 乘积变为正数, 不能再替换
// 2. number[N-1]与number[i]符号不同, 则number[N-1]的影响因子较大, number[i]的影响因子较小, 因为替换之后, 乘积变为正数
// 2.1 如果number[temp]与number[N-1]符号相同, 即number[temp]与number[i]符号不同,则直接替换, 因为此时number[temp]的影响因子较大, number[i]替换出去之后, 能使乘积为正
// 2.2 如果number[temp]与number[N-1]符号不同, 即number[temp]与number[i]符号相同, 则此时number[temp]与number[i], 绝对值越大, 影响因子越大
for(i = 0; i < N - 1; ++i) {
if (product > 0) {
// 首先排除number[N - 1]与number[i]符号不同的情况
// 当product为正数时, 若number[N - 1]与number[i]符号不同, 替换后的结果必然是负号
if (number[N - 1] * number[i] < 0) // 此时不需要判断number[i]和number[temp], 因为只有当number[N-1]和number[m]符号相同时才有替换的意义, 所以number[temp]和number[N-1]的符号必然相同
continue;
if (abs(number[i]) >= abs(number[N - 1]))
continue;
if (temp == -1) {
temp = i;
continue;
}
if (abs(number[i]) < abs(number[temp]))
temp = i;
} else {
// number[N-1]与number[i]符号相同的情况下
if (number[N - 1] * number[i] > 0){
// 此时仍不需要比较number[i]和number[temp]的影响因子
// 当number[temp]与number[i]和number[N - 1]同号时, number[temp]的影响因子必然最小
// 当number[temp]与number[i]符号不同时, 则换出number[temp], 乘积为正, 正合适.
if (abs(number[N - 1]) >= abs(number[i]))
continue;
if (temp == -1) {
temp = i;
continue;
}
if (number[temp] * number[i] > 0) {
if (abs(number[temp]) < abs(number[i]))
temp = i;
}
} else { // number[N-1]与number[i]符号不同
if (temp == -1) {
temp = i;
continue;
}
if (number[temp] * number[N-1] < 0) {
if (abs(number[temp])  > abs(number[i]))
temp = i;
} else {
temp = i; 
}
}
}
}
return temp;
}


// 书中给出的算法一:
/**
 * number[]为初始数组, 定义数组s[], s[i]表示数组前i个元素的乘积
 * 定义数组t[], t[i]表示数组后i个元素的乘积
 * 定义数组p[i], 表示除了第i个元素之外的其他元素的乘积
 * 则p[i] = s[i-1]*t[i+1]
 * 如此计算s和t, 各需要扫描一次数组, 计算p需要扫描一次s和t, 寻找p中的最大值, 再扫描一次, 时间复杂度为O(4*N), 空间复杂度为O(3*N)
 */
int max2(int number[]) {
int s[N + 1], t[N + 2], p[N + 1];
int i, product;
product = 1;
// 设置s的边界元素, s[0] = 1, t的边界元素t[N] = 1
// 主要是用于计算p[0]和p[N - 1]
s[0] = 1;
t[N+1] = 1;
for(i = 0; i < N; ++i) {
product *= number[i];
s[i + 1] = product;
}
product = 1;
for(i = N - 1; i >= 0; --i) {
product *= number[i];
t[i + 1] = product;
}
for(i = 1; i <= N; ++i)
p[i] = s[i-1] * t[i+1];
product = 1;
for(i = 2; i <= N; ++i) {
if (p[product] < p[i])
product = i;
}
printf("乘积最大的子数组为不包含第%d个元素的子数组, 其乘积为%d\n", product, p[product]);
return 0;
}


// 书中给出的算法二:
/**
 * 算法二的思想很简单的, 通过判断N个数字的乘积的正负, 来确定该选择哪N-1个数字的组合会使得结果最大.
 * 根本思想就是求解数组中正数, 负数和0的个数.
 * 当0的个数>=2时, 无论怎么选, N-1个数的乘积总为0
 * 当0的个数 == 1, 且负数的个数为奇数的时候, 除0之外的N-1个数的乘积为负值, 此时仍然返回0
 * 当0的个数 == 1, 且负数的个数为偶数的时候, 除0之外的N-1个数字的组合即为所求
 * 当0的个数为0时, 分析负数的个数:
 * 负数为奇数个, 则乘积为负数, 去掉一个绝对值最小的负数, 即可得到一个最大的正数乘积
 * 负数为偶数个, 则乘积为正数, 
 * 此时需要判断正数的个数, 若正数个数为0, 则去掉一个绝对值最大的负数
 * 若正数个数不为0, 则去掉绝对值最小的正数.
 * 此算法只需要遍历一次数组, 且空间复杂度为O(1)
 */
int Product(int index, int number[]) {
int i;
int product = 1;
for(i = 0; i < N; ++i) {
if (i != index)
product *= number[i];
}
return product;
}
int max3(int number[]) {
int num[3]; // 下标0, 1, 2分别记录0, 正数, 负数的个数
int min = -1; // 绝对值最小的正数的下标
int max1 = -1; // 绝对值最大的负数的下标
int min1 = -1; // 绝对值最小的负数的下标
int zero = -1; // 值为0的数字的下标, 只需记录一个即可
int i;
for (i = 0; i < 3; ++i)
num[i] = 0;
for(i = 0; i < N; ++i) {
if (number[i] == 0) {
num[0]++;
zero = i;
}
else if (number[i] > 0) {
num[1]++;
if (min == -1) {
min = i;
continue;
}
if (number[i] < number[min])
min = i;
} else {
num[2]++;
if (max1 == -1 && min1 == -1) {
max1 = i;
min1 = i;
continue;
}
if (number[i] < number[max1])
max1 = i;
if (number[i] > number[min1])
min1 = i;
}
}
if (num[0] >= 2) {
printf("乘积最大的子数组为不包含任意一个元素的子数组, 其乘积为%d\n", 0);
return 0;
}
if (num[0] == 1) {
if (num[2] % 2 == 1){
printf("乘积最大的子数组为包含第%d个元素的任何一个N-1元素的子数组, 其乘积为%d\n", zero + 1, 0);
return 0;
}
printf("乘积最大的子数组为不包含第%d个元素的子数组, 其乘积为%d\n", zero + 1, Product(zero, number));
}
if (num[2] % 2 == 1) {
printf("乘积最大的子数组为不包含第%d个元素的子数组, 其乘积为%d\n", min1 + 1, Product(min1, number));
return 0;
}
if (num[1] == 0) {
printf("乘积最大的子数组为不包含第%d个元素的子数组, 其乘积为%d\n", max1 + 1, Product(max1, number));
return 0;
}
printf("乘积最大的子数组为不包含第%d个元素的子数组, 其乘积为%d\n", min + 1, Product(min, number));
return 0;
}


int main() {
int number[] = {-10, -9, -8, -7, -6, -5, -4, -3, -2, -1};
int i;
int temp, product;
temp = max1(number);
product = 1;
if (temp == -1)
temp = N - 1;
for(i = 0; i < N; ++i) {
if (i != temp)
product *= number[i];
}
printf("乘积最大的子数组为不包含第%d个元素的子数组, 其乘积为%d\n", temp + 1, product);
max2(number);
max3(number);
return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值