leetcode 前缀和[&哈希表优化]

前缀和(Prefix Sum)定义:前缀和是一种重要的预处理,能大大降低查询的时间复杂度。结合Hash缓存,能够进一步优化提升算法执行效率。

对数组nums进行前缀和初始化需要O(n)时间。

新建数组prefixSum,数组长度定义为nums.length+1,确保第nums.length个元素存储了前面0到nums.length-1个元素的和。将数组nums的累加依次放入数组prefixSum中

prefixSum [0] = 0 【备注:此处不要定义prefix[0]=nums[0],这种定义违背前缀和的意义】
prefixSum [1] = prefixSum[0] + nums[0] = 0 + num[0]

prefixSum [i] = prefixSum [i-1] + nums [i]。

由此推导出两个变换公式:
(1)nums[某一项] = 两个相邻 前缀和 之差:
nums[x] = prefixSum[x] - prefixSum[x - 1]
(2)从left到right的元素和等于prefixSum[right+1] –prefixSum[left];

前缀和的应用场景是,需要对某个区间[i…j]频繁查询累计和,避免每次查询都遍历这个区间。

leetcode560. 和为K的子数组

1. 题目

给定一个整数数组和一个整数k,你需要找到该数组中和为k的连续的子数组的个数。

示例 1 :

输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

说明 :
数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。

2. 解答

/*
易错点:
1.每次循环前应该是先搜索再存sum,否则当k = 0时,将搜索到当前位置
2.循环开始前应该先存个0进去。因为当前n个元素的和sum = k时,其中有一个结果应该是这n个元素本身
*/

typedef struct {
    int preSum;      /* we'll use this field as the key */
    int count;
    UT_hash_handle hh;    /* makes this structure hashable */
}hash_node;

int subarraySum(int* nums, int numsSize, int k) {
    int sum = 0;
    int needPresum;
    int count = 0;

    hash_node *g_users = NULL;
    hash_node *add_user = (hash_node *)malloc(sizeof(hash_node));
    add_user->preSum = 0;
    add_user->count = 1;

    HASH_ADD_INT(g_users, preSum, add_user);

    hash_node *find_user = NULL;
    for (int i = 0; i < numsSize; i++) {
        sum += nums[i];
        needPresum = sum - k;
        HASH_FIND_INT(g_users, &needPresum, find_user);
        if (find_user != NULL) {
            count += find_user->count;
        }

        HASH_FIND_INT(g_users, &sum, find_user);
        if (find_user == NULL) {
            hash_node *new_user = (hash_node *)malloc(sizeof(hash_node));
            new_user->preSum = sum;
            new_user->count = 1;
            HASH_ADD_INT(g_users, preSum, new_user);
        } else {
            find_user->count++;
        }
    }
    return count;
}

leetcode523. 连续的子数组和

1. 题目

给定一个包含 非负数 的数组和一个目标 整数 k ,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,且总和为 k 的倍数,即总和为 n * k ,其中 n 也是一个整数。

示例 1:
输入:[23,2,4,6,7], k = 6
输出:True
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6。

示例 2:
输入:[23,2,6,4,7], k = 6
输出:True
解释:[23,2,6,4,7]是大小为 5 的子数组,并且和为 42。

2. 解答

/*
我们假设preSum[i]表示nums[0] + ... + nums[i]的和,那么当preSum[j] - preSum[i]刚好为n * k,即k的倍数时,preSum[i] % k == preSum[j] % k这个关系表达式应该成立;或者可以看做preSum[j] = preSum[i] + n * k;
那我们以preSum[i] % k作为哈希索引的key值,i索引作为哈希表的value值;就可以在索引到相同key值且索引相差至少为2的情况下,返回true
*/

struct UtHashStr {
    int preSum;         /* key */
    int num;        /* value */    // 由于返回值不是统计个数,统计的是长度大于等于2的数组是否存在等于K,此处表示数组下标的索引值
    UT_hash_handle hh;      /* makes this structure hashable! */
};
bool checkSubarraySum(int* nums, int numsSize, int k) {
    struct UtHashStr *hash = NULL;  /* 就理解为头节点吧 */
    struct UtHashStr *s = (struct UtHashStr*)malloc(sizeof(struct UtHashStr));
    s->preSum = 0;
    s->num = -1;
    /* 这里将[0, -1]键值对插入,为什么0对应的是-1呢?因为0对应的实际上是总和刚好为k倍数的值,而题目要求连续子数组大小至少为2,所以这里初始化为-1 */
    HASH_ADD_INT(hash, preSum, s);
    int sum = 0;
    for (int i = 0; i < numsSize; i++) {
        sum += nums[i];
        if (k != 0) {
            sum = sum % k;
        }
        struct UtHashStr *tmp;
        HASH_FIND_INT(hash, &sum, tmp); /* 如果两数对k取余数相等,那么这两数相差为k的倍数,所以看对k取余数的哈希值,如果相等,则判断两者索引相差 */
        if (tmp == NULL) {
            tmp = (struct UtHashStr*)malloc(sizeof(struct UtHashStr));
            tmp->preSum = sum;
            tmp->num = i;
            HASH_ADD_INT(hash, preSum, tmp);
        } else {
            if (i - (tmp->num) > 1) {   /* 如果两数对k取余数哈希值相等,且索引相差至少为2(hash[0] = -1初始化),则存在连续子数组并返回true */
                return true;
            }
        }
    }
    return false;
}

