leetcode-计算贡献

本文介绍了如何运用单调栈解决一系列数组问题,包括寻找子串的唯一字符、计算子数组的最小值之和、求解巫师的总力量以及子数组范围和。通过建立单调栈来寻找子序列的边界,结合前缀和优化,实现高效计算。文章展示了不同场景下的代码实现,并探讨了优化思路。
摘要由CSDN通过智能技术生成

6050. 字符串的总引力

开始我想的是类似最长回文子串那种,二维dp。本来我是觉得dp[i][j]表示从i到j的子字符串长度,然后从dp[i+1][j]和dp[i][j-1]中计算得到。debug走了一下,在这中间计算的话会有重复值

力扣

828. 统计子串中的唯一字符

 滑窗,超时

class Solution:
    def uniqueLetterString(self, s: str) -> int:
        count=0
        #滑动窗口
        left=-1
        while left<len(s)-1:
            left+=1
            cur=s[left]
            count+=1
            visited={}
            visited[cur]=1
            right=left+1
            while right<len(s):
                cur = s[right]
                if cur in visited:
                   visited[cur]+=1
                else:
                    visited[cur]=1
                cur_count=0
                for key in visited:
                    if visited[key]==1:
                        cur_count+=1
                count += cur_count
                right += 1
        return count

算贡献+组合数学

class Solution:
    def uniqueLetterString(self, s: str) -> int:
        res=0
        #转化为:每个字母所在在子串,包含1个当前字母的子串个数。
        for i in range(len(s)):
            cur=s[i]
            #往左
            j=i-1
            while j>=0 and s[j]!=cur:
                j=j-1
            left_count=i-j 
            #往右
            h=i+1
            while h<=len(s)-1 and s[h]!=cur:
                h=h+1
            right_count=h-i 
            cur_count=left_count*right_count
            res+=cur_count
        return res

907. 子数组的最小值之和

 算贡献+组合数学:超时

注意,left取大于等于,right就取大于,避免重复。类似的情况还有,求数组里某个数右边的第一个最小数字。

class Solution(object):
    def sumSubarrayMins(self, arr):
        #找贡献,以arr[i]为最小值,的左边界和右边界
        res = 0
        for i in range(len(arr)):
            if i>=1:
                left = i-1
                while left >= 0:
                    #left取大于等于,right就取大于,避免重复
                    if arr[left] >= arr[i]:
                        left -= 1
                    else:
                        break
                left_count = i-left
            else:
                left_count = 1

            if i<len(arr)-1:
                right = i+1
                while right < len(arr) - 1:
                    if arr[right] >= arr[i]:
                        right += 1
                    else:
                        break
                right_count = right - i
            else:
                right_count = 1
            res += (left_count*right_count)*arr[i]
        return res
# arr = [3,1,2,4]
arr = [71,55,82,55]
res = Solution().sumSubarrayMins(arr)
print(res)

单调递增栈

力扣

单调递增/递减栈,本质上是在求s[i]的左边界和右边界。上面的解法是暴力求s[i]的左边界和右边界,单调递增栈是用空间换时间,维护一个下标数组,来求s[i]的左边界和右边界。

注意:

#保证元素都会被弹出
        arr.append(-1)

class Solution:
    def sumSubarrayMins(self, arr: List[int]) -> int:
        #找贡献,以arr[i]为最小值,的左边界和右边界
        #用单调递增栈来求
        stack = []
        #保证元素都会被弹出
        arr.append(-1)
        res = 0
        for i in range(len(arr)):
            while len(stack) > 0 and arr[i] < arr[stack[-1]]:
                cur_minv_val_index = stack.pop()
                if len(stack) > 0:
                    left_index = stack[-1]
                else:
                    left_index = -1
                right_index = i
                res += arr[cur_minv_val_index]*(cur_minv_val_index - left_index)*(right_index - cur_minv_val_index)
            stack.append(i)
        return res%(10**9+7)

1498 满足条件的子序列数目

超时

