2021-4

前缀和 & HASH

560. 和为K的子数组(中等)

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

示例 1 :

输入:nums = [1,1,1], k = 2

输出: 2 , [1,1] [1,1] 为两种不同的情况。

说明 :

数组的长度为 [1, 20,000]

数组中元素的范围是 [-1000, 1000] ,且整数 的范围是 [-1e7, 1e7]

方法:前缀和+哈希表

思路和算法

定义 pre[i] 为 [0..i]里所有数的和,则 pre[i] 可以由pre[i−1] 递推而来,即:

pre[i]=pre[i−1]+nums[i]

 

那么 [j..i] 这个子数组和为 k这个条件我们可以转化为

pre[i]−pre[j−1]==k

 

简单移项可得符合条件的下标 j需要满足

pre[j−1]==pre[i]−k

 

考虑以 i 结尾的和为 k的连续子数组个数时只要统计有多少个前缀和为 pre[i]−k 的pre[j] 即可。建立哈希表 mp,以和为键,出现次数为对应的值,记录 pre[i] 出现的次数,从左往右边更新边计算答案,那么以 i 结尾的答案 mp[pre[i]−k] 即可在 O(1) 时间内得到。最后的答案即为所有下标结尾的和为 k 的子数组个数之和。

需要注意的是,从左往右边更新边计算的时候已经保证了mp[pre[i]−k] 里记录的 pre[j] 的下标范围是0≤j≤i 。同时,由于pre[i] 的计算只与前一项的答案有关,因此我们可以不用建立 pre 数组,直接用 pre 变量来记录 pre[i−1] 的答案即可。

 

class Solution:

    def subarraySum(self, nums: List[int], k: int) -> int:

        # pre_times存储前缀和出现的次数

        pre_times = collections.defaultdict(int)

        pre_times[0] = 1 先给一个初始值,代表前缀和为0的出现了一次

        cur_sum = 0  记录当前位置的前缀和

        n, res = len(nums), 0

        for i in range(n):

            cur_sum += nums[i]

            if cur_sum - k in pre_times: 如果当前前缀和减去目标值k所得到的值在字典中出现

                res += pre_times[cur_sum-k]

            pre_times[cur_sum] += 1

        return res

 974. 和可被 K 整除的子数组(中等)

给定一个整数数组 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]

方法1:哈希表+逐一统计

令 P[i] = A[0] + A[1] + ... + A[i]。

那么每个连续子数组的和 sum(i,j) 就可以写成 P[j] - P[i-1](其中0<i<j)的形式。

此时,判断子数组的和能否被 K 整除就等价于判断 (P[j] - P[i-1]) mod K == 0,根据 同余定理,只要 P[j] mod K == P[i-1] mod K,就可以保证上面的等式成立。

因此可以考虑对数组进行遍历,在遍历时统计答案。当遍历到第 i 个元素时,可以维护一个以前缀和模 K的值为键,出现次数为值的哈希表 record,在遍历的同时进行更新。这样在计算以 i 结尾的符合条件的子数组个数时,根据上面的分析,答案即为 [0..i-1] 中前缀和模 K也为 P[i]modK 的位置个数,即 record[P[i]modK]。

需要注意的一个边界条件是,我们需要对哈希表初始化,记录 record[0]=1,这样就考虑了前缀和本身被 K整除的情况。

class Solution:

    def subarraysDivByK(self, A: List[int], K: int) -> int:

        record = {01}

        total, ans = 00

        for elem in A:

            total += elem

            modules = total % K

            same = record.get(modules, 0)

            ans += same

            record[modules] = same + 1

        return ans

方法2:哈希表+单次统计

https://leetcode-cn.com/problems/subarray-sums-divisible-by-k/solution/he-ke-bei-k-zheng-chu-de-zi-shu-zu-by-leetcode-sol/

差分

253(会员)

1109. 航班预订统计

这里有 n 个航班,它们分别从 1 到 n 进行编号。

我们这儿有一份航班预订表,表中第 i 条预订记录 bookings[i] = [i, j, k] 意味着我们在从 i 到 j 的每个航班上预订了 k 个座位。

请你返回一个长度为 n 的数组 answer,按航班编号顺序返回每个航班上预订的座位数。

 

示例:

输入:bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5

输出:[10,55,45,25,25]

提示:

1 <= bookings.length <= 20000

1 <= bookings[i][0] <= bookings[i][1] <= n <= 20000