leetcode974. 和可被K整除的子数组

1. 题目

给定一个整数数组 A,返回其中元素之和可被 K 整除的(连续、非空)子数组的数目。

示例:
输入:A = [4,5,0,-2,-3,1], K = 5
输出:7
解释:
有 7 个子数组满足其元素之和可被 K = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]

2. 解答

/*
以preSum[i]表示nums[0]+...+nums[i]这一段的前缀和,那如果preSum[i] % k == m; preSum[j] % k == m; 那nums[i + 1]到nums[j]的和可以被k整除,这一段是符合题目要求的;
如果在首次遇见preSum[i] % k == m时,将其放入哈希表,以m余数为key值,并将其value值初始化为1;
则当第二次遇见preSum[j] % k == m时(j > i),搜索哈希表发现key为m的已经存在,那nums[i + 1]到nums[j]这一段就是余数为m为起点的第一段符合题目意思的连续子数组,这时我们更新result += key(m).value,key == m的value值更新为 1 + 1 = 2;
我们继续看,当第三次遇见preSum[p] % k == m时(p > j), 搜索哈希发现key为md的节点已经存在,那nums[j + 1] -> nums[k]这一段就是余数为m为起点的“第二段”符合题目意思的连续子数组,但是这时候我们发现其实nums[i + 1] -> nums[k]这一段也符合题意,所以我们继续更新result += key(m).value, key == m的value值同步更新为 2 + 1 = 3;以此类推。。。
所以,相同余数(key)的哈希值,每多一个段,这一段可以与之前存在的段都组成新的子数组段,所以result += key(m).value
*/
//注意:此题是整数,66是非负整数,索引值preSum[i] % k即可,但此题要区分preSum[i]是正数还是负数,索引值应为索引值(preSum[i] % k + k) % k

typedef struct hash_node {
    int preSum;
    int count;
    UT_hash_handle hh;
} UTHash;

int subarraysDivByK(int* A, int ASize, int K) {
    int sum = 0;
    int count = 0;
    UTHash *g_hash_node = NULL;
    UTHash *cur_node = (UTHash *)malloc(sizeof(UTHash));
    cur_node->preSum = 0;
    cur_node->count = 1;
    HASH_ADD_INT(g_hash_node, preSum, cur_node);

    for (int i = 0; i < ASize; i++) {
        sum += A[i];
        sum = (sum % K + K) % K;

        UTHash *find_node = NULL;
        HASH_FIND_INT(g_hash_node, &sum, find_node);
        if (find_node == NULL) {
            UTHash *cur_node = (UTHash *)malloc(sizeof(UTHash));
            cur_node->preSum = sum;
            cur_node->count = 1;
            HASH_ADD_INT(g_hash_node, preSum, cur_node);
        } else {
            count += find_node->count;
            find_node->count++;
        }
    }
    return count;
}

leetcode525. 连续数组

1. 题目

给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

示例 1:
输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。

示例 2:
输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。

提示:
1 <= nums.length <= 105
nums[i] 不是 0 就是 1

2. 解答

// 前缀和思想
// 1. 将数组中的0替换为-1, 子数组0/1个数相同转为子数组求和为0;
// 2. 利用前缀和和hash表实现, 如果preSum[i] == preSum[j], 则说明子数组和[i + 1, j]为0

typedef struct {
    int key;
    int value;
    UT_hash_handle hh;
} NumHash;

#define MAX(a, b) (((a) > (b)) ? (a) : (b))

int findMaxLength(int* nums, int numsSize) {
    int sum = 0;
    int res = 0;

    NumHash *g_num = NULL;
    NumHash *cur = (NumHash *)malloc(sizeof(NumHash));
    cur->key = 0;
    cur->value = -1;
    HASH_ADD_INT(g_num, key, cur);

    for (int i = 0; i < numsSize; i++) {
        sum += nums[i] ? 1 : -1;
        NumHash *find = NULL;
        HASH_FIND_INT(g_num, &sum, find);
        if (find != NULL) {
            res = MAX(res, i - find->value);
        } else {
            NumHash *temp = (NumHash *)malloc(sizeof(NumHash));
            temp->key = sum;
            temp->value = i;
            HASH_ADD_INT(g_num, key, temp);
        }
    }
    return res;
}

剑指 Offer II 011. 0 和 1 个数相同的子数组

1. 题目

给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

示例 1:
输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。

示例 2:
输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量 0 和 1 的最长连续子数组。

提示:
1 <= nums.length <= 105
nums[i] 不是 0 就是 1

2. 解答

typedef struct {
    int key;
    int value;
    UT_hash_handle hh;
} hash_node;

int findMaxLength(int* nums, int numsSize) {
    if (nums == NULL || numsSize <= 0) {
        return 0;
    }
    int res = 0;
    hash_node *g_user = NULL;
    int sum = 0;

    for (int i = 0; i < numsSize; i++) {
        sum += nums[i] > 0 ? 1 : -1;
        if (sum == 0) {
            res = i + 1;
        }
        hash_node *find = NULL;
        HASH_FIND_INT(g_user, &sum, find);
        if (!find) {
            find = (hash_node *)malloc(sizeof(hash_node));
            find->key = sum;
            find->value = i;
            HASH_ADD_INT(g_user, key, find);
        } else {
            res = fmax(res, i - find->value);
        }
    }
    
    return res;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值