Leetcode——前后缀数组

文章讲述了如何解决数组中除自身外的元素乘积问题,通过空间复杂度优化至O(1),并探讨了旋转函数的递推关系,避免了双重循环导致的时间复杂度过高。同时,提出了路径和问题的递归与前缀和的解决方案,强调了避免重复遍历在递归算法中的重要性。
摘要由CSDN通过智能技术生成

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
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值