Go 算法专项之数组(二)

141 阅读4分钟

续:Go 算法专项之数组(一)

累加数组数字

560. 和为 K 的子数组

题目大意:输入一个整数数组和一个整数k,请问数组中有多少个数字之和等于k的连续子数组?
例如,输入数组[1,1,1],k的值为2,有2个连续子数组之和等于2。

分析: 方法一:枚举所有子数组,时间复杂度为 O(n2)。

方法二:

遍历数组,保存从 num0 开始的所有连续子数组的和:

  • sum0 = [num0]
  • sum1 = [num0, num1]
  • sum2 = [num0, num1, num2]
  • ...
  • sumn-1 = [num0, num1, ..., numn-1]

判断两种情况:

  1. 是否存在 sumi = k。
  2. 假设存在 sumi - sumj = k (i > j),则子数组 [numj+1, ..., numi] 的和等于 k。

此方法时间复杂度为 O(n),空间复杂度为 O(n)。参考代码如下:

func subarraySum(nums []int, k int) int {
   cnt := make(map[int]int)
   count := 0
   sum := 0

   for _, num := range nums {
      sum += num
      if sum == k {
         count++
      }
      count += cnt[sum - k]
      cnt[sum]++
   }
   return count
}

525. 连续数组

题目大意:输入一个只包含0和1的数组,请问如何求0和1的个数相同的最长连续子数组的长度?
例如,在数组[0,1,0]中有两个子数组包含相同个数的0和1,分别是[0,1]和[1,0],它们的长度都是2,因此输出2。

分析: 和上一道题相似的解题思路,把 0 当作 -1。对于相同的 sum,哈希表只保存 i 最小的那个,这样两个连续子数组的之间相差的数字个数就能最多。参考代码如下:

func findMaxLength(nums []int) int {
   cnt := make(map[int]int)
   maxLen := 0
   sum := 0
   for i := 0; i < len(nums); i++ {
      if nums[i] == 0 {
         sum--
      } else {
         sum++
      }
      if sum == 0 {
         maxLen = max(i + 1, maxLen)
      }

      if v, ok := cnt[sum]; ok {
         maxLen = max(i - v, maxLen)
      } else {
         cnt[sum] = i
      }
   }
   return maxLen
}

func max(a, b int) int {
   if a > b {
      return a
   }
   return b
}

724. 寻找数组的中心下标

题目大意:输入一个整数数组,如果一个数字左边的子数组的数字之和等于右边的子数组的数字之和,那么返回该数字的下标。如果存在多个这样的数字,则返回最左边一个数字的下标。如果不存在这样的数字,则返回-1。
例如,在数组[1,7,3,6,2,9]中,下标为3的数字(值为6)的左边3个数字1、7、3的和与右边两个数字2和9的和相等,都是11,因此正确的输出值是3。

分析: 先计算数组总和,然后再从左到右遍历数组,比较左子数组(num0到numi-1)和右子数组(numi+1到numn-1)即可。时间复杂度为O(n)。

func pivotIndex(nums []int) int {
   left := 0
   right := 0
   for i := 0; i < len(nums); i++ {
      right += nums[i]
   }

   for i := 0; i < len(nums); i++ {
      right -= nums[i]
      if left == right {
         return i
      }
      left += nums[i]
   }
   return -1
}

304. 二维区域和检索 - 矩阵不可变

题目大意:输入一个二维矩阵,如何计算给定左上角坐标和右下角坐标的子矩阵的数字之和?对于同一个二维矩阵,计算子矩阵的数字之和的函数可能由于输入不同的坐标而被反复调用多次。
例如,输入图中的二维矩阵,以及左上角坐标为(2,1)和右下角坐标为(4,3)的子矩阵,该函数输出8。

epub_40107878_30.jpeg

分析: 如果直接计算目标区域的和,那么每次计算的时间复杂度为 O(nm)。这道题主要是调用计算目标区域和的次数很多,需要优化计算性能。

假设由上角到坐标点(r,c)的和记作 sumr,c
则目标区域(r1,c1,r2,c2)的和为:sumr2,c2 - sumr1-1,c2 - sumr2,c1-1 + sumr1-1,c1-1

这样我们每次计算目标区域的和的时间复杂度为 O(1),需要 O(nm) 的辅助空间。为了避免 r1-1 小于 0 的情况,我们可以增加一个值为0的首行和首列。参考代码如下:

type NumMatrix struct {
   sum [][]int
}

func Constructor(matrix [][]int) NumMatrix {
   sum := make([][]int, len(matrix)+1)
   sum[0] = make([]int, len(matrix[0])+1)
   for i := 1; i < len(sum); i++ {
      row := make([]int, len(matrix[0])+1)
      rowSum := 0
      for j := 1; j < len(row); j++ {
         rowSum += matrix[i-1][j-1]
         row[j] = rowSum + sum[i-1][j]
      }
      sum[i] = row
   }
   return NumMatrix{
      sum: sum,
   }
}

func (this *NumMatrix) SumRegion(row1 int, col1 int, row2 int, col2 int) int {
   return this.sum[row2+1][col2+1] - this.sum[row1][col2+1] - this.sum[row2+1][col1] + this.sum[row1][col1]
}

参考