方法:
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(n−1)的递推关系
思路和算法
这道题目的暴力做法很容易想到:双重循环。但是题目的数据量是 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(n−1)的定义,可以写出如下表达式:
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]+⋯+(n−1)×arr[n−1]
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[n−1]+1×arr[0]+2×arr[1]+⋯+(n−1)×arr[n−2]
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[n−2]+1×arr[n−1]+2×arr[0]+⋯+(n−1)×arr[n−3]
⋯
\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(n−1)=0×arr[1]+1×arr[2]+2×arr[3]+⋯+(n−1)×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)−(n−1)∗arr[n−1]+(arr[0]+arr[1]+⋯+arr[n−2])
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)−(n−1)∗arr[n−2]+(arr[0]+arr[1]+⋯+arr[n−3])+arr[n−1]
.
.
.
...
...
通过以上关系式,总结出下列递推表达式:
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(i−1)−(n−1)×arr[n−i]+(arr[0]+⋯+arr[n−i−1])+(arr[n−i+1]+⋯+arr[n−1])
其中, a r r [ 0 ] + ⋯ + a r r [ n − i − 1 ] arr[0] + \cdots + arr[n - i - 1] arr[0]+⋯+arr[n−i−1]是数组前 n − i n - i n−i项的和, a r r [ n − i + 1 ] + ⋯ + a r r [ n − 1 ] arr[n - i + 1] + \cdots + arr[n - 1] arr[n−i+1]+⋯+arr[n−1]是数组后 i − 1 i - 1 i−1项的和。这引导我们利用前缀和的思想进行优化计算。
糟了,马失前蹄了。
思路和算法
- 递归。
- 递归 + 前缀和。其中前缀和用哈希表实现。
方法一 递归
看到这道题目一个很直观的想法是:
- 从当前结点出发有两条路径
- 把当前结点加入到之前的路径也有两条不同路径
因此,需要对每个结点进行 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
的节点个数。
实现细节
在回溯时,要把当前路径和从哈希表中删掉