前端刷题路-Day33:最大子序和(题号53)&笨阶乘(题号1006)

226 阅读4分钟

最大子序和(题号53)

题目

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入:nums = [1]
输出:1

示例 3:

输入:nums = [0]
输出:0

示例 4:

输入:nums = [-1]
输出:-1

示例 5:

输入:nums = [-100000]
输出:-100000

提示:

  • 1 <= nums.length <= 3 * 104
  • -105 <= nums[i] <= 105

进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

链接

leetcode-cn.com/problems/ma…

解释

这题确实是个简单题,用DP可以很轻松的做出来,难就难在进阶里面的分治法。

关于分治这里不多做赘述,官方说的已经很清楚了,可以点击这里查看详情,并且这里的题解也包含JavaScript,非常方便,想了解分治的同学可以好好看看。

自己的答案(DFS)

var maxSubArray = function(nums) {
  var max = Number.MIN_SAFE_INTEGER
  function dfs(i, sum) {
    for (let j = i; j < nums.length; j++){
      sum += nums[j]
      max = Math.max(max, sum)
      if (j > i) {
        dfs(j, 0)
      }
    }
  }
  dfs(0, 0)
  return max
};

很简单的一个DFS,结果也很简单,在执行第198个用例的时候超时了。

自己的答案(DP+数组)

var maxSubArray = function(nums) {
  var len = nums.length
      max = nums[0]
      arr = new Array(len)
  arr[0] = nums[0]
  for (let i = 1; i < nums.length; i++) {
    arr[i] = Math.max(arr[i - 1] + nums[i], nums[i])
    max = Math.max(max, arrHas[i])
  }
  return max
};

DP数组的解法也很简单,主要的DP公式就是前一个DP最大值加上当前元素,和当前元素进行比较。

取两者的最大值,之后每次循环都需要和max做一次对比,以防在途中遇到最大值遗忘了。

自己的答案(DP+降维)

和之前的许多DP一样,这个DP问题也可以降低空间复杂度,从O(n)降低到O(1)

var maxSubArray = function(nums) {
  var max = Number.MIN_SAFE_INTEGER
      prv = max
  for (const num of nums) {
    prv = Math.max(prv + num, num)
    max = Math.max(max, prv)
  }
  return max
};

简单方便,越看越喜欢,哈哈哈😂

更好的方法(DP+判断)

这个答案就让我很懵了,一开始看到这个答案以为是贪心,结果发现他其实就是某种意义上的DP,并不是贪心,话不多说,先看代码👇(原答案链接):

var maxSubArray = function(nums) {
    let ans = nums[0];
    let sum = 0;
    for(let num of nums) {
        // if(sum > 0) { 可以写成这样
        if(sum + num > num ){
            sum = sum + num;
        } else {
            sum = num;
        }
        ans = Math.max(ans, sum);
    };
    return ans;
};

看起来是不是有点奇妙,尤其是这个大小的比较,很像贪心啊,其实这里处理sum还真就是DP,类似于👆降维后的DP,不过我利用的是Math.max,他这里用的大小的比较。

这里的判断逻辑也很简单,如果前面的sum小于0,那

sum + num > num

必然是false,所以直接将num赋值给sum即可,如果sum大于0,那就在sum的基础上再加上当前元素,因为必然会更大。

最后也是和我一样,每次获取到sum之后都和max进行一下对比,保证不会遗漏到中途的某个数值。

不得不说这个老哥的清奇的思路,佩服佩服。

更好的方法(分治)

来了来了,这题的重头戏来了——分治。

其实这题用分治来解决并不是最佳解决方案,因为其时间复杂度比DP要高,虽然空间复杂度都是一样的,但变量多了不少,还需要多生成几个方法,占用了更多的内存。

具体的代码这里也不放了,笔者自己盲写了5遍才将将记住,里面的逻辑着实是有点麻烦,不过看懂了也还好,就看能不能记住了。

具体的还是看官方解答吧👉:最大子序和的第二种解法

说的真的很好,而且很详细。

笨阶乘(题号1006)

题目

通常,正整数 n 的阶乘是所有小于或等于 n 的正整数的乘积。例如,factorial(10) = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1

