【Leetcode刷题随笔】2765最长交替子数组

1.题目描述:

该题目标是在一个整数数组 nums 中寻找最长的“交替子数组”。这种交替子数组的特点是:其元素按照“递增1,递减1,递增1…”的模式循环排列,且子数组的长度必须大于1,例如数组nums = [2,3,4,3,4],交替子数组有 [2,3],[3,4],[3,4,3] 和 [3,4,3,4]。最长的子数组为 [3,4,3,4],长度为 4。详细题目描述见原题:原题。

2.1解题思路一(双层循环):

这道题有一个重要的点是如何实现递增一、递减一、递增一的规律。观察发现,递增递减会导致相邻的两个数是相差1,相隔的两个数是相等即索引差值为偶数时,两元素相等,索引差值为奇数时,两元素相差1,因此可以从这个角度去实现,判断子数组是否满足题目要求。

而对于符合条件的子数组,再判断是否为最长子数组,就需要不断更新当前子数组的长度,取更长的长度存入更新变量中。

因此,本体可以利用双层循环的方法,外层循环从左至右遍历数组,依次取每一个元素作为子数组的起始点,内层循环从起始点出发往右取子数组,并判断子数组是否满足条件。

2.1.1 代码实现(C语言)

int alternatingSubarray(int* nums, int numsSize) {
    int res = -1;
    for (int firstIndex = 0; firstIndex < numsSize; firstIndex++) {
        for (int i = firstIndex + 1; i < numsSize; i++) {
            int length = i - firstIndex + 1;
            if (nums[i] - nums[firstIndex] == (length - 1) % 2) {
                res = fmax(res, length);
            } else {
                break;
            }
        }
    }
    return res;
}

2.1.2 代码解释

  • 首先定义一个res用于存储最长子数组长度,初始化为-1,即若不存在符合条件的子数组则返回-1。
  • 构建外层循环,从索引0开始往右依次遍历,每一个被遍历的元素依次作为子数组的起始索引firstindex。
  • 在内层循环中,循环变量 i 从firstindex+1处出发往右遍历,构成[nums[firstindex],nums[firstindex+1],nums[firstindex+2]…]这样的子数组。
    • 在内存循环中,首先计算子数组长度length。
    • 再根据length(近似上面提到的索引差值)判断子数组是否符合递增递减的要求,若符合要求则更新该数组的长度为res,即
      nums[i] - nums[firstIndex] == (length - 1) % 2;
      res = fmax(res, length);
      
      可以举例解释,如nums[1, 2, 1, 3, 1, 2],
      * 当 firstindex = 0, i = 1时,length = 1 - 0 + 1 = 2,nums[i] - nums[firstindex] = 2 - 1 = 1, (length - 1) % 2 = 1,所以nums[i] - nums[firstindex] == (length - 1) % 2,符合条件,看元素也确实是递增了1,此时更新res,取当前res和当前length的较大值。
      * 当firstindex = 0, i = 2时,length = 3,nums[i] - nums[firstindex] = 1 - 1 = 0;(length - 1) % 2 = 0;所以nums[i] - nums[firstindex] == (length - 1) % 2,符合条件,看元素也确实是递减了1.此时更新res,取当前res和当前length的较大值。
      * 当firstindex = 0, i = 3时,length = 4,nums[i] - nums[firstindex] = 3 - 1 = 2;(length - 1) % 2 = 1,所以nums[i] - nums[firstindex] 不等于 (length - 1) % 2,不符合条件,不用更新res,而是break回到外层循环将firstindex右移一位继续寻找新的子数组,直到整个数组遍历完毕,返回最大的res。

2.2 解题思路二(单层循环):

从上面双层循环的方法可知,当我们内层循环遍历到不符合条件的索引 i 时,会跳回到外层循环,此时符合条件的子数组是从nums[firstindex, …, i - 1],并将外层循环从firstindex右移到firstindex+1处进行后续判断。

但是,题目要求符合条件的子数组开始时必须是递增1。即nums[firstindex]到nums[firstindex+1]是递增1,nums[firstindex+1]到nums[firstindex+2]是递减1。

