Leetcode——前后缀数组

238.除自身以外数组的乘积

方法:answer[i] = 左边元素乘积 × \times × 右边元素乘积
left[i]:第i个元素左边元素的乘积,left[i] = left[i - 1] * nums[i - 1]
right[i]:第i个元素右边元素的乘积,right[i] = right[i + 1] * nums[i + 1]

空间复杂度O(1)优化
用结果数组answer作为上述的left数组,用一个变量R来跟踪第i个元素右边元素的乘积。更新方式为:answer[i] = answer[i] * R,R = R * nums[i]


396.旋转函数
方法:找 F ( 0 ) , F ( 1 ) , ⋯   , F ( n − 1 ) F(0),F(1),\cdots,F(n - 1) F(0),F(1),,F(n1)的递推关系

思路和算法

这道题目的暴力做法很容易想到:双重循环。但是题目的数据量是 1 0 5 10^5 105,若使用双重循环,则很容易超时。

根据 F ( 0 ) , F ( 1 ) , ⋯   , F ( n − 1 ) F(0),F(1),\cdots,F(n - 1) F(0),F(1),,F(n1)的定义,可以写出如下表达式:
F ( 0 ) = 0 × a r r [ 0 ] + 1 × a r r [ 1 ] + 2 × a r r [ 2 ] + ⋯ + ( n − 1 ) × a r r [ n − 1 ] F(0) = 0 \times arr[0] + 1 \times arr[1] + 2 \times arr[2] + \cdots + (n - 1) \times arr[n - 1] F(0)=0×arr[0]+1×arr[1]+2×arr[2]++(n1)×arr[n1]
F ( 1 ) = 0 × a r r [ n − 1 ] + 1 × a r r [ 0 ] + 2 × a r r [ 1 ] + ⋯ + ( n − 1 ) × a r r [ n − 2 ] F(1) = 0 \times arr[n - 1] + 1 \times arr[0] + 2 \times arr[1] + \cdots + (n - 1) \times arr[n - 2] F(1)=0×arr[n1]+1×arr[0]+2×arr[1]++(n1)×arr[n2]
F ( 2 ) = 0 × a r r [ n − 2 ] + 1 × a r r [ n − 1 ] + 2 × a r r [ 0 ] + ⋯ + ( n − 1 ) × a r r [ n − 3 ] F(2) = 0 \times arr[n - 2] + 1 \times arr[n - 1] + 2 \times arr[0] + \cdots + (n - 1) \times arr[n - 3] F(2)=0×arr[n2]+1×arr[n1]+2×arr[0]++(n1)×arr[n3]
⋯ \cdots
F ( n − 1 ) = 0 × a r r [ 1 ] + 1 × a r r [ 2 ] + 2 × a r r [ 3 ] + ⋯ + ( n − 1 ) × a r r [ 0 ] F(n - 1) = 0 \times arr[1] + 1 \times arr[2] + 2 \times arr[3] + \cdots + (n - 1) \times arr[0] F(n1)=0×arr[1]+1×arr[2]+2×arr[3]++(n1)×arr[0]

通过观察相邻两个旋转函数的表达式,写出下列关系式:
F ( 1 ) = F ( 0 ) − ( n − 1 ) ∗ a r r [ n − 1 ] + ( a r r [ 0 ] + a r r [ 1 ] + ⋯ + a r r [ n − 2 ] ) F(1) = F(0) - (n - 1) * arr[n - 1] + (arr[0] + arr[1] + \cdots + arr[n - 2]) F(1)=F(0)(n1)arr[n1]+(arr[0]+arr[1]++arr[n2])
F ( 2 ) = F ( 1 ) − ( n − 1 ) ∗ a r r [ n − 2 ] + ( a r r [ 0 ] + a r r [ 1 ] + ⋯ + a r r [ n − 3 ] ) + a r r [ n − 1 ] F(2) = F(1) - (n - 1) * arr[n - 2] + (arr[0] + arr[1] + \cdots + arr[n - 3]) + arr[n - 1] F(2)=F(1)(n1)arr[n2]+(arr[0]+arr[1]++arr[n3])+arr[n1]
. . . ... ...

通过以上关系式,总结出下列递推表达式:
F ( i ) = F ( i − 1 ) − ( n − 1 ) × a r r [ n − i ] + ( a r r [ 0 ] + ⋯ + a r r [ n − i − 1 ] ) + ( a r r [ n − i + 1 ] + ⋯ + a r r [ n − 1 ] ) F(i) = F(i - 1) - (n - 1) \times arr[n - i] + (arr[0] + \cdots + arr[n - i - 1]) + (arr[n - i + 1] + \cdots + arr[n - 1]) F(i)=F(i1)(n1)×arr[ni]+(arr[0]++arr[ni1])+(arr[ni+1]++arr[n1])

