Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions content/blog/disjoint_set/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ preview_image: /images/previews/disjoint_set.webp
tags:
- data_structure
- graph
- disjoint_set
---

---
Expand Down
52 changes: 52 additions & 0 deletions content/blog/number_of_provinces/go/solution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package number_of_provinces

type DisjointSet struct {
nodes []int
}

func NewDisjointSet(size int) DisjointSet {
nodes := make([]int, size)

for i := 0; i < size; i++ {
nodes[i] = i
}

return DisjointSet{
nodes: nodes,
}
}

func (uf *DisjointSet) find(node int) int {
return uf.nodes[node]
}

func (uf *DisjointSet) union(x int, y int) {
rootX := uf.find(x)
rootY := uf.find(y)

if rootY != rootX {
for i := range uf.nodes {
if uf.nodes[i] == rootY {
uf.nodes[i] = rootX
}
}
}
}

func findCircleNum(isConnected [][]int) int {
n := len(isConnected)
disjointSet := NewDisjointSet(n)

numberOfProvinces := n

for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
if isConnected[i][j] == 1 && disjointSet.find(i) != disjointSet.find(j) {
numberOfProvinces -= 1
disjointSet.union(i, j)
}
}
}

return numberOfProvinces
}
42 changes: 42 additions & 0 deletions content/blog/number_of_provinces/go/solution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package number_of_provinces

import (
"github.com/stretchr/testify/assert"
"testing"
)

type suit struct {
name string
isConnected [][]int
expected int
}

func Test_findCircleNum(t *testing.T) {
testCases := []suit{
{
name: "example_1",
isConnected: [][]int{
{1, 1, 0},
{1, 1, 0},
{0, 0, 1},
},
expected: 2,
},
{
name: "example_2",
isConnected: [][]int{
{1, 0, 0},
{0, 1, 0},
{0, 0, 1},
},
expected: 3,
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
res := findCircleNum(testCase.isConnected)
assert.Equal(t, testCase.expected, res)
})
}
}
107 changes: 107 additions & 0 deletions content/blog/number_of_provinces/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
layout: layouts/post.njk
title: Количество провинций
date: 2025-02-05
complexity: medium
original_url: https://leetcode.com/problems/number-of-provinces/description/
preview_image: /images/previews/number_of_provinces.webp
tags:
- medium
- graph
- disjoint_set
---

---

## Описание задачи

Есть `n` городов, некоторые из которых соединены между собой.

Если город `a` напрямую соединен c городом `b`, а город `b` напрямую соединен с городом `c`, тогда города `a` и `c`
косвенно соединены между собой.

Провинция — это группа напрямую или косвенно связанных между собой городов.

Вам дана матрица `isConnected` размером `n` x `n`, где `isConnected[i][j] = 1`, если `i`-й город и `j`-й город напрямую
соединены, и `isConnected[i][j] = 0` в противном случае.

Верните общее количество провинций.

---

## Ограничения

- Размер матрицы `n` — это целое число в диапазоне от `1` до `200`
- В качестве значений в матрице используются только `1` b `0`
- `isConnected[i][j] == isConnected[j][i]`

---

## Примеры

{% tabs %}

{% tab "Пример №1" %}

**Входные данные**

```
isConnected = [[1,1,0],[1,1,0],[0,0,1]]
```

**Ответ**: `2`

{% endtab %}

{% tab "Пример №2" %}

**Входные данные**

```
isConnected = [[1,0,0],[0,1,0],[0,0,1]]
```

**Ответ**: `3`

{% endtab %}
{% endtabs %}

---

## Решение

У этой задачи достаточно много решений и все они достаточно сложные. Но мы можем легко решить задачу, если знать как
работает структура данных `DisjointSet` (также известная как `UnionFind`).

Подробно про реализацию структуры данных `DisjointSet` можно посмотреть [в этом посте](../disjoint_set).

Как только мы имеем реализованную структуру данных, задача становится крайне легкой.

Алгоритм решения:

- Создать инстанс структуру данных `DisjointSet`.
- Определить переменную `numberOfProvinces` равную `n` в начале состояния. Далее мы будем уменьшать ее значение при
объединении городов в провинцию.
- Так как главная диагональ матрицы `isConnected` отображает соединение каждого города с самим собой, то мы можем ее не
рассматривать. Также нам не требуется проверять всю матрицу так как `isConnected[i][j] == isConnected[j][i]`, поэтому
будем перебирать элементы над главной диагональю. Для этого запустим цикл в цикле по `i` и `j` от `i = 0` и
`j = i + 1`.
- Если два города соединены (`isConnected[i][j] == 1`) и они уже не находятся в одном множестве, то мы объединяем эти
города в множество при помощи метода — то мы объединяем города `i` и `j` в одну провинцию.

В итоге после перебора элементов матрицы переменная `numberOfProvinces` будет показывать количество получившихся
провинций.

### Оценка сложности

**По времени**

- Перебор матрицы занимает <code>O(n<sup>2</sup>)</code>
- Поиск методом find занимает `O(n)`
- Объединение методом union занимает `O(n)`

Общая сложность по памяти <code>O(n<sup>2</sup>)</code>.

**По памяти**

Сложность `O(n)` для хранения состояния структуры `DisjointSet`.
37 changes: 37 additions & 0 deletions content/blog/number_of_provinces/ts/solution.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { findCircleNum } from './solution';

type Suit = {
name: string
isConnected: number[][]
expected: number
}

const suits: Suit[] = [
{
name: "example_1",
isConnected: [
[1, 1, 0],
[1, 1, 0],
[0, 0, 1],
],
expected: 2,
},
{
name: "example_2",
isConnected: [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
],
expected: 3,
},
]

describe('findCircleNum', () => {
suits.forEach(suit => {
test(suit.name, () => {
const res = findCircleNum(suit.isConnected)
expect(res).toEqual(suit.expected);
})
})
})
46 changes: 46 additions & 0 deletions content/blog/number_of_provinces/ts/solution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
class DisjointSet {
readonly nodes: number[];

constructor(size: number) {
this.nodes = new Array(size);

for (let i = 0; i < size; i++) {
this.nodes[i] = i;
}
}

find(node: number): number {
return this.nodes[node];
}

union(x: number, y: number): void {
const rootX = this.find(x);
const rootY = this.find(y);

if (rootX !== rootY) {
for (let i = 0; i < this.nodes.length; i++) {
if (this.nodes[i] === rootY) {
this.nodes[i] = rootX;
}
}
}
}
}

export const findCircleNum = (isConnected: number[][]): number => {
const n = isConnected.length;
const disjointSet = new DisjointSet(n);

let numberOfProvinces = n;

for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
if (isConnected[i][j] == 1 && disjointSet.find(i) != disjointSet.find(j)) {
numberOfProvinces -= 1;
disjointSet.union(i, j);
}
}
}

return numberOfProvinces
}
Binary file added public/images/previews/number_of_provinces.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.