[LeetCode] 乘积为正数的最长子数组长度

256 阅读3分钟

这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

题目

乘积为正数的最长子数组长度

解析

两个数相乘有以下几种情况:

  • 任何数和0相乘:得0
  • 任何乘负数得相反值
  • 任何乘正数性质不变

那么,至少可以得出第一个边界条件:

  • 只要遇到0,那么就中断了,因为要求的是连续的长度。

而两个负数相乘,得到的是正数;那么可以维护一个二维dp数组,来记录:到这个位置为止,连续的相乘为正数序列多长,连续的相乘为负数序列多长。

dp[i]:0为正数,1为负数

那么,如果我们读下一个数,就可以做出判断:

  • 如果是0:总结状态,重新开始

  • 如果是正数:

    //当前正数序列长度+1
    dp[i+1][0] = dp[i][0]+1;
    //如果当前负数序列长度为0,那么维持为0;否则,+1
    dp[i+1][1] = dp[i][1]==0?0:dp[i][1]+1;
    
  • 如果是负数:

    //如果当前负数序列长度>0,那么正数长度=负数长度+1;否则正数序列长度为0
    dp[i+1][0] = dp[i][1]>0?dp[i][1]+1:0;
    //负数序列长度为之前的正数序列长度+1
    dp[i+1][1] = dp[i][0]+1;
    

为了编码方便,将dp数组长度变为n+1(否则索引0位置获取不到),也就是说数组中对应位置的状态,在dp数组中的索引需要+1.

public static int getMaxLen(int[] nums) {
        int[][] dp = new int[nums.length+1][2];
        int max = 0;
        for (int i = 0; i < nums.length; i++) {
            if(nums[i]==0){
                continue;
            }
            if(nums[i]<0){
                dp[i+1][0] = dp[i][1]>0?dp[i][1]+1:0;
                dp[i+1][1] = dp[i][0]+1;
            }else{
                dp[i+1][0] = dp[i][0]+1;
                dp[i+1][1] = dp[i][1]==0?0:dp[i][1]+1;
            }
            max = Math.max(dp[i+1][0],max);
        }
        return max;
    }

AC了,但是成绩垃圾:

执行用时:16 ms, 在所有 Java 提交中击败了5.14%的用户

内存消耗:57.3 MB, 在所有 Java 提交中击败了5.06%的用户

改进

空间上,显然dp数组只用到了前后两个位置的4个数,那么用4个数即可;同时注意到相互之间的关系,那么其实用2个数就可以替代dp数组了。

public static int getMaxLen(int[] nums) {
    int lastP = 0,lastM = 0;
    int max = 0;
    for (int i = 0; i < nums.length; i++) {
        if(nums[i]==0){
            lastP = 0;
            lastM = 0;
            continue;
        }
        if(nums[i]<0){
            int tmp = lastP;
            lastP = lastM >0?lastM+1:0;
            lastM = tmp+1;
        }else{
            lastP = lastP+1;
            lastM = lastM==0?0:lastM+1;
        }
        max = Math.max(lastP,max);
    }
    return max;
}

执行用时:3 ms, 在所有 Java 提交中击败了99.70%的用户

内存消耗:54.8 MB, 在所有 Java 提交中击败了71.81%的用户

时间上,思考一下:是否我们需要读到每一个数都做一次max?

事实上我们可以只在lastP更新到更大值的时候做就可以。

public static int getMaxLen(int[] nums) {
        int lastP = 0,lastM = 0;
        int max = 0;
        for (int i = 0; i < nums.length; i++) {
            if(nums[i]==0){
                lastP = 0;
                lastM = 0;
                continue;
            }
            boolean calMax = false;
            if(nums[i]<0){
                int tmp = lastP;
                if(lastM >0){
                    calMax = true;
                    lastP = lastM+1;
                }else lastP = 0;
                lastM = tmp+1;
            }else{
                lastP = lastP+1;
                calMax = true;
                lastM = lastM==0?0:lastM+1;
            }
            if(calMax) max = Math.max(lastP,max);
        }
        return max;
    }

执行用时:3 ms, 在所有 Java 提交中击败了99.70%的用户

内存消耗:54.7 MB, 在所有 Java 提交中击败了86.96%的用户