1 <= bookings[i][2] <= 10000

 

  1. 换一种思路理解题意,将问题转换为:某公交车共有 n 站,第 i 条记录 bookings[i] = [i, j, k] 表示在 i 站上车 k 人,乘坐到 j 站,在 j+1 站下车,需要按照车站顺序返回每一站车上的人数
  2. 根据 1 的思路,定义 counter[] 数组记录每站的人数变化,counter[i] 表示第 i+1 站。遍历 bookings[]bookings[i] = [i, j, k] 表示在 i 站增加 k 人即 counters[i-1] += k,在 j+1 站减少 k 人即 counters[j] -= k
  3. 遍历(整理)counter[] 数组,得到每站总人数: 每站的人数为前一站人数加上当前人数变化 counters[i] += counters[i - 1]

 

class Solution:

    def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]:

        ans = [0 for i in range(n+1)]

        for i, j, k in bookings:

            ans[i-1] += k

            ans[j] -= k

        for s in range(1, n):

            ans[s] += ans[s-1]

        return ans[:n]

1094. 拼车(中等)

假设你是一位顺风车司机,车上最初有 capacity 个空座位可以用来载客。由于道路的限制,车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向,你可以将其想象为一个向量)。

这儿有一份乘客行程计划表 trips[][],其中 trips[i] = [num_passengers, start_location, end_location] 包含了第 i 组乘客的行程信息:

必须接送的乘客数量;

乘客的上车地点;

以及乘客的下车地点。

这些给出的地点位置是从你的 初始 出发位置向前行驶到这些地点所需的距离(它们一定在你的行驶方向上)。

请你根据给出的行程计划表和车子的座位数,来判断你的车是否可以顺利完成接送所有乘客的任务(当且仅当你可以在所有给定的行程中接送所有乘客时,返回 true,否则请返回 false)。

 

示例 1

输入:trips = [[2,1,5],[3,3,7]], capacity = 4

输出:false

示例 2

输入:trips = [[2,1,5],[3,3,7]], capacity = 5

输出:true

示例 3

输入:trips = [[2,1,5],[3,5,7]], capacity = 3

输出:true

示例 4

输入:trips = [[3,2,7],[3,7,9],[8,3,9]], capacity = 11

输出:true

方法:差分

1109. 航班预订统计

class Solution:

    def carPooling(self, trips: List[List[int]], capacity: int) -> bool:

        n = len(trips)

        curAns = [0] * 1001

        for i in range(n):

            curAns[trips[i][1]] += trips[i][0]

            curAns[trips[i][2]] -= trips[i][0]

        for i in range(11001):

            curAns[i] += curAns[i-1]

            if curAns[i] > capacity:

                return False

        return True

 

122. 买卖股票的最佳时机 II(简单)

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

 

示例 1:

输入: [7,1,5,3,6,4]

输出: 7

解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。

     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:

输入: [1,2,3,4,5]

输出: 4

解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。

     注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。

     因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入: [7,6,4,3,1]

输出: 0

解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

 

提示:

1 <= prices.length <= 3 * 10 ^ 4

0 <= prices[i] <= 10 ^ 4

解题思路:

股票买卖策略:

(1)单独交易日:设今天价格 p1、明天价格 p2,则今天买入、明天卖出可赚取金额 p2- p1(负值代表亏损)。

(2) 连续上涨交易日:设此上涨交易日股票价格分别为 p1, p2, ... , pn,则第一天买最后一天卖收益最大,即 pn - p1;等价于每天都买卖,即 pn - p1=(p2 - p1)+(p3 - p2)+...+(pn - pn-1)。

(3)连续下降交易日:则不买卖收益最大,即不会亏钱。

 

算法流程:

遍历整个股票交易日价格列表 price,策略是所有上涨交易日都买卖(赚到所有利润),所有下降交易日都不买卖(永不亏钱)。

设 temp 为第 i-1 日买入与第 i 日卖出赚取的利润,即 temp = prices[i] - prices[i - 1] ;

当该天利润为正 temp > 0,则将利润加入总利润 profit;当利润为 0或为负,则直接跳过;

遍历完成后,返回总利润 profit。

 

复杂度分析:

时间复杂度 O(N):只需遍历一次price;

空间复杂度 O(1):变量使用常数额外空间。

 

class Solution:

    def maxProfit(self, prices: List[int]) -> int:

        profit = 0

        n = len(prices)

        for i in range(1, n):

            temp = prices[i] - prices[i-1]

            if temp > 0:

                profit += temp

        return profit

拓扑排序

字符串

5

227

387. 字符串中的第一个唯一字符(简单)

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

示例:

s = "leetcode"

返回 0

s = "loveleetcode"

返回 2

提示:你可以假定该字符串只包含小写字母。

方法:使用哈希表存储频数

对字符串进行两次遍历:

在第一次遍历时,我们使用哈希映射统计出字符串中每个字符出现的次数。

在第二次遍历时,我们只要遍历到了一个只出现一次的字符,那么就返回它的索引,否则在遍历结束后返回 −1。

