1. 乘积最大子数组
(1)遍历
- 遍历数组时计算当前最大值,不断更新
- 令imax为当前最大值,则当前最大值为 imax = max(imax * nums[i], nums[i])
- 由于存在负数,那么会导致最大的变最小的,最小的变最大的。
- 因此还需要维护当前最小值imin,imin = min(imin * nums[i], nums[i])
- 当负数出现时则imax与imin进行交换再进行下一步计算
- 时间复杂度:O(n)
class Solution {
public int maxProduct(int[] nums) {
int max = Integer.MIN_VALUE, imax = 1, imin = 1;
for(int i=0; i<nums.length; i++){
if(nums[i] < 0){
int tmp = imax;
imax = imin;
imin = tmp;
}
imax = Math.max(imax*nums[i], nums[i]);
imin = Math.min(imin*nums[i], nums[i]);
max = Math.max(max, imax);
}
return max;
}
}
2. 乘积小于K的子数组
(1)滑动窗口
有这样一个规律:窗口内每加入一个数,如果乘积仍然小于k,则 满足条件的子数组数量 += 窗口内元素数量
例如 [1.2.3.4.5] , k = 7, 符合要求子数组数量为res=0;
[1] res += 子数组.length; res=1;
[1,2] res =res+ 2 = 3
[1,2,3] res = res+3 =6
当遇到比k还大的元素时,直接重新在这个元素的右边继续开始计算
当窗口内元素乘积大于等于k时,在左边踢出元素,收缩窗口,直到乘积不再大于等于k
class Solution {
public int numSubarrayProductLessThanK(int[] nums, int k) {
if(k <= 1)
return 0;
int ans = 0, left = 0, right = 0;
int mul = 1;
while(right < nums.length) {
//当left = right时就为1了,是小于k的不会越界
//此时保证了mul是小于k的,相当于求右边界左边界为l,右边界为r的连续子数组的个数,
//右边界是不断递增的,又因为是连续,所以满足条件的每种长度的子数组只会有一个
//且右边界一直在变也就是不会重复
//窗口内每加入一个数,如果乘积仍然小于k,则 满足条件的子数组数量 += 窗口内元素数量
mul *= nums[right];
while(mul >= k) { //当遇到比k还大的元素时,直接重新在这个元素的右边继续开始计算
mul /= nums[left]; //当窗口内元素乘积mul大于等于k时,在左边踢出元素,收缩窗口,直到乘积不再大于等于k
left++;
}
ans += (right - left + 1);
right++;
}
return ans;
}
}
3. 除自身以外数组的乘积
(1)左边乘积 * 右边乘积(有点前缀和的思想)
原数组: [1 2 3 4]
左部分的乘积: 1 1 1*2 1*2*3
右部分的乘积: 2*3*4 3*4 4 1
结果: 1*2*3*4 1*3*4 1*2*4 1*2*3*1
class Solution {
public int[] productExceptSelf(int[] nums) {
// 结果存储
int[] res = new int[nums.length];
int k = 1;
// 首先存储每个数左边数的乘积
for(int i = 0; i < res.length; i++){
res[i] = k;
k = k * nums[i]; //K有点像前缀和的思想,存的是i之前的数的乘积
}
k = 1;
// 然后在原有的基础上乘上每个数右边数的乘积
for(int i = res.length - 1; i >= 0; i--){
res[i] *= k; //K有点像前缀和的思想,存的是i之前的数的乘积
k *= nums[i];
}
return res;
}
}
4. 字符串相乘
(1)模拟手算
class Solution {
/**
* 计算形式
* num1
* x num2
* ------
* result
*/
public String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0")) {
return "0";
}
// 保存计算结果
String res = "0";
// num2 逐位与 num1 相乘
for (int i = num2.length() - 1; i >= 0; i--) {
int carry = 0;
// 保存 num2 第i位数字与 num1 相乘的结果
StringBuilder temp = new StringBuilder();
// 补 0
for (int j = 0; j < num2.length() - 1 - i; j++) {
temp.append(0);
}
int n2 = num2.charAt(i) - '0';
// num2 的第 i 位数字 n2 与 num1 相乘
for (int j = num1.length() - 1; j >= 0 || carry != 0; j--) {
int n1 = j < 0 ? 0 : num1.charAt(j) - '0';
int product = (n1 * n2 + carry) % 10;
temp.append(product);
carry = (n1 * n2 + carry) / 10;
}
// 将当前结果与新计算的结果求和作为新的结果
res = addStrings(res, temp.reverse().toString());
}
return res;
}
/**
* 对两个字符串数字进行相加,返回字符串形式的和
*/
public String addStrings(String num1, String num2) {
StringBuilder builder = new StringBuilder();
int carry = 0;
for (int i = num1.length() - 1, j = num2.length() - 1;
i >= 0 || j >= 0 || carry != 0;
i--, j--) {
int x = i < 0 ? 0 : num1.charAt(i) - '0';
int y = j < 0 ? 0 : num2.charAt(j) - '0';
int sum = (x + y + carry) % 10;
builder.append(sum);
carry = (x + y + carry) / 10;
}
return builder.reverse().toString();
}
}
(2)模拟
需要知道一个数学定理:
- 两个长度分别为 n 和 m 的数相乘,长度不会超过 n + m。
因此我们可以创建一个长度为 n + m 的数组 res 存储结果。 - 另外,最后拼接结果时需要注意忽略前导零。
class Solution {
public String multiply(String n1, String n2) {
int n = n1.length(), m = n2.length();
int[] res = new int[n + m];
for (int i = n - 1; i >= 0; i--) {
for (int j = m - 1; j >= 0; j--) {
int a = n1.charAt(i) - '0';
int b = n2.charAt(j) - '0';
int r = a * b;
r += res[i + j + 1];
res[i + j + 1] = r % 10;
res[i + j] += r / 10;
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n + m; i++) {
if (sb.length() == 0 && res[i] == 0) continue;
sb.append(res[i]);
}
return sb.length() == 0 ? "0" : sb.toString();
}
}