Hash哈希

文章讲述了在处理数据查询时,如何通过创建哈希集合和维护前缀和来解决LC2036最大子数组和问题,避免内存溢出和时间复杂度。作者提供了两种改进的解决方案,一种利用哈希表存储每个数字对应的小前缀和,另一种简化为滚动前缀和计算。
摘要由CSDN通过智能技术生成

当需要快速查询数据的记录时:

  1. 手动按某规则为数据建立索引,利用数组维护数据
  2. 若1中需要开大数组爆内存,利用官方库的Hash集合,可以将最好情况下的时间复杂度优化到O(1)避免T和M。

1. LC 2036 最大好子数组和

VP双周赛123T3。一开始卡住了。一直想在Hash表里存每种数字出现的索引,查询nums[i]±k的所有可能索引,前缀和相减得到子数组和维护最大值。最后不出意外的T了。既然要找之前的最小前缀和,为啥不在Hash表里直接维护每个数字对应的最小前缀和呢?改完A了。

import java.util.HashMap;

class Solution {
    public long maximumSubarraySum(int[] nums, int k) {
        HashMap<Integer, Long> m = new HashMap<>();

        int n = nums.length;
        long[] prefix = new long[n+1];

        long ans = Long.MIN_VALUE;
        long sum = 0;

        for (int i = 0; i < n; i++) {
            sum += nums[i];
            prefix[i+1] = sum;

            Long nk = m.get(nums[i] - k);
            Long pk = m.get(nums[i] + k);
            
            if(nk!=null){
                ans = Math.max(ans,sum-nk);
            }
            
            if(pk!=null){
                ans = Math.max(ans,sum-pk);
            }

            Long pre = m.get(nums[i]);
            if(pre==null){
                m.put(nums[i],prefix[i]);
            }else{
                m.put(nums[i],Math.min(prefix[i],pre));
            }
        }
        
        return ans==Long.MIN_VALUE?0:ans;
    }
}

注意子数组是闭区间,前缀和数组里加个哨兵即可。然后这道题实际上不需要前缀和数组,滚动一个前缀和就可以了,闭区间滞后一个元素即可。

import java.util.HashMap;

class Solution {
    public long maximumSubarraySum(int[] nums, int k) {
        HashMap<Integer, Long> m = new HashMap<>();

        int n = nums.length;

        long ans = Long.MIN_VALUE;
        long sum = 0;

        for (int num : nums) {

            Long nk = m.get(num - k);
            Long pk = m.get(num + k);

            if (nk != null) {
                ans = Math.max(ans, sum + num - nk);
            }

            if (pk != null) {
                ans = Math.max(ans, sum + num - pk);
            }

            Long pre = m.get(num);
            if (pre == null) {
                m.put(num, sum);
            } else {
                m.put(num, Math.min(sum, pre));
            }

            sum += num;
        }

        return ans==Long.MIN_VALUE?0:ans;
    }
}

2. LC 3164 优质数对的总数Ⅱ

  1. 先筛选nums1中能被k整除的数,把x//k加入到数组中
  2. 哈希表m再统计nums2各个数字出现的频率
  3. 对于通过筛选的nums1中出现的数v,枚举[1:sqrt(v)],查看是否是因子d
    1. 不是因子跳过
    2. 是因子
      1. 是平方根,加 m[d]
      2. 不是平方根,加 m[d] + m[v//d]
from typing import List
from collections import defaultdict
import math

class Solution:
    def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:
        arr = []

        for x in nums1:
            if x%k==0:
                arr.append(x//k)

        m = defaultdict(int)

        for x in nums2:
            m[x] += 1
        
        res = 0
        for x in arr:
            for i in range(1,int(math.sqrt(x))+1):
                if x%i == 0:
                    res += m[i] + (m[x//i] if i*i!=x else 0)
        
        return res

3. LC 100358 找出有效子序列的最大长度Ⅱ

VP 周赛404 T3。写了个哈希表假解过了。

首先可以证明,对于一个合法子序列seq,seq[i]%k = seq[i+2]%k。

证明如下:

对于任意一个子序列中的相邻三元组(a,b,c),设其对k的余数为(p,q,r),下面证明p=r。

根据模数性质有:

(a+b)\ mod\ k = (a\ mod \ k \ + b\ mod\ k)\ mod \ k

因此根据题意可得:

( a\ +\ b)\ mod\ k = (b \ +\ c)\ mod\ k \Leftrightarrow (p+q)\ mod\ k = (q+r)\ mod\ k

第一种情况:

(p+q) = (q+r)

则p=r,得证。

第二种情况,采用反证法,假设p≠r。且有:

(p+q) \neq (q+r)

则因为:

\left\{ \begin{aligned} 0≤p<k \\ 0≤q<k \\ 0≤r<k \end{aligned} \right.

那么:

\left\{ \begin{aligned} 0≤p+q<2k \\ 0≤q+r<2k \end{aligned} \right.

不妨设:

\left\{ \begin{aligned} p+q = 0+d \\ q+r = k+d \end{aligned} \right.\\或\\\left\{ \begin{aligned} p+q = k+d \\ q+r = 0+d \end{aligned} \right.

其中:

0\leq d<k

则有:

| r-p | = k

这与r-p<k矛盾。

因此p=r,得证。

综上,结论中的性质成立。

实现上:

  1. 首先看出来相隔元素对k的余数相等
  2. 然后根据余数分组
  3. 然后两两匹配,构造最长子序列 优化的点:
  4. 自己和自己匹配就是当前组的长度 O(k)遍历一下即可
  5. 不同余数的组匹配,直接把第一个元素索引更小的组当作开始的组,否则结果必然不会更优
from collections import defaultdict
from typing import List
class Solution:
    def maximumLength(self, nums: List[int], k: int) -> int:
        ans = 2
        m = defaultdict(list)

        for i,x in enumerate(nums):
            m[x%k].append(i)
        
        for v in m.values():
            ans = max(ans,len(v))

        partitions = list(m.keys())

        for i in range(len(partitions)):
            for j in range(i+1,len(partitions)):
                l1 = m[partitions[i]]
                l2 = m[partitions[j]]

                if l1[0] > l2[0]:
                    l1,l2 = l2,l1
                
                p1,p2 = 0,0
                stk = [l1[p1]]
                flag = False

                while (p1<len(l1) and flag) or (p2<len(l2) and not flag):
                    if not flag:
                        while p2 < len(l2) and l2[p2] < stk[-1]:
                            p2 += 1
                        if p2 < len(l2):
                            stk.append(l2[p2])
                            flag = not flag
                    else:
                        while p1 < len(l1) and l1[p1] < stk[-1]:
                            p1 += 1
                        if p1 < len(l1):
                            stk.append(l1[p1])
                            flag = not flag
                    
                ans = max(ans,len(stk))

        return ans

O(n*k^2)的做法,分组O(n),自己匹配自己O(k),两两匹配O(n * k^2),明显超时的做法。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值