Skip to content

Commit 527449b

Browse files
committed
feat: add solutions to lc problem: No.2179
No.2179.Count Good Triplets in an Array
1 parent a30edb6 commit 527449b

File tree

6 files changed

+706
-19
lines changed

6 files changed

+706
-19
lines changed

Diff for: solution/2100-2199/2179.Count Good Triplets in an Array/README.md

+239-17
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ tags:
6565

6666
<!-- solution:start -->
6767

68-
### 方法一:树状数组或线段树
68+
### 方法一:树状数组
6969

7070
对于本题,我们先用 pos 记录每个数在 nums2 中的位置,然后依次对 nums1 中的每个元素进行处理。
7171

@@ -79,27 +79,14 @@ tags:
7979
1. ...
8080
1. 最后是 2,此时 nums2 中出现情况为 `[4,1,0,2,3]`,2 之前有值的个数是 4,2 之后没有值的个数是 0。因此以 2 为中间数字能形成 0 个好三元组。
8181

82-
我们可以用**树状数组****线段树**这两种数据结构来更新 nums2 中各个位置数字的出现情况,快速算出每个数字左侧 1 的个数,以及右侧 0 的个数。
83-
84-
**数据结构 1:树状数组**
82+
我们可以用**树状数组**来更新 nums2 中各个位置数字的出现情况,快速算出每个数字左侧 1 的个数,以及右侧 0 的个数。
8583

8684
树状数组,也称作“二叉索引树”(Binary Indexed Tree)或 Fenwick 树。 它可以高效地实现如下两个操作:
8785

8886
1. **单点更新** `update(x, delta)`: 把序列 x 位置的数加上一个值 delta;
8987
1. **前缀和查询** `query(x)`:查询序列 `[1,...x]` 区间的区间和,即位置 x 的前缀和。
9088

91-
这两个操作的时间复杂度均为 $O(\log n)$。
92-
93-
**数据结构 2:线段树**
94-
95-
线段树将整个区间分割为多个不连续的子区间,子区间的数量不超过 `log(width)`。更新某个元素的值,只需要更新 `log(width)` 个区间,并且这些区间都包含在一个包含该元素的大区间内。
96-
97-
- 线段树的每个节点代表一个区间;
98-
- 线段树具有唯一的根节点,代表的区间是整个统计范围,如 `[1, N]`
99-
- 线段树的每个叶子节点代表一个长度为 1 的元区间 `[x, x]`
100-
- 对于每个内部节点 `[l, r]`,它的左儿子是 `[l, mid]`,右儿子是 `[mid + 1, r]`, 其中 `mid = ⌊(l + r) / 2⌋` (即向下取整)。
101-
102-
> 本题 Python3 线段树代码 TLE。
89+
这两个操作的时间复杂度均为 $O(\log n)$。因此,整体的时间复杂度为 $O(n \log n)$,其中 $n$ 为数组 $\textit{nums1}$ 的长度。空间复杂度 $O(n)$。
10390

10491
<!-- tabs:start -->
10592

