数据结构与算法--子数组和为0

本文探讨了解决子数组和为0的问题,提供两种方法:哈希表法和排序法。哈希表法通过存储累积和及其索引来快速定位到和为0的子数组,而排序法则通过对累积和进行排序来寻找重复的累积和值。

问题:子数组和为0

Given an integer array, find a subarray where the sum of numbers is zero.
Your code should return the index of the first number and the index of the last number.

Example
Given [-3, 1, 2, -3, 4], return [0, 2] or [1, 3].

Note
There is at least one subarray that it's sum equals to zero.

解题思路:

题目中的对象是分析子串和,那么我们先从常见的对数组求和出发,f(i)=∑​0​i​​nums[i] 表示从数组下标 0 开始至下标 i 的和。子串和为0,也就意味着存在不同的 i​1​​ 和 i​2​​ 使得 f(i​1​​)−f(i​2​​)=0, 等价于 f(i​1​​)=f(i​2​​). 思路很快就明晰了,使用一 vector 保存数组中从 0 开始到索引i的和,在将值 push 进 vector 之前先检查 vector 中是否已经存在,若存在则将相应索引加入最终结果并返回。

解法一:哈希表

class Solution {
public:
    /**
     * @param nums: A list of integers
     * @return: A list of integers includes the index of the          first number
     *          and the index of the last number
     */
    vector subarraySum(vector nums){
        vector result;
        // curr_sum for the first item, index for the second item
        map hash;
        hash[0] = 0;

        int curr_sum = 0;
        for (int i = 0; i != nums.size(); ++i) {
            curr_sum += nums[i];
            if (hash.find(curr_sum) != hash.end()) {
                result.push_back(hash[curr_sum]);
                result.push_back(i);
                return result;
            } else {
                hash[curr_sum] = i + 1;
            }
        }

        return result;
    }
};

源码分析

为了将curr_sum == 0的情况也考虑在内,初始化哈希表后即赋予 <0, 0>. 给 hash赋值时使用i + 1, push_back时则不必再加1.

由于 C++ 中的map采用红黑树实现,故其并非真正的「哈希表」,C++ 11中引入的unordered_map用作哈希表效率更高,实测可由1300ms 降至1000ms.

复杂度分析

遍历求和时间复杂度为 O(n), 哈希表检查键值时间复杂度为 O(logL), 其中 L 为哈希表长度。如果采用unordered_map实现,最坏情况下查找的时间复杂度为线性,最好为常数级别。

解法二:排序

除了使用哈希表,我们还可使用排序的方法找到两个子串和相等的情况。这种方法的时间复杂度主要集中在排序方法的实现。由于除了记录子串和之外还需记录索引,故引入pair记录索引,最后排序时先按照sum值来排序,然后再按照索引值排序。如果需要自定义排序规则可参考sort_pair_second.

class Solution {
public:
    /**
     * @param nums: A list of integers
     * @return: A list of integers includes the index of the first number
     *          and the index of the last number
     */
    vector subarraySum(vector nums){
        vector result;
        if (nums.empty()) {
            return result;
        }

        const int num_size = nums.size();
        vector <pair<int,int>> sum_index(num_size + 1);
        for (int i = 0; i != num_size; ++i) {
            sum_index[i + 1].first = sum_index[i].first + nums[i];
            sum_index[i + 1].second = i + 1;
        }

        sort(sum_index.begin(), sum_index.end());
        for (int i = 1; i < num_size + 1; ++i) {
            if (sum_index[i].first == sum_index[i - 1].first) {
                result.push_back(sum_index[i - 1].second);
                result.push_back(sum_index[i].second - 1);
                return result;
            }
        }

        return result;
    }
};

源码分析

没啥好分析的,注意好边界条件即可。这里采用了链表中常用的「dummy」节点方法,pair排序后即为我们需要的排序结果。这种排序的方法需要先求得所有子串和 然后再排序,最后还需要遍历排序后的数组,效率自然是比不上哈希表。但是在某些情况下这种方法有一定优势。

复杂度分析

遍历求子串和,时间复杂度为 O(n), 空间复杂度 O(n). 排序时间复杂度近似 O(nlogn), 遍历一次最坏情况下时间复杂度为 O(n). 总的时间复杂度可近似为 O(nlogn). 空间复杂度 O(n).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值