相反,我们设计了一个笨阶乘 clumsy:在整数的递减序列中,我们以一个固定顺序的操作符序列来依次替换原有的乘法操作符:乘法(*),除法(/),加法(+)和减法(-)。

例如,clumsy(10) = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1。然而,这些运算仍然使用通常的算术运算顺序:我们在任何加、减步骤之前执行所有的乘法和除法步骤,并且按从左到右处理乘法和除法步骤。

另外,我们使用的除法是地板除法(floor division),所以 10 * 9 / 8 等于 11。这保证结果是一个整数。

实现上面定义的笨函数:给定一个整数 N,它返回 N 的笨阶乘。

示例 1:

输入:4
输出:7
解释:7 = 4 * 3 / 2 + 1

示例 2:

输入:10
输出:12
解释:12 = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1

提示:

  • 1 <= N <= 10000
  • -2^31 <= answer <= 2^31 - 1 (答案保证符合 32 位整数。)

链接

leetcode-cn.com/problems/cl…

解释

这题的题目并不复杂,重点是四则运算的优先级问题。

官方给的答案是,在JavaScript中就是数组,利用数组的pop方法取出最后一个元素,之后对其进行四则运算。如果是乘法或者除法就取出元素,运算完成后再插入;如果是加法或者减法,则直接推入元素,减法推入反转之后的元素。

还有一种不常见的方法就是纯数学方法解决,这感觉没啥可说的,就自己的推导出一个数学公式出来即可。

笔者这里给出的答案其实有点类似于官方栈操作,当并没有用具体的栈,而是用两层循环,具体可以看👇的代码。

自己的答案(双层循环)

var clumsy = function (N) {
  var res = null
      X = Math.ceil(N / 4)
  for (let x = 0; x < X; x++) {
    var tmp = 1
    for (let i = 0; i < 4; i++) {
      var localNum = N - (x * 4 + i)
      if (localNum === 0) {
        res = res === null ? tmp : res -= tmp
        break
      }
      switch (i) {
        case 0:
          tmp = localNum
          break;
        case 1:
          tmp = tmp * localNum
          break;
        case 2:
          tmp = ~~(tmp / localNum)
          break;
        default:
          res = res === null ? tmp : res - tmp
          res += localNum
          break;
      }
    }
  }
  return res
};

由于四则运算的优先级规律,这里笔者将数组拆分的结果分成四个一组,使用X代表组的个数,如此判断总共有多少个四则运算,之后将每组四则运算的结果放到一块,变成一个数组,最后在所有的数字之间使用减号连接即可。

拿10举个例子,这里将数字分成了:

  • 10 * 9 / 8 + 7
  • 6 * 5 / 4 + 3
  • 2 * 1

之后相加这些数字即可,相对于栈来说还是有点冗余的,判断条件有些略多,尤其是在最后一个X长度不足4的情况下需要再进行一次判断。

时间复杂度还是O(n),因为Xi的个数还是N个,空间复杂一如既往是O(1)

更好的方法(栈)

var clumsy = function (N) {
  var stack = [N--]
  index = 0
  while (N > 0) {
    var activeIndex = index % 4
    switch (activeIndex) {
      case 0:
        stack.push(stack.pop() * N)
        break;
      case 1:
        stack.push(~~(stack.pop() / N))
        break;
      case 2:
        stack.push(N)
        break;
      default:
        stack.push(-N)
        break;
    }
    N--
    index++
  }
  return stack.reduce((item, total) => {
    return item + total
  }, 0)
}

这里稍微改造了一下官方的答案,比方说用switch...case代替了if...else,还有就是使用~~代替了Math.floorMath.ceil

主要逻辑也比较简单,如果是乘法和除法就取出栈末尾的数据,进行运算后再推入到栈的末尾,如果是加法直接推入栈的末尾,如果是减法就在数组前加一个符号推入栈。

在遍历完成后将栈里面所有的数字相加,返回和即可。

更好的方法(数学)

数学这块就不多说了,官方答案已经说的很清楚了,不多赘述。



PS:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)

有兴趣的也可以看看我的个人主页👇

Here is RZ