此时不难看出:

  • 如果我们跳回到外层循环后还去从firstindex+1处遍历的话,那他到firstindex+2(如果存在的话)肯定是递减1,已经不符合开始时必须递增1的题目条件,属于是无效计算,浪费计算资源。

  • 同理,如果子数组足够长,我们可以将外层循环从firstindex右移到firstindex+2处进行内层循环判断,此时firstindex+2到firstindex+3处是递增1,符合题目条件。然而,此时内层循环遍历到 i 时依然会break,所以此时的子数组长度也就是nums[firstindex+2, …, i-1],明显小于最初的nums[firstindex, …, i - 1]的长度,不可能是最长交替子数组。所以,从firstindex+2处开始往右遍历也是属于无效计算,浪费计算资源。

因此,当我们在nums[i]处发现不符合题目对交替子数组的要求时,下一步直接从nums[i-1]处进行处理,跳过前面那些nums[firstindex+1, firstindex+2, …i - 2],以节省计算资源。

这样做的话就不再需要外层循环的遍历,只保留内层循环。当子数组在nums[i]处被打破时,我们直接判断nums[i-1]是否可以作为新的符合条件的子数组的起点:

  • 如果可以,则将firstindex置为 i - 1,并将前面已存储的res和2进行比较(2是指nums[i-1]到nums[i]的长度),取较大值更新res,然后循环继续往后遍历。
  • 如果不可以,则将新子数组起始索引置为 i,并往后继续遍历。

2.2.1 代码实现(C语言)

int alternatingSubarray(int* nums, int numsSize){
    int res = -1;
    int firstindex = 0;
    for(int i = 1; i < numsSize; i++){
        int length = i - firstindex + 1;
        if(nums[i] - nums[firstindex] == (length - 1) % 2){
            res = fmax(res, length);
        }else{
            if(nums[i] - nums[i - 1] == 1){
                firstindex = i - 1;
                res = fmax(res, 2);
            }else{
                firstindex = i;
            }     
        }
    }
    return res;
}

2.2.2 代码解释

  • 首先定义res和firstindex,分别初始化为-1和0。
  • 只用一层循环,循环变量 i 从1开始(避免firstindex和 i 都为0的情况)。
  • 定义length,和双层循环的定义一样。
  • 判断当前位置的值是否符合交替子数组要求,如果满足则更新res。
    if(nums[i] - nums[firstindex] == (length - 1) % 2){
            res = fmax(res, length);
    
  • 如果不满足要求,则判断nums[i-1]能否作为新子数组的开头(即和下一个元素是不是递增1的关系)。
    • 如果可以作为新子数组开头,则将firstindex置为 i - 1,并更新res。
    • 如果不能,则后移一位,让索引 i 作为新开头去进行后续判断。
    else{
            if(nums[i] - nums[i - 1] == 1){
                firstindex = i - 1;
                res = fmax(res, 2);
            }else{
                firstindex = i;
            } 
    

3. 总结

这道题关键是通过什么方法来实现先递增再递减,只要理清了这个问题就很简单,除了本文提到的方法还可以使用双指针、动态规划和滑动窗口等方法解答。

分割线-------------------------------------------------------分割线

更新双指针解法

2.3 解题思路三(双指针)

2.3.1 代码实现(C语言)

int alternatingSubarray(int* nums, int numsSize){
    int res = 0;
    for(int L = 0; L < numsSize - 1; L++) { 
        int R = L + 1; int pre = nums[R] - nums[L];
        if(pre != 1) continue;
        while(R + 1 < numsSize && nums[R + 1] - nums[R] == -pre) {
            pre = -pre;
            R++;
        }
        res = fmax(res, R - L + 1);
        L = R - 1; 
    }
    return res == 0 ? -1 : res;  
}

2.3.2 代码解释

  • 首先定义左右指针L和R,初始化L为0,R为L+1;
  • 在循环中通过左右指针不断遍历整个数组。其中定义pre为两指针指向元素的差值。
  • 先判断第一个差值pre是否为1,即子数组是不是符合开头递增1的条件,如果不满足则continue跳过后续循环步骤,将L右移一位继续判断。
  • 如果满足开头递增1,则判断后续元素是否满足交替递增递减。即通过while循环扩展子数组,只要下一个元素与前一个元素的差值与当前的差值相反(即交替增减),就继续向右扩展子数组。每次扩展后,更新 pre 的值为其相反数,以反映下一个差值应该是多少。
  • 当子数组被打破时通过fmax函数更新res的长度。并将子数组开头元素索引重置为R-1,重新寻找新的子数组。
  • 三元条件运算符获取res的返回值,如果没有找到子数组(res==0)则返回-1,否则返回子数组长度。

你可能感兴趣的:(leetcode数组篇,leetcode,算法,职场和发展)