@@ -302,20 +289,87 @@ func goodTriplets(nums1 []int, nums2 []int) int64 {
302289
}
303290
```
304291

292+
#### TypeScript
293+
294+
```ts
295+
class BinaryIndexedTree {
296+
private c: number[];
297+
private n: number;
298+
299+
constructor(n: number) {
300+
this.n = n;
301+
this.c = Array(n + 1).fill(0);
302+
}
303+
304+
private static lowbit(x: number): number {
305+
return x & -x;
306+
}
307+
308+
update(x: number, delta: number): void {
309+
while (x <= this.n) {
310+
this.c[x] += delta;
311+
x += BinaryIndexedTree.lowbit(x);
312+
}
313+
}
314+
315+
query(x: number): number {
316+
let s = 0;
317+
while (x > 0) {
318+
s += this.c[x];
319+
x -= BinaryIndexedTree.lowbit(x);
320+
}
321+
return s;
322+
}
323+
}
324+
325+
function goodTriplets(nums1: number[], nums2: number[]): number {
326+
const n = nums1.length;
327+
const pos = new Map<number, number>();
328+
nums2.forEach((v, i) => pos.set(v, i + 1));
329+
330+
const tree = new BinaryIndexedTree(n);
331+
let ans = 0;
332+
333+
for (const num of nums1) {
334+
const p = pos.get(num)!;
335+
const left = tree.query(p);
336+
const total = tree.query(n);
337+
const right = n - p - (total - left);
338+
ans += left * right;
339+
tree.update(p, 1);
340+
}
341+
342+
return ans;
343+
}
344+
```
345+
305346
<!-- tabs:end -->
306347

307348
<!-- solution:end -->
308349

309350
<!-- solution:start -->
310351

311-
### 方法二
352+
### 方法二:线段树
353+
354+
我们也可以用线段树来实现。线段树是一种数据结构,能够高效地进行区间查询和更新操作。它的基本思想是将一个区间划分为多个子区间,并且每个子区间都可以用一个节点来表示。
355+
356+
线段树将整个区间分割为多个不连续的子区间,子区间的数量不超过 `log(width)`。更新某个元素的值,只需要更新 `log(width)` 个区间,并且这些区间都包含在一个包含该元素的大区间内。
357+
358+
- 线段树的每个节点代表一个区间;
359+
- 线段树具有唯一的根节点,代表的区间是整个统计范围,如 `[1, N]`
360+
- 线段树的每个叶子节点代表一个长度为 1 的元区间 `[x, x]`
361+
- 对于每个内部节点 `[l, r]`,它的左儿子是 `[l, mid]`,右儿子是 `[mid + 1, r]`, 其中 `mid = ⌊(l + r) / 2⌋` (即向下取整)。
362+
363+
时间复杂度 $O(n \log n)$,其中 $n$ 为数组 $\textit{nums1}$ 的长度。空间复杂度 $O(n)$。
312364

313365
<!-- tabs:start -->
314366

315367
#### Python3
316368

317369
```python
318370
class Node:
371+
__slots__ = ("l", "r", "v")
372+
319373
def __init__(self):
320374
self.l = 0
321375
self.r = 0
@@ -539,6 +593,174 @@ public:
539593
};
540594
```
541595
596+
#### Go
597+
598+
```go
599+
type Node struct {
600+
l, r, v int
601+
}
602+
603+
type SegmentTree struct {
604+
tr []Node
605+
}
606+
607+
func NewSegmentTree(n int) *SegmentTree {
608+
tr := make([]Node, 4*n)
609+
st := &SegmentTree{tr: tr}
610+
st.build(1, 1, n)
611+
return st
612+
}
613+
614+
func (st *SegmentTree) build(u, l, r int) {
615+
st.tr[u].l = l
616+
st.tr[u].r = r
617+
if l == r {
618+
return
619+
}
620+
mid := (l + r) >> 1
621+
st.build(u<<1, l, mid)
622+
st.build(u<<1|1, mid+1, r)
623+
}
624+
625+
func (st *SegmentTree) modify(u, x, v int) {
626+
if st.tr[u].l == x && st.tr[u].r == x {
627+
st.tr[u].v += v
628+
return
629+
}
630+
mid := (st.tr[u].l + st.tr[u].r) >> 1
631+
if x <= mid {
632+
st.modify(u<<1, x, v)
633+
} else {
634+
st.modify(u<<1|1, x, v)
635+
}
636+
st.pushup(u)
637+
}
638+
639+
func (st *SegmentTree) pushup(u int) {
640+
st.tr[u].v = st.tr[u<<1].v + st.tr[u<<1|1].v
641+
}
642+
643+
func (st *SegmentTree) query(u, l, r int) int {
644+
if st.tr[u].l >= l && st.tr[u].r <= r {
645+
return st.tr[u].v
646+
}
647+
mid := (st.tr[u].l + st.tr[u].r) >> 1
648+
res := 0
649+
if l <= mid {
650+
res += st.query(u<<1, l, r)
651+
}
652+
if r > mid {
653+
res += st.query(u<<1|1, l, r)
654+
}
655+
return res
656+
}
657+
658+
func goodTriplets(nums1 []int, nums2 []int) int64 {
659+
n := len(nums1)
660+
pos := make(map[int]int)
661+
for i, v := range nums2 {
662+
pos[v] = i + 1
663+
}
664+
665+
tree := NewSegmentTree(n)
666+
var ans int64
667+
668+
for _, num := range nums1 {
669+
p := pos[num]
670+
left := tree.query(1, 1, p)
671+
right := n - p - (tree.query(1, 1, n) - tree.query(1, 1, p))
672+
ans += int64(left * right)
673+
tree.modify(1, p, 1)
674+
}
675+
676+
return ans
677+
}
678+
```
679+
680+
#### TypeScript
681+
682+
```ts
683+
class Node {
684+
l: number = 0;
685+
r: number = 0;
686+
v: number = 0;
687+
}
688+
689+
class SegmentTree {
690+
private tr: Node[];
691+
692+
constructor(n: number) {
693+
this.tr = Array(4 * n);
694+
for (let i = 0; i < 4 * n; i++) {
695+
this.tr[i] = new Node();
696+
}
697+
this.build(1, 1, n);
698+
}
699+
700+
private build(u: number, l: number, r: number): void {
701+
this.tr[u].l = l;
702+
this.tr[u].r = r;
703+
if (l === r) return;
704+
const mid = (l + r) >> 1;
705+
this.build(u << 1, l, mid);
706+
this.build((u << 1) | 1, mid + 1, r);
707+
}
708+
709+
modify(u: number, x: number, v: number): void {
710+
if (this.tr[u].l === x && this.tr[u].r === x) {
711+
this.tr[u].v += v;
712+
return;
713+
}
714+
const mid = (this.tr[u].l + this.tr[u].r) >> 1;
715+
if (x <= mid) {
716+
this.modify(u << 1, x, v);
717+
} else {
718+
this.modify((u << 1) | 1, x, v);
719+
}
720+
this.pushup(u);
721+
}
722+
723+
private pushup(u: number): void {
724+
this.tr[u].v = this.tr[u << 1].v + this.tr[(u << 1) | 1].v;
725+
}
726+
727+
query(u: number, l: number, r: number): number {
728+
if (this.tr[u].l >= l && this.tr[u].r <= r) {
729+
return this.tr[u].v;
730+
}
731+
const mid = (this.tr[u].l + this.tr[u].r) >> 1;
732+
let res = 0;
733+
if (l <= mid) {
734+
res += this.query(u << 1, l, r);
735+
}
736+
if (r > mid) {
737+
res += this.query((u << 1) | 1, l, r);
738+
}
739+
return res;
740+
}
741+
}
742+
743+
function goodTriplets(nums1: number[], nums2: number[]): number {
744+
const n = nums1.length;
745+
const pos = new Map<number, number>();
746+
nums2.forEach((v, i) => pos.set(v, i + 1));
747+
748+
const tree = new SegmentTree(n);
749+
let ans = 0;
750+
751+
for (const num of nums1) {
752+
const p = pos.get(num)!;
753+
const left = tree.query(1, 1, p);
754+
const total = tree.query(1, 1, n);
755+
const right = n - p - (total - left);
756+
ans += left * right;
757+
tree.modify(1, p, 1);
758+
}
759+
760+
return ans;
761+
}
762+
```
763+
542764
<!-- tabs:end -->
543765

544766
<!-- solution:end -->

0 commit comments

Comments
 (0)