class Solution:
    def numSubseq(self, nums: List[int], target: int) -> int:
       # Sort the array nums.
        # nums[i] +nums[j] ≤ target.
        # Count the number of subsequences.
        res = 0
        nums.sort()
        for i in range(len(nums)):
            flag = False
            flag2 = False
            for j in range(i, len(nums)):
                if nums[i] + nums[j] <= target:
                    flag = True
                    continue
                else:
                    #如果走到数组最后都没有break,说明j是满足条件的,不用j-1
                    flag2 = True
                    break
            # i必须选,剩下的可选可不选(i+1~j)
            # 假如i+1~j为4,那么结果是c(4,1)+c(4,2)+c(4,3)+c(4,4)+1
            # 其实每个位置可选可不选,每个位置两种情况,2的(i+1~j)次方
            if flag == True and flag2 == True:
                res += 2 ** (j - 1 - i)
            elif flag == True and flag2 == False:
                res += 2 ** (j - i)
        return res % (10 ** 9 + 7)

排序+二分+计算贡献

排序+二分找边界,类似363. 矩形区域不超过 K 的最大数值和

leetcode-前缀和/差分数组_林冲风雪山神庙的博客-CSDN博客

力扣

class Solution:
    def numSubseq(self, nums: List[int], target: int) -> int:
        nums.sort()
        total_len = len(nums)
        res = 0
        for i in range(total_len):
            left, right = i, total_len - 1
            while left <= right:
                mid = (left + right) // 2
                if nums[i] + nums[mid] <= target:
                    left = mid + 1
                else:
                    right = mid - 1
            if left-1 >= i:
                #最后一个满足条件的if nums[i] + nums[mid] <= target:left = mid + 1
                #所以最后一个满足条件的下标mid=left-1
                res += 2**(left-1-i)
        return res % (10**9 + 7)

排序+双指针+计算贡献

因为数组是有序的,那么如果nums[l]+nums[r]大于target,比nums[l]往后的数,加上+nums[r]一定也是大于target的

class Solution {
public:
    static constexpr int mod = 1e9+7;
    int numSubseq(vector<int>& nums, int target) {
        const int n = (int)nums.size();
        sort(nums.begin(), nums.end());
        int l = 0, r = n-1;
        int ans = 0;
        while (l<=r) {
            if (nums[l] + nums[r] > target) {
                --r;
            } else {
                ans = (ans + (1<<(r-l))) % mod;
                ++l;
            }
        }
        return ans;
    }
};

作者:sui-xin-yuan
链接:https://leetcode-cn.com/problems/number-of-subsequences-that-satisfy-the-given-sum-condition/solution/shu-by-sui-xin-yuan-hamy/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

6077. 巫师的总力量和

 单调栈+前缀和的前缀和

力扣

这个思路有点像子数组范围和 

 for i in range(len(nums)):
            ans += nums[i] * (i - left_max[i]) * (right_max[i] - i)
            ans -= nums[i] * (i - left_min[i]) * (right_min[i] - i)
        return ans

超时

class Solution(object):
    def totalStrength(self, strength):
        """
        :type strength: List[int]
        :rtype: int
        """
        mod = 10**9 + 7
        nums = strength
        left_min=[-1  for i in range(len(nums))]
        right_min=[-1  for i in range(len(nums))]

        #最小值,往右边可以辐射到哪里,单调递递增
        stack=[]
        for i in range(len(nums)-1,-1,-1):
            while stack and nums[i]<nums[stack[-1]]:
                stack.pop()
            if stack and nums[i]>=nums[stack[-1]]:
                right_min[i]=stack[-1]
            else:
                right_min[i]=len(nums)
            stack.append(i)
        #最小值,往左边边可以辐射到哪里,单调递增
        stack=[]
        for i in range(len(nums)):
            while stack and nums[i]<=nums[stack[-1]]:
                stack.pop()
            if stack and nums[i]>nums[stack[-1]]:
                left_min[i]=stack[-1]
            else:
                left_min[i]=-1
            stack.append(i)
        # print(left_min,right_min)
        presum = [0 for _ in range(len(nums))]
        presum[0] = nums[0]
        for i in range(1,len(nums)):
            presum[i] = presum[i-1] + nums[i]
        ans=0
        for i in range(len(nums)):
            cur_sum = 0
            left_count = i - left_min[i]
            right_count = right_min[i] - i 
            for v in range(left_min[i],i):
                if v != -1:
                    cur_sum -= presum[v] * right_count
            for v in range(i,right_min[i]):
                cur_sum += presum[v] * left_count
            ans += cur_sum * nums[i]
        return ans % mod

前缀和的前缀和优化

