力扣每日一题(三)划分题 + 思路题

目录

[1. 划分题&记忆化搜索 @cache](#1. 划分题&记忆化搜索 @cache)

[3003. 执行操作后的最大分割数量 -- 记忆化搜索](#3003. 执行操作后的最大分割数量 -- 记忆化搜索)

[3144. 分割字符频率相等的最少子字符串 -- 记忆化搜索](#3144. 分割字符频率相等的最少子字符串 -- 记忆化搜索)

[698. 划分为k个相等的子集](#698. 划分为k个相等的子集)

[3117. 划分数组得到最小的值之和](#3117. 划分数组得到最小的值之和)

[2963. 统计好分割方案的数目](#2963. 统计好分割方案的数目)

[2. 执行操作 思路题](#2. 执行操作 思路题)

[3347. 执行操作后元素的最高频率 II](#3347. 执行操作后元素的最高频率 II)

[3397. 执行操作后不同元素的最大数量](#3397. 执行操作后不同元素的最大数量)

[2273. 移除字母异位词后的结果数组 -- 快慢指针](#2273. 移除字母异位词后的结果数组 -- 快慢指针)

[56. 合并区间](#56. 合并区间)

[1553. 吃掉 N 个橘子的最少天数](#1553. 吃掉 N 个橘子的最少天数)


1. 划分题&记忆化搜索 @cache

3003. 执行操作后的最大分割数量 -- 记忆化搜索

对字符串s 可以最多修改一个字符,然后进行规则划分(前缀不能超过k个不同字符)

记忆化搜索:( i, mask, changed)

现在到第几个字符;这一段前面出现了哪些字符 用位运算进行mask;有没有changed过。

mask : 26个字母是否出现;

不修改:加上 i ,mask中的数量超过 k,就要在 i-1 分割,分割+1;否则接着mask。

如果 changed 可以修改,枚举改成哪个字母,找 max。

python 复制代码
class Solution:
    def maxPartitionsAfterOperations(self, s: str, k: int) -> int:
        @cache
        def dfs(i: int, mask: int, changed: bool) -> int:
            if i == len(s):
                return 1

            # 不改 s[i]
            bit = 1 << (ord(s[i]) - ord('a'))
            new_mask = mask | bit
            if new_mask.bit_count() > k:
                # 分割出一个子串,这个子串的最后一个字母在 i-1
                # s[i] 作为下一段的第一个字母,也就是 bit 作为下一段的 mask 的初始值
                res = dfs(i + 1, bit, changed) + 1
            else:  # 不分割
                res = dfs(i + 1, new_mask, changed)
            if changed:
                return res

            # 枚举把 s[i] 改成 a,b,c,...,z
            for j in range(26):
                new_mask = mask | (1 << j)
                if new_mask.bit_count() > k:
                    # 分割出一个子串,这个子串的最后一个字母在 i-1
                    # j 作为下一段的第一个字母,也就是 1<<j 作为下一段的 mask 的初始值
                    res = max(res, dfs(i + 1, 1 << j, True) + 1)
                else:  # 不分割
                    res = max(res, dfs(i + 1, new_mask, True))
            return res

        return dfs(0, 0, False)

3144. 分割字符频率相等的最少子字符串 -- 记忆化搜索

对每个位置 i,往后枚举找可划分区间 (i,j)

在循环找末端点位置 j 的时候,用 Counter 计数,并用 all 判断是否次数都同。

python 复制代码
class Solution:
    def minimumSubstringsInPartition(self, s: str) -> int:
        n = len(s)
        @cache
        def dfs(i:int)->int:
            if i==n:
                return 0
            # Counter 进行计数
            c=Counter()
            ans=inf
            for j in range(i,n):
                c[s[j]]+=1
                
                # 优化不用判断,字母种类不是字母数目的因数的话 不可能都相等了
                if (j-i+1)%len(c)!=0:
                    continue
                cc=c[s[j]]
    
                # 可划分就 dfs
                if all(cc==ac for ac in c.values()):
                    ans=min(ans,dfs(j+1)+1)
            return ans
        return dfs(0)

698. 划分为k个相等的子集

记忆化搜索

判断能不能把 n 个数 划分为 k 个相等的子集。 先看因数和最大值判断。

二进制 mask 表示有没有使用过。 从 2^n-1 -> 0

dfs (s, p) 代表对当前状态 s,当前余数 p,是否可以继续成功划分。

nums.sort() 从小到大,如果 nums[i] 塞到 p 会爆,后面更大的也会爆(减少判断次数)。

python 复制代码
class Solution:
    def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
        all = sum(nums)
        if all % k:
            return False
        per = all // k
        nums.sort()  # 方便下面剪枝
        if nums[-1] > per:
            return False
        n = len(nums)

        @cache
        def dfs(s, p):
            if s == 0:
                return True
            for i in range(n):
                if nums[i] + p > per:
                    break
                if s >> i & 1 and dfs(s ^ (1 << i), (p + nums[i]) % per):  # p + nums[i] 等于 per 时置为 0
                    return True
            return False
        return dfs((1 << n) - 1, 0)

动态规划 dp[s] 存状态 s 当前的余数。

(因为不管什么顺序放,最后剩下来的一个桶(余数)是一定的)

没塞过,且不爆的 -> 转换为可达状态。

python 复制代码
class Solution:
    def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
        all = sum(nums)
        if all % k:
            return False
        per = all // k
        nums.sort()
        if nums[-1] > per:
            return False
        n = len(nums)
        dp = [-1] * (1 << n)
        dp[0] = 0
        for i in range(0, 1 << n):
            if dp[i] == -1:
                continue
            for j in range(n):
                if dp[i] + nums[j] > per:
                    break
                if (i >> j & 1) == 0:
                    next = i | (1 << j)
                    if dp[next] == -1:
                        dp[next] = (dp[i] + nums[j]) % per
        return dp[(1 << n) - 1] != -1

3117. 划分数组得到最小的值之和

目标划分为 每一段的& 和给出的第二个数组 对应相等。 数组的"值"定义为 数组最后一个元素。

dfs(i, j, _and) 现在在位置 i,正在划分第 j 段,第 j 段累积的&值为 _and。

当前 _and 与对应值相等则可以进行划分。

python 复制代码
class Solution:
    def minimumValueSum(self, nums: List[int], andValues: List[int]) -> int:
        n, m = len(nums), len(andValues)
        @cache
        def dfs(i: int, j: int, and_: int) -> int:
            if n - i < m - j:  # 剩余元素不足
                return inf
            if j == m:  # 分了 m 段
                return 0 if i == n else inf
            and_ &= nums[i]
            res = dfs(i + 1, j, and_)  # 不划分
            if and_ == andValues[j]:  # 划分,nums[i] 是这一段的最后一个数
                res = min(res, dfs(i + 1, j + 1, -1) + nums[i])
            return res
        ans = dfs(0, 0, -1)
        return ans if ans < inf else -1

2963. 统计好分割方案的数目

所有相同数字必须出现在一段的分割。分割的方案数。

先看哪些位置是必须划分在一起的,如果 m 段,中间有 m-1 个位置是可拼接的,结果即为 2^(m-1)

出现过的数都要被包含,要到最右端 max_r。

先第一遍循环,使用覆盖,得到 r[x] 为 x 出现的最右边位置。

python 复制代码
class Solution:
    def numberOfGoodPartitions(self, nums: List[int]) -> int:
        r = {}
        for i, x in enumerate(nums):
            r[x] = i
        m = max_r = 0
        for i, x in enumerate(nums):
            max_r = max(max_r, r[x])
            if max_r == i:  # 区间无法延长
                m += 1
        return pow(2, m - 1, 1_000_000_007)

2. 执行操作 思路题

3347. 执行操作后元素的最高频率 II

可以对 numOperations 个元素调整 [-k,k],最多可以多少相同的数。

nums[i] 可以变成 nums[i] - k ~ nums[i] + k 区间里的数,只需要知道哪个位置被最多的区间包含。

区间+1用差分实现,后一轮统计时 sum += diff

还有修改元素个数条件,用 cnt[x] 记录 x 本身有多少个。上界是 cnt[x] + numOperations。

所以为 min(sum_x,cnt[x] + numOperations)

由于这个值 sum_x 只受diff影响,后一项只受 cnt[x] 影响。

所以最后结果的最大值位置 只可能出现在 x,x-k,x+k+1。只需要记录、比较这些位置。

python 复制代码
class Solution:
    def maxFrequency(self, nums: List[int], k: int, numOperations: int) -> int:
        cnt = defaultdict(int)
        diff = defaultdict(int)
        for x in nums:
            cnt[x] += 1
            diff[x]  # 把 x 插入 diff,以保证下面能遍历到 x
            diff[x - k] += 1  # 把 [x-k,x+k] 中的每个整数的出现次数都加一
            diff[x + k + 1] -= 1

        ans = sum_d = 0
        for x, d in sorted(diff.items()):
            sum_d += d
            ans = max(ans, min(sum_d, cnt[x] + numOperations))
        return ans

3397. 执行操作后不同元素的最大数量

可以把每个数上下调整k,问最多可以有多少不同的数。

可以建模为军训站队,这个区域最多可以站多少人?最左边的同学会移动到最左边,方便右边的同学好站。

把数从小到大安排,每次安排到能放到的(x-k,x+k)区间,最左边的空位(前一个人pre 之后)。

python 复制代码
class Solution:
    def maxDistinctElements(self, nums: List[int], k: int) -> int:
        nums.sort()
        ans = 0
        pre = -inf  # 记录每个人左边的人的位置
        for x in nums:
            x = min(max(x - k, pre + 1), x + k)
            if x > pre:
                ans += 1
                pre = x
        return ans

2273. 移除字母异位词后的结果数组 -- 快慢指针

一个词如果和前一个词异位(同样的单词组成 只是顺序不同)就删掉。

快慢指针的思想:快指针遍历,慢指针把要保留的单词存下来。(实现原空间覆盖)

python 复制代码
class Solution:
    def removeAnagrams(self, words: List[str]) -> List[str]:
        k = 1
        for s, t in pairwise(words): # 快指针遍历 pairwise取连续两个词
            if sorted(s) != sorted(t): # 慢指针存
                words[k] = t
                k += 1 
        del words[k:] # 删掉后面的
        return words

56. 合并区间

开始位置从前到后排序,和前一个区间不重叠就添加,重叠就通过max结束位置进行合并。

python 复制代码
class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort(key=lambda x: x[0])
        ans = []
        for interval in intervals:
            # 不重叠 添加新区间
            if not ans or ans[-1][1] < interval[0]:
                ans.append(interval)
            # 与最后一个区间合并
            else:
                ans[-1][1] = max(ans[-1][1], interval[1])
        return ans

1553. 吃掉 N 个橘子的最少天数

只吃一个橘子,在 n 比较大的时候一定是比较劣的选择。

实际从 除以2和除以3 中选,吃一个只是为了吃掉对应的余数。

开记忆化 + 函数自我调用。

python 复制代码
class Solution:
    @cache
    def minDays(self, n: int) -> int:
        if n<=1:
            return n
        return min(n%2+1+self.minDays(n//2), n%3+1+self.minDays(n//3))
相关推荐
CoovallyAIHub4 小时前
首个自监督微调Stable Diffusion框架!更清晰、更泛化的单目深度估计(附代码地址)
深度学习·算法·计算机视觉
伟大的车尔尼4 小时前
双指针的概念
数据结构·算法·双指针
CoovallyAIHub4 小时前
谷歌量子计算迎来历史性突破!13000倍性能提升,首款可验证算法登上《Nature》封面
深度学习·算法·计算机视觉
@卞4 小时前
排序算法(1)--- 插入排序
数据结构·算法·排序算法
zh_xuan5 小时前
LeeCode 74. 搜索二维矩阵
数据结构·算法·leecode
.ZGR.5 小时前
蓝桥杯题库——部分简单题题解(Java)
java·数据结构·算法
闻缺陷则喜何志丹5 小时前
【单调队列 多重背包】P1776 宝物筛选|普及+
c++·算法·动态规划·洛谷·多重背包·单调队列
大数据张老师6 小时前
【无标题】
算法·图论
小李小李快乐不已6 小时前
图论理论基础(1)
数据结构·算法·leetcode·深度优先·图论·广度优先·宽度优先