Постановка задачи
Получив массив целых чисел nums
, верните целочисленный массив counts, где counts[i] — это количество меньших элементов справа от nums[i].
Постановка задачи взята с: https://leetcode.com/problems/count-of-smaller-numbers-after-self
Пример 1:
Input: nums = [5, 2, 6, 1]
Output: [2, 1, 1, 0]
Explanation:
To the right of 5 there are 2 smaller elements (2 and 1).
To the right of 2 there is only 1 smaller element (1).
To the right of 6 there is 1 smaller element (1).
To the right of 1 there is 0 smaller element.
Пример 2:
Input: nums = [-1]
Output: [0]
Пример 3:
Input: nums = [-1, -1]
Output: [0, 0]
Ограничения:
- 1 <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
Объяснение
Решение грубой силы
Наивный подход — использовать вложенные циклы. Внешний цикл выбирает все элементы слева направо. Внутренний цикл выполняет итерацию по всем элементам справа от выбранного элемента и обновляет количество элементов меньше числа.
Фрагмент C++ этого подхода выглядит следующим образом:
vector<int> countSmaller(vector<int>& nums) { int i, j; vector<int> ans; int n = nums.size(); for (i = 0; i < n; i++) ans[i] = 0; for (i = 0; i < n; i++) { for (j = i + 1; j < n; j++) { if (nums[j] < nums[i]) ans[i]++; } } }
Временная сложность этого подхода составляет O(n²). Сложность пространства составляет O(n).
АВЛ-дерево
В этом подходе мы используем самобалансирующееся двоичное дерево поиска, дерево AVL.
Мы перебираем массив справа налево и вставляем элементы один за другим в дерево AVL. При вставке нового ключа в дерево AVL мы сравниваем элемент с корневым значением дерева. Если элемент больше корня, то все узлы в левом поддереве меньше элемента. К вставляемому элементу добавляем размер левого поддерева. Мы рекурсивно используем один и тот же подход для всех узлов.
Фрагмент C++ этого подхода выглядит следующим образом:
struct node { int key; struct node* left; struct node* right; int height; int size; }; int height(struct node* node) { return node != NULL ? node->height : 0; } int size(struct node* node) { return node != NULL ? node->size : 0; } int max(int a, int b) { return (a > b) ? a : b; } struct node* newNode(int key) { struct node* node = (struct node*)malloc(sizeof(struct node)); node->key = key; node->left = NULL; node->right = NULL; node->height = 1; node->size = 1; return node; } struct node* rightRotate(struct node* y) { struct node* leftTree = y->left; struct node* T2 = leftTree->right; leftTree->right = y; y->left = T2; y->height = max(height(y->left), height(y->right)) + 1; leftTree->height = max(height(leftTree->left), height(leftTree->right)) + 1; y->size = size(y->left) + size(y->right) + 1; leftTree->size = size(leftTree->left) + size(leftTree->right) + 1; return x; } struct node* leftRotate(struct node* x) { struct node* y = x->right; struct node* T2 = y->left; y->left = x; x->right = T2; x->height = max(height(x->left), height(x->right)) + 1; y->height = max(height(y->left), height(y->right)) + 1; x->size = size(x->left) + size(x->right) + 1; y->size = size(y->left) + size(y->right) + 1; return y; } int getBalance(struct node* node) { return node != NULL ? height(node->left) - height(node->right) : 0; } struct node* insert(struct node* node, int key, int* count) { if (node == NULL) return (newNode(key)); if (key < node->key) node->left = insert(node->left, key, count); else { node->right = insert(node->right, key, count); *count = *count + size(node->left) + 1; } node->height = max(height(node->left), height(node->right)) + 1; node->size = size(node->left) + size(node->right) + 1; int balance = getBalance(node); if (balance > 1 && key < node->left->key) return rightRotate(node); if (balance < -1 && key > node->right->key) return leftRotate(node); if (balance > 1 && key > node->left->key) { node->left = leftRotate(node->left); return rightRotate(node); } if (balance < -1 && key < node->right->key) { node->right = rightRotate(node->right); return leftRotate(node); } return node; } void constructLower(int nums[]) { int i, j; int n = nums.size(); struct node* root = NULL; for (i = 0; i < n; i++) ans[i] = 0; for (i = n - 1; i >= 0; i--) { root = insert(root, nums[i], &ans[i]); } }
Временная сложность этого подхода составляет O(n * log(n)). Сложность пространства составляет O(n).
Сортировка слиянием
Идея состоит в том, чтобы использовать алгоритм MergeSort. При обратном слиянии массива, как и при сортировке слиянием, мы сортируем элементы массива в порядке убывания и отслеживаем меньшие элементы.
Мы знаем, как работает сортировка слиянием, давайте сначала проверим алгоритм.
Алгоритм
//countSmaller(nums)
- initialize vector pair v
set n = nums.size
set ans = vector<int> ans(n, 0)
- loop for i = 0; i < n; i++
// push the element and the index
- v.push_back({ nums[i], i })
- for end
- mergesort(v, ans, 0, n - 1)
- return ans
// mergesort(v, ans, i, j)
- if i < j
- set mid = (i + j) / 2
//recursively call left half of the array
- mergesort(v, ans, i, mid)
//recursively call right half of the array
- mergesort(v, ans, mid + 1, j)
// merge the array and sort the elements
- merge(v, ans, i, mid, j)
- if end
// merge(v, ans, l, mid, h)
- initialize vector pair temp
set i = l
set j = mid + 1
- loop while i < mid + 1 && j <= h
- if v[i].first > v[j].first
// add up all the elements that are less than this element
// and these elements are present in the 2nd half of the array
- set ans[v[i].second] += (h - j + 1)
- temp.push_back(v[i])
- increment i, i++
- else
- temp.push_back(v[j])
- increment j, j++
- if end
- while end
- loop while i <= mid
- temp.push_back(v[i])
- i++
- while end
- loop while j <= h
- temp.push_back(v[j])
- j++
- while end
- loop for k = 0, i = l; i <= h; i++, k++
- v[i] = temp[k]
- for end
Временная сложность этого подхода составляет O(n * log(n)). Сложность пространства составляет O(n).
Давайте проверим наш алгоритм на C++, Golang и JavaScript.
С++ решение
class Solution { public: void merge(vector<pair<int, int>> &v, vector<int> &ans, int l, int mid, int h) { vector<pair<int, int>> temp; int i = l; int j = mid + 1; while (i < mid + 1 && j <= h) { if (v[i].first > v[j].first) { ans[v[i].second] += (h - j + 1); temp.push_back(v[i]); i++; } else { temp.push_back(v[j]); j++; } } while (i <= mid) temp.push_back(v[i++]); while (j <= h) temp.push_back(v[j++]); for (int k = 0, i = l; i <= h; i++, k++) v[i] = temp[k]; } void mergesort(vector<pair<int, int>> &v, vector<int> &ans, int i, int j) { int mid; if(i < j) { mid = (i + j) / 2; mergesort(v, ans, i, mid); mergesort(v, ans, mid + 1, j); merge(v, ans, i, mid, j); } } vector<int> countSmaller(vector<int>& nums) { vector<pair<int, int>> v; int n = nums.size(); vector<int> ans(n, 0); for (int i = 0; i < n; i++) { v.push_back({nums[i], i}); } mergesort(v, ans, 0, n - 1); return ans; } };
Голанг решение
type Pair struct { Val int Index int } func merge(v []Pair, ans []int, l, mid, h int) { temp := make([]Pair, h - l +1) i, j, k := l, mid + 1, 0 for i <= mid && j <= h { if v[i].Val > v[j].Val { ans[v[i].Index] += h - j + 1 temp[k] = v[i] i++ } else { temp[k] = v[j] j++ } k++ } for i <= mid { temp[k] = v[i] i++ k++ } for j <= h { temp[k] = v[j] j++ k++ } for i := l; i <= h; i++ { v[i] = temp[i - l] } } func mergeSort(v []Pair, ans []int, i, j int) { if i < j { mid := (i + j)/2 mergeSort(v, ans, i, mid) mergeSort(v, ans, mid + 1, j) merge(v, ans, i, mid, j) } } func countSmaller(nums []int) []int { n := len(nums) ans := make([]int, n) v := make([]Pair, n) for index, value := range nums { v[index] = Pair{value, index} } mergeSort(v, ans, 0, n - 1) return ans }
JavaScript-решение
var merge = function(v, ans, l, mid, h) { let temp = []; let i = l, j = mid + 1; while(i < mid + 1 && j <= h) { if(j < v.length && v[i][0] > v[j][0]) { ans[v[i][1]] += (h - j + 1); temp.push(v[i]); i++; } else { temp.push(v[j]); j++; } } while (i <= mid) temp.push(v[i++]); while (j <= h) temp.push(v[j++]); for (let k = 0, i = l; i <= h; i++, k++) v[i] = temp[k]; } var mergesort = function(v, ans, i, j) { let mid; if(i < j) { mid = Math.round((i + j) / 2)-1; mergesort(v, ans, i, mid); mergesort(v, ans, mid + 1, j); merge(v, ans, i, mid, j); } } var countSmaller = function(nums) { let v = []; let n = nums.length; let ans = new Array(n); for(let i = 0; i < n; i++) { let x = [nums[i], i]; v.push(x); } for(let i = 0; i < n; i++) { ans[i] = 0; } mergesort(v, ans, 0, n - 1); return ans; };
Давайте проверим наш алгоритм на нескольких примерах, чтобы увидеть, как работает решение.
Input: nums = [5, 2, 6, 1]
// countSmaller
Step 1: vector<pair<int, int>> v
int n = nums.size()
= 4
vector<int> ans(n, 0)
Step 2: loop for int i = 0; i < n; i++
v.push_back({nums[i], i});
for end
v will be [[5, 0], [2, 1], [6, 2], [1, 3]]
Step 3: mergesort(v, ans, 0, n - 1)
mergesort(v, ans, 0, 3)
// mergesort(v, ans, 0, 3)
Step 4: if i < j
0 < 3
true
mid = i + j / 2
= 0 + 3 / 2
= 1
mergesort(v, ans, i, mid)
mergesort(v, ans, 0, 1)
// mergesort(v, ans, 0, 1)
Step 5: if i < j
0 < 1
true
mid = i + j / 2
= 0 + 1 / 2
= 0
mergesort(v, ans, i, mid)
mergesort(v, ans, 0, 0)
// mergesort(v, ans, 0, 0)
Step 6: if i < j
0 < 1
false
we rollback to step 5
// mergesort(v, ans, 0, 1)
Step 7: if i < j
0 < 1
true
mid = i + j / 2
= 0 + 1 / 2
= 0
mergesort(v, ans, i, mid)
mergesort(v, ans, 0, 0) // evaluated in Step 6
mergesort(v, ans, mid + 1, j)
mergesort(v, ans, 0 + 1, 3)
mergesort(v, ans, 1, 3)
// mergesort(v, ans, 1, 3)
Step 8: if i < j
1 < 3
true
mid = i + j / 2
= 1 + 3 / 2
= 2
mergesort(v, ans, i, mid)
mergesort(v, ans, 1, 2)
This recursion occurs till we get each element as
[5], [2], [6], [1]
We then reach a step where we first merge 6 and 1 and call
merge(v, ans, i, mid, j)
merge(v, ans, 2, 2, 3)
// merge(v, ans, 2, 2, 3)
Step 9: vector<pair<int, int>> temp
int i = l
= 2
int j = mid + 1
= 2 + 1
= 3
loop while i < mid + 1 && j <= h
2 < 2 + 1 && 3 <= 3
true
if v[i].first > v[j].first
v[2].first > v[3].first
6 > 1
true
ans[v[i].second] = ans[v[i].second] + (h - j + 1)
ans[v[2].second] = ans[v[2].second] + 3 - 3 + 1
= 0 + 1
= 1
ans = [0, 0, 1, 0]
temp.push_back(v[i])
temp = [[6, 2]]
i++
i = 3
if end
loop while i < mid + 1 && j <= h
3 < 2 + 1 && 3 <= 3
false
loop while i <= mid
3 <= 2
false
loop while j <= h
3 <= 3
true
temp.push_back(v[j])
temp.push_back(v[3])
temp = [[6, 2], [1, 3]]
j++
j = 4
loop while j <= h
4 <= 3
false
loop for int k = 0, i = l; i <= h;
k = 0
i = 2
i <= h
2 <= 3
v[i] = temp[k]
v[2] = temp[0]
= [6, 2]
i++
i = 3
k++
k = 1
loop for i <= h
3 <= 3
true
k = 1
i = 3
v[i] = temp[k]
v[3] = temp[1]
= [1, 3]
i++
i = 4
k++
k = 2
loop for i <= h
4 <= 3
false
We backtrack to step where we merge 5 and 2.
We then backtrack to step where we merge [5, 2] and [6, 1]
We keep updating the ans array and get the final result as [2, 1, 1, 0].
Первоначально опубликовано на https://alkeshghorpade.me.