class Solution(object):
    def totalStrength(self, strength):
        """
        :type strength: List[int]
        :rtype: int
        """
        mod = 10**9 + 7
        nums = strength
        left_min=[-1  for i in range(len(nums))]
        right_min=[-1  for i in range(len(nums))]

        #最小值,往右边可以辐射到哪里,单调递递增
        stack=[]
        for i in range(len(nums)-1,-1,-1):
            while stack and nums[i]<nums[stack[-1]]:
                stack.pop()
            if stack and nums[i]>=nums[stack[-1]]:
                right_min[i]=stack[-1]
            else:
                right_min[i]=len(nums)
            stack.append(i)
        #最小值,往左边边可以辐射到哪里,单调递增
        stack=[]
        for i in range(len(nums)):
            while stack and nums[i]<=nums[stack[-1]]:
                stack.pop()
            if stack and nums[i]>nums[stack[-1]]:
                left_min[i]=stack[-1]
            else:
                left_min[i]=-1
            stack.append(i)
        # print(left_min,right_min)
        presum = [0 for _ in range(len(nums))]
        presum[0] = nums[0]
        for i in range(1,len(nums)):
            presum[i] = presum[i-1] + nums[i]
        presum2 = [0 for _ in range(len(nums))]
        presum2[0] = presum[0]
        for i in range(1,len(nums)):
            presum2[i] = presum2[i-1] + presum[i]
        ans=0
        for i in range(len(nums)):
            cur_sum = 0
            left_count = i - left_min[i]
            right_count = right_min[i] - i 
            if i >= 1:
                cur_sum += (presum2[right_min[i]-1]-presum2[i-1]) * left_count
            else:
                cur_sum += presum2[right_min[i]-1] * left_count
            if left_min[i]-1 >= 0 and i-1 >= 0:
                cur_sum -= (presum2[i-1] - presum2[left_min[i]-1]) * right_count
            elif i-1 >= 0 and left_min[i]-1 < 0:
                cur_sum -= presum2[i-1]  * right_count
            # for v in range(left_min[i],i):
            #     if v != -1:
            #         cur_sum -= presum[v] * right_count
            # for v in range(i,right_min[i]):
            #     cur_sum += presum[v] * left_count
            ans += cur_sum * nums[i]
        return ans % mod

2104. 子数组范围和

单调栈

class Solution:
    def subArrayRanges(self, nums: List[int]) -> int:
        #nums[i]是多少子数组的最大值,以及多少子数组的最小值。相加
        #求nums[i]是多少子数组的最大值,找到nums[i]左边和右边的边界,2个单调栈
        #求nums[i]是多少子数组的最小值,找到nums[i]左边和右边的边界,2个单调栈。共4个单调栈

        left_max=[-1 for i in range(len(nums))]
        right_max=[-1  for i in range(len(nums))]
        left_min=[-1  for i in range(len(nums))]
        right_min=[-1  for i in range(len(nums))]
        
        #最大值,往右边可以辐射到哪里,单调递减栈
        stack=[]
        for i in range(len(nums)-1,-1,-1):
            while stack and nums[i]>nums[stack[-1]]:
                stack.pop()
            if stack and nums[i]<=nums[stack[-1]]:
                right_max[i]=stack[-1]
            else:
                right_max[i]=len(nums)
            stack.append(i)
        #最大值,往左边可以辐射到哪里,单调递减栈
        stack=[]
        for i in range(len(nums)):
            while stack and nums[i]>=nums[stack[-1]]:
                stack.pop()
            if stack and nums[i]<nums[stack[-1]]:
                left_max[i]=stack[-1]
            else:
                left_max[i]=-1
            stack.append(i)
        #最小值,往右边可以辐射到哪里,单调递递增
        stack=[]
        for i in range(len(nums)-1,-1,-1):
            while stack and nums[i]<nums[stack[-1]]:
                stack.pop()
            if stack and nums[i]>=nums[stack[-1]]:
                right_min[i]=stack[-1]
            else:
                right_min[i]=len(nums)
            stack.append(i)
        #最小值,往左边边可以辐射到哪里,单调递增
        stack=[]
        for i in range(len(nums)):
            while stack and nums[i]<=nums[stack[-1]]:
                stack.pop()
            if stack and nums[i]>nums[stack[-1]]:
                left_min[i]=stack[-1]
            else:
                left_min[i]=-1
            stack.append(i)
        print(left_max)
        print(right_max)
        print(left_min)
        print(right_min)
        ans=0
        for i in range(len(nums)):
            ans += nums[i] * (i - left_max[i]) * (right_max[i] - i)
            ans -= nums[i] * (i - left_min[i]) * (right_min[i] - i)
        return ans
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值