其中, a r r [ 0 ] + ⋯ + a r r [ n − i − 1 ] arr[0] + \cdots + arr[n - i - 1] arr[0]++arr[ni1]是数组前 n − i n - i ni项的和, a r r [ n − i + 1 ] + ⋯ + a r r [ n − 1 ] arr[n - i + 1] + \cdots + arr[n - 1] arr[ni+1]++arr[n1]是数组后 i − 1 i - 1 i1项的和。这引导我们利用前缀和的思想进行优化计算。


437.路径总和III

糟了,马失前蹄了。

思路和算法
  1. 递归。
  2. 递归 + 前缀和。其中前缀和用哈希表实现。
方法一 递归

看到这道题目一个很直观的想法是:

  1. 从当前结点出发有两条路径
  2. 把当前结点加入到之前的路径也有两条不同路径

因此,需要对每个结点进行 4 4 4次深度优先搜索。

错误案例

上述思路不差,但如果不小心,很容易写出重复遍历的代码:

int _pathSum(TreeNode* root,int targetSum,long long path){
        if(root == nullptr) return 0;

        int res = 0;
        if(path != LONG_MIN){
            if(root -> val == targetSum) res += 1;
            res += _pathSum(root -> left,targetSum,root -> val);
            res += _pathSum(root -> right,targetSum,root -> val);
        }

        // path = 0;
        path = path == LONG_MIN ? root -> val : path + root -> val;

        if(path == targetSum) res += 1;
        res += _pathSum(root -> left, targetSum, path);
        res += _pathSum(root -> right, targetSum, path);
        return res;
    }

以测试用例 [ 1 , n u l l , 2 , n u l l , 3 , n u l l , 4 , n u l l , 5 ]        3 [1,null,2,null,3,null,4,null,5]\;\;\;3 [1,null,2,null,3,null,4,null,5]3为例,从结点2开始的路径会遍历结点3,结点2延续之前的路径同样会遍历结点3,这就导致以结点3开始的路径会重复遍历两次,不仅提高了复杂度,而且可能导致结果错误。

正确做法

dfs1遍历所有结点,时间复杂度是 O ( n ) O(n) O(n);用dfs2搜素所有以当前结点为根节点,并且满足路径和是targetSum的所有路径,时间复杂度同样是 O ( n ) O(n) O(n)。整体时间复杂度是 O ( n 2 ) O(n^2) O(n2)。代码如下:

int res, t;
    void dfs1(TreeNode* root){
        if(root == nullptr) return;
        dfs1(root -> left);
        dfs1(root -> right);
        dfs2(root, root -> val);
    }

    void dfs2(TreeNode* root, long long path){
        if(path == t) res ++;
        if(root -> left != nullptr) dfs2(root -> left, path + root -> left -> val);
        if(root -> right != nullptr) dfs2(root -> right, path + root -> right -> val);
    }
教训

总的来说,还是得自己的原因,解题思路比较混乱。

方法二 递归 + 前缀和

注意到,每次dfs只能是从上向下的单方向,因此,每次dfs可以认为是一个一维的搜索。于是问题转换为:在从根节点root到节点b的路径中,有多少节点满足sum[a...b] == targetSum

具体而言,用哈希表存储从根节点出发到节点cur中的路径中,路径所有节点到根节点root的路径和。满足sum[a...b] == targetSum节点数目即是哈希表中path - target出现的次数。其中,path表示包括节点cur在内的路径和,path - target出现的次数表示当前路径以某一节点为起点,到节点cur的路径和为target的节点个数。

实现细节
在回溯时,要把当前路径和从哈希表中删掉


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述:给定一个非负整数数组nums和一个整数m,你需要将这个数组分成m个非空的连续子数组。设计一个算法使得这m个子数组中的最大和最小。 解题思路: 这是一个典型的二分搜索题目,可以使用二分查找来解决。 1. 首先确定二分的左右边界。左边界为数组中最大的值,右边界为数组中所有元素之和。 2. 在二分搜索的过程中,计算出分割数组的组数count,需要使用当的中间值来进行判断。若当的中间值不够分割成m个子数组,则说明mid值偏小,将左边界更新为mid+1;否则,说明mid值偏大,将右边界更新为mid。 3. 当左边界小于等于右边界时,循环终止,此时的左边界即为所求的结果。 具体步骤: 1. 遍历数组,找到数组中的最大值,并计算数组的总和。 2. 利用二分查找搜索左右边界,从左边界到右边界中间的值为mid。 3. 判断当的mid值是否满足题目要求,若满足则更新右边界为mid-1; 4. 否则,更新左边界为mid+1。 5. 当左边界大于右边界时,循环终止,返回左边界即为所求的结果。 代码实现: ```python class Solution: def splitArray(self, nums: List[int], m: int) -> int: left = max(nums) right = sum(nums) while left <= right: mid = (left + right) // 2 count = 1 total = 0 for num in nums: total += num if total > mid: total = num count += 1 if count > m: left = mid + 1 else: right = mid - 1 return left ``` 时间复杂度分析:二分搜索的时间复杂度为O(logN),其中N为数组的总和,而遍历数组的时间复杂度为O(N),因此总的时间复杂度为O(NlogN)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值