diff --git a/README.md b/README.md index 3376db8..0295d87 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,7 @@ My LeetCode solutions with Chinese explanation. 我的LeetCode中文题解。 | 310 |[Minimum Height Trees](https://leetcode.com/problems/minimum-height-trees/)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/310.%20Minimum%20Height%20Trees.md)|Medium| | | 312 |[Burst Balloons](https://leetcode.com/problems/burst-balloons/)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/312.%20Burst%20Balloons.md)|Hard| | | 313 |[Super Ugly Number](https://leetcode.com/problems/super-ugly-number/)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/313.%20Super%20Ugly%20Number.md)|Medium| | +| 315 |[Count of Smaller Numbers After Self](https://leetcode.com/problems/count-of-smaller-numbers-after-self/)|[C++](solutions/315.%20Count%20of%20Smaller%20Numbers%20After%20Self.md)|Hard| | | 318 |[Maximum Product of Word Lengths](https://leetcode.com/problems/maximum-product-of-word-lengths/)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/318.%20Maximum%20Product%20of%20Word%20Lengths.md)|Medium| | | 319 |[Bulb Switcher](https://leetcode.com/problems/bulb-switcher/)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/319.%20Bulb%20Switcher.md)|Medium| | | 322 |[Coin Change](https://leetcode.com/problems/coin-change/)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/322.%20Coin%20Change.md)|Medium| | diff --git a/solutions/315. Count of Smaller Numbers After Self.md b/solutions/315. Count of Smaller Numbers After Self.md new file mode 100644 index 0000000..31f2f58 --- /dev/null +++ b/solutions/315. Count of Smaller Numbers After Self.md @@ -0,0 +1,271 @@ +# [315. Count of Smaller Numbers After Self](https://leetcode.com/problems/count-of-smaller-numbers-after-self/) + +# 思路 +给定一个数组,计算每个数字右边所有小于这个数字的个数。 + +## 思路一、BST + +我们从后往前遍历数组,如果某个数字的右边所有数字都是有序的,那么我们就可以使用二分计算该数字右边所有小于这个数字的个数,但是如何维护有序呢,如果使用插入排序,那每次维护有序数组的时间复杂度为O(n),所以总的复杂度为O(n^2),这和暴力法是一样的。 + +除了有序数组之外,二叉搜索树也可以进行二分。而每次向二叉树里插入元素的复杂度平均为O(logn),所以总的时间复杂度平均就为O(nlogn)。另外,每个节点需要存放以这个节点为根的树有多少个节点。 + +时间复杂度平均为O(nlogn),空间复杂度为O(n)。注意BST可能会退化成链表,这样时间复杂度就为O(n^2)了。 + +## 思路二、归并排序 + +如果某个元素`nums[i]`大于其右边的某个元素`nums[j]`(j > i),那么这元素``就构成了一个逆序对,所以我们只需要求出以`nums[i]`为第一个元素的逆序对个数。 + +求逆序对最经典的方法就是分治,即归并排序。所以这题我们也可以用归并排序,只需要新增一行代码:在进行`merge`时,记录有多少个以`nums[i]`开头的逆序对。 + +时间复杂度为O(nlogn),空间复杂度为O(n)。 + +## 思路三、线段树/树状数组 + +此题还可以用线段树/树状数组做,我们知道线段树和树状数组可以求前缀和,而这题可以转换成求前缀和。具体转换过程如下: + +1. 我们先遍历一遍数组,确定数组中元素的最小值`MIN`和`MAX`,然后想象有一个大小为`MAX - MIN`的全0数组`arr`,在这个数组上构建线段树/树状数组; +2. 然后从后往前遍历数组nums,将`arr[nums[i]]++`,更新线段树/树状数组,这样`arr`在区间`(nums[i], MAX]`的元素和即为`nums[i]`右侧它小的元素个数。 + +时间复杂度O(nlogN),空间复杂度O(N),其中`N = MAX - MIN`; + +# C++ +## 思路一 +``` C++ +struct BstNode{ + int val, node_num; // node_num记录这棵树有多少节点 + BstNode *left, *right; + BstNode(int x): val(x), node_num(1), left(NULL), right(NULL){} +}; + +class Solution { +private: + void BST_insert(BstNode *root, BstNode *node){ + root -> node_num += 1; + if(node -> val >= root -> val){ // 插入到右子树 + if(root -> right) BST_insert(root -> right, node); + else root -> right = node; + } + else{ // 插入到左子树 + if(root -> left) BST_insert(root -> left, node); + else root -> left = node; + } + } + + int count(BstNode *root, int target){ + if(!root) return 0; + + if(root -> val < target) + return 1 + (root -> left == NULL ? 0 : root -> left -> node_num) \ + + count(root -> right, target); + + else return count(root -> left, target); + } + +public: + vector countSmaller(vector& nums) { + vectorres(nums.size(), 0); + if(nums.empty()) return res; + BstNode *root = new BstNode(nums.back()); + for(int i = nums.size() - 2; i >= 0; i--){ + res[i] = count(root, nums[i]); + BstNode *node = new BstNode(nums[i]); + BST_insert(root, node); + } + return res; + } +}; +``` + +## 思路二 +``` C++ +class Solution { +private: + vectorres; + void merge_sort(vector>&nums_with_idx, int l, int r){ + if(l >= r) return; + int mid = (l + r) / 2; + merge_sort(nums_with_idx, l, mid); + merge_sort(nums_with_idx, mid+1, r); + merge(nums_with_idx, l, mid, r); + } + + void merge(vector>&nums_with_idx, int l, int mid, int r){ + vector>merged; + int i = l, j = mid + 1; + while(i <= mid && j <= r){ + if(nums_with_idx[i].first <= nums_with_idx[j].first){ + // 与普通归并排序相比新增的一步, 即记录逆序数: + res[nums_with_idx[i].second] += j - mid - 1; // nums[i]大于nums[mid+1,...,j-1] + merged.push_back(nums_with_idx[i++]); + } + else merged.push_back(nums_with_idx[j++]); + } + while(i <= mid){ + res[nums_with_idx[i].second] += j - mid - 1; + merged.push_back(nums_with_idx[i++]); + } + //while(j <= r) merged.push_back(nums_with_idx[j++]); + for(int k = 0; k < merged.size(); k++) nums_with_idx[k+l] = merged[k]; + } + +public: + vector countSmaller(vector& nums) { + vector>nums_with_idx; + res = vector(nums.size(), 0); + for(int i = 0; i < nums.size(); i++) + nums_with_idx.push_back({nums[i], i}); + merge_sort(nums_with_idx, 0, nums.size() - 1); + return res; + } +}; +``` + +## 思路三、树状数组 +[来源](https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/solution/c-shu-zhuang-shu-zu-by-mryx/) +``` C++ +class Solution { +public: + int* tree, n; + int lowbit(int x){ + return x&(-x); + } + void update(int pos, int delta){ + while (pos <= n){ + tree[pos] += delta; + pos += lowbit(pos); + } + } + int getSum(int pos){ + int ret = 0; + while (pos){ + ret += tree[pos]; + pos -= lowbit(pos); + } + return ret; + } + vector countSmaller(vector& nums) { + n = nums.size(); + vector ret(n); + if (n == 0) return ret; + + int minn = -50000, maxx = 50000; + for (int i=0;i=0;--i){ + ret[i] = getSum(nums[i] - minn); + update(nums[i]-minn+1, 1); + } + return ret; + } +}; +``` + +## 思路三、线段树 +[来源](https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/solution/c-xian-duan-shu-jie-fa-by-dufre/) + +``` C++ +struct SegmentTreeNode{ + int start; + int end; + int count; + + SegmentTreeNode* left; + SegmentTreeNode* right; + + SegmentTreeNode(int _start, int _end):start(_start),end(_end) { + count = 0; + left = NULL; + right = NULL; + } +}; + +class Solution { +public: + SegmentTreeNode* build(int start, int end){ + if (start > end) + return NULL; + + SegmentTreeNode* root = new SegmentTreeNode(start, end); + + if (start == end){ + root->count = 0; + }else{ + int mid = start + (end - start)/2; + root->left = build(start, mid); + root->right = build(mid+1, end); + } + return root; + } + + int count(SegmentTreeNode* root, int start, int end){ + if (root == NULL || start>end) + return 0; + if (start==root->start && end==root->end){ + return root->count; + } + int mid = root->start + (root->end - root->start)/2; + int leftcount = 0, rightcount = 0; + + if (start <= mid){ + if (mid < end) + leftcount = count(root->left, start, mid); + else + leftcount = count(root->left, start, end); + } + + if (mid < end){ + if (start <= mid) + rightcount = count(root->right, mid+1, end); + else + rightcount = count(root->right, start, end); + } + + return (leftcount + rightcount); + } + + void insert(SegmentTreeNode* root, int index, int val){ + if (root->start==index && root->end==index){ + root->count += val; + return; + } + + int mid = root->start + (root->end - root->start)/2; + if (index>=root->start && index<=mid){ + insert(root->left, index, val); + } + if (index>mid && index<=root->end){ + insert(root->right, index, val); + } + + root->count = root->left->count + root->right->count; + } + + vector countSmaller(vector& nums) { + vector res; + if (nums.empty()) + return res; + res.resize(nums.size()); + int start = nums[0]; + int end = nums[0]; + + for (int i=1; i=0; i--){ + res[i] = count(root, start, nums[i]-1); + insert(root, nums[i], 1); + } + + return res; + } +}; +```