题目难度: 中等
原题链接
今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复
剑指offer2
就能看到该系列当前连载的所有文章了, 记得关注哦~
题目描述
给你一个整数数组 nums ,请计算数组的 中心下标 。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。
示例 1:
- 输入:nums = [1,7,3,6,5,6]
- 输出:3
- 解释:
- 中心下标是 3 。
- 左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
- 右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。
示例 2:
- 输入:nums = [1, 2, 3]
- 输出:-1
- 解释:数组中不存在满足此条件的中心下标。
示例 3:
- 输入:nums = [2, 1, -1]
- 输出:0
- 解释:
- 中心下标是 0 。
- 左侧数之和 sum = 0 ,(下标 0 左侧不存在元素),
- 右侧数之和 sum = nums[1] + nums[2] = 1 + -1 = 0 。
提示:
- 1 <= nums.length <= 10^4
- -1000 <= nums[i] <= 1000
题目思考
- 如何优化时间和空间复杂度?
解决方案
思路
- 分析题目, 最容易想到的做法就是两重循环: 外层循环固定每个下标, 然后从它的左右下标开始两个内层循环, 分别求出左右两侧的和, 相等的话作为最终结果
- 但这种做法时间效率太低 (
O(N^2)
), 如何优化呢? - 重新分析题目, 左侧所有元素相加的和显然就是前缀和, 而右侧所有元素相加的和自然是后缀和
- 我们可以利用这一点, 先正向遍历, 得出每个下标对应的前缀和, 再反向遍历一遍, 累加当前的后缀和, 如果某下标的后缀和等于其前缀和, 就说明找到一个有效下标, 继续遍历直到找到最左有效下标为止
- 这样我们就把时间复杂度优化到了
O(N)
- 另外由于题目要求最左有效下标, 我们还可以调换两次遍历的顺序, 这样第二次正向遍历时, 得到的第一个有效下标即为最终结果, 进一步优化了时间
- 这还没完, 我们可以进一步压缩成一次正向遍历搞定, 也不需要前缀和或后缀和数组
- 具体做法是初始化前缀和为 0, 后缀和为整个数组的和
- 然后正向遍历数组, 先将后缀和减去当前下标 i 的值, 此时前缀和就是[0,i)子数组的和, 而后缀和是[i+1,n)子数组的和, 即 i 的左侧和右侧元素的和
- 此时如果前缀和以及后缀和相等, 则说明找到最左有效下标, 否则前缀和加上当前元素的值继续遍历
- 这样进一步将空间复杂度优化到了
O(1)
- 下面代码中对上述每个步骤都有详细注释, 方便大家理解
复杂度
- 时间复杂度
O(N)
: 只需要遍历数组一遍 - 空间复杂度
O(1)
: 只使用了几个常数空间的变量
代码
class Solution:
def pivotIndex(self, nums: List[int]) -> int:
# 单次正向遍历+前/后缀和
lsm = 0
rsm = sum(nums)
for i, x in enumerate(nums):
# 后缀和减去当前元素
rsm -= x
if lsm == rsm:
# [0,i)前缀和==[i+1,n)后缀和
# 且当前是最靠近左边的下标, 返回它
return i
# 前缀和加上当前元素
lsm += x
# 没找到符合条件的下标, 返回-1
return -1
大家可以在下面这些地方找到我~😊
我的 GitHub
我的 Leetcode
我的 CSDN
我的知乎专栏
我的头条号
我的牛客网博客
我的公众号: 算法精选, 欢迎大家扫码关注~😊