class Solution:

    def firstUniqChar(self, s: str) -> int:

        hash = collections.Counter(s)

        for i, ch in enumerate(s):

            if hash[ch] == 1:

                return i

        return -1

451. 根据字符出现频率排序(中等)

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:

输入:

"tree"

输出:

"eert"

 

解释:

'e'出现两次,'r'和't'都只出现一次。

因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。

示例 2:

输入:

"cccaaa"

输出:

"cccaaa"

 

解释:

'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。

注意"cacaca"是不正确的,因为相同的字母必须放在一起。

示例 3:

输入:

"Aabb"

输出:

"bbAa"

 

解释:

此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。

注意'A'和'a'被认为是两种不同的字符。

方法1:collections.Counter

简单介绍一下 Counter。

它是一个用来统计出现次数的类,在 collections 包里。

Counter(s) 就可以返回一个类似字典的结构,键是s中的项,值是这个项出现的次数。

对于字符串,就是每个字符和它出现的次数。

Counter类有一个函数,叫most_common(int n),可以返回最常出现的几项,如果不加参数,就全部返回,相当于按照出现次数从大到小输出。

输出的格式形如 [('s',4),('a',3)]。

即字符s出现4次,a出现3次。

class Solution:

    def frequencySort(self, s: str) -> str:

        return ''.join(i * j for i, j in Counter(s).most_common())

方法2:堆排序

大根堆

class Solution:

    def frequencySort(self, s: str) -> str:

        hash = collections.defaultdict(int)

        for i in s:

            hash[i] += 1

        res = []

        heapq.heapify(res)

        for i in hash:

            for j in range(hash[i]):

                heapq.heappush(res, (-hash[i], i))

        return ''.join([heapq.heappop(res)[1for _ in range(len(s))])     

 

方法3:桶排序

93. 复原IP地址(中等)

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

例如:"0.1.2.201" 和 "192.168.1.1" 是 有效的 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效的 IP 地址。

 

示例 1

输入:s = "25525511135"

输出:["255.255.11.135","255.255.111.35"]

示例 2

输入:s = "0000"

输出:["0.0.0.0"]

示例 3

输入:s = "1111"

输出:["1.1.1.1"]

示例 4

输入:s = "010010"

输出:["0.10.0.10","0.100.1.0"]

示例 5

输入:s = "101023"

输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]

 

提示:

  • 0 <= s.length <= 3000
  • s 仅由数字组成

方法:dfs

由于我们需要找出所有可能复原出的 IP 地址,因此可以考虑使用递归的方法,对所有可能的字符串分隔方式进行搜索,并筛选出满足要求的作为答案。

设题目中给出的字符串为 s。我们用递归函数dfs(segId,segStart) 表示我们正在从s[segStart] 的位置开始,搜索 IP 地址中的第 segId 段,其中segId{0,1,2,3}。由于 IP 地址的每一段必须是 [0,255] 中的整数,因此我们从 segStart 开始,从小到大依次枚举当前这一段 IP 地址的结束位置 segEnd。如果满足要求,就递归地进行下一段搜索,调用递归函数dfs(segId+1,segEnd+1)

特别地,由于 IP 地址的每一段不能有前导零,因此如果 s[segStart] 等于字符 0,那么 IP 地址的第segId 段只能为 0,需要作为特殊情况进行考虑。

在递归搜索的过程中,如果我们已经得到了全部的 4 IP 地址(即segId=4),并且遍历完了整个字符串(即 segStart=s∣,其中 |s| 表示字符串 s 的长度),那么就复原出了一种满足题目要求的 IP 地址,我们将其加入答案。在其它的时刻,如果提前遍历完了整个字符串,那么我们需要结束搜索,回溯到上一步。

class Solution:

    def restoreIpAddresses(self, s: str) -> List[str]:

        SEG_COUNT = 4

        ans = list()

        segments = [0] * SEG_COUNT

        

        def dfs(segId: int, segStart: int):

            如果找到了4IP地址并且遍历完了字符串,那么就是一种答案

            if segId == SEG_COUNT:

                if segStart == len(s):

                    ipAddr = ".".join(str(seg) for seg in segments)

                    ans.append(ipAddr)

                return

            如果还没有找到4IP地址就已经遍历完了字符串,那么提前回溯

            if segStart == len(s):

                return 

            由于不能有前导零,如果当前数字为0,那么这一段IP地址只能为0

            if s[segStart] == "0":

                segments[segId] = 0

                dfs(segId+1, segStart+1)

            一般情况,枚举每一种可能性并递归

            addr = 0

            for segEnd in range(segStart, len(s)):

                addr = addr * 10 + (ord(s[segEnd]) - ord("0"))

                if 0 < addr <= 0xFF:

                    segments[segId] = addr

                    dfs(segId+1, segEnd+1)

                else:

                    break

        dfs(00)

        return ans

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值