[LeetCode] 493. Reverse Pairs 翻转对
Given an array nums, we call (i, j) an important reverse pair if i < j and nums[i] > 2*nums[j].
You need to return the number of important reverse pairs in the given array.
Example1:
Input: [1,3,2,3,1]
Output: 2
Example2:
Input: [2,4,3,5,1]
Output: 3
Note:
- The length of the given array will not exceed
50,000. - All the numbers in the input array are in the range of 32-bit integer.
这道题第一次做的时候就感觉和 Count of Smaller Numbers After Self 很像,开始也是用的二分查找法来建立有序数组,LintCode 上也有一道非常类似的题 Reverse Pairs 翻转对,虽然那道题没有加2倍的限制条件,但是思路都是一样的。于是我就开始稍稍改了一下,二分搜索的时候是搜索 nums[i]/2.0,加入数组的时候是加入原数 num,我记得当时是可以通过 OJ 的,但是后来 OJ 变的严格起来了,什么二分搜索啊,BST 啊之类的解法统统弄死,据目前来看,侥幸存活下来的方法就只有 BIT 和 MergeSort 这两种方法了。对于这类问题的本质的分析,大神 fun4LeetCode 的帖子讲的非常好,这里也借鉴一下大神的讲解来帮助理解吧。
对于这类数组找数字之间的关系的题,一种很好的解题思路就是拆分数组来解决子问题,就是把大问题拆成小问题,把小问题一一解决来,大问题的答案也就出来了,这么说起来是不是有点像 DP 的感觉,但是不同的是,博主感觉此类问题很难找到 DP 的状态转移方程吧。fun4LeetCode 大神归纳了两种拆分方法,一种叫顺序重现关系(Sequential Recurrence Relation),用式子表示是 T(i, j) = T(i, j - 1) + C。这里的C就是处理最后一个数字的子问题,那么用文字来描述就是“已知翻转对的第二个数字为 nums[j], 在子数组 nums[i, j - 1] 中找翻转对的第一个数字”,这里翻转对的两个条件中的顺序条件已经满足,就只需要找比 2*nums[j] 大的数即可。当然最森破的方法就是线性扫描,但这样整个时间复杂度就会上升到令人发指的 O(n2),这又怎么能逃过连 Binary Search 都不放过的 OJ 的魔爪。由于二分搜索和 BST 等方法已经被 OJ 阉割,所以我们只能用树状数组 Binary Indexed Tree 来做了。关于 BIT,我之前有篇博客 Range Sum Query - Mutable 应该讲的比较清楚了,如果弄懂了那篇博客,我们对 BIT 的机制也应该有个基本的了解,由于 BIT 的存储方式不是将数组中的数字对应的一一存入,而是有的对应存入,有的是存若干个数字之和,其设计初衷之一就是要在 O(lgn) 的时间复杂度下完成求和运算。那么我们该如何利用这一特性呢,这跟这道题又有什么关系呢,别着急,博主会慢慢解释。
首先我们应该确定一个遍历的方向,这里博主推荐从后往前遍历数组,这样做的好处是对于当前遍历到的数字,在已遍历过的数字中找小于当前数字的一半 (nums[i]/2.0) 的数字,这样的遍历方向也能跟上面的顺序重现关系的定义式统一起来。当然如果你想强行从前往后遍历,也不是不行,那么就需要在已遍历的数字中找大于当前数字的二倍 (nums[i]*2) 的数字就行了。由于我们要在之前遍历过的数字中找符合条件的数字,怎么样利用 BIT 的特性来快速的找到是这种解法的最大难点。我们需要将之前遍历过的数字存入 BIT 中,怎么存是难点。由于之前那篇博客我们知道 BIT 用 update 函数来存数,需要提供要存入的位置和要存入的数字这两个参数,那么这里难道我们就按照数字在原数组中的位置存入 BIT 吗,这样做毫无意义!我们要存的是该数字在有序数组中的位置,而且存入的也不是该数字本身,而是该数字出现的次数1。我们用题目中的第一个例子来说明,我们先给数组排序,得到:
1 1 2 3 3
对于每一个数字我们要确定其在 BIT 中的位置,由于有重复数字的存在,那么每个数字对应的位置就是其最后出现的位置,而且因为 BIT 是从1开始的,并不是像一般的数组那样从0开始,那么有如下对应关系:
1->2, 2->3, 3->5
那么当我们遇到数字1了,就 update(2,1),遇到数字2了,就 update(3,1),遇到数字3了,就 update(5,1)。我们之前解释了并不把数字本身存入 BIT,而是将其对应的位置存入 BIT,真正存入的数字是1,这样方便累加,而且由于1是固定的,在下面的代码中就不用将1当作函数的参数了。这样我们知道了如果存入数字,那么我们在遍历到新数字时,为了得到符合要求的数字的个数,需利用 BIT 的 getSum 函数。getSum 函数需要提供一个位置参数,可以返回该位置之前的所有数之和。同理,我们提供的参数既不是当前遍历到的数字本身,也不是其在原数组中的位置,而是该数字的一半 (nums[i]/2.0) 在有序数组中的正确位置,可以用 lower_bound 函数来找第一个不小于目标值的位置,当然我们也可以自己写个二分搜索的子函数来代替 lower_bound 函数。比如我们当前遍历到的数字是3,那么我们在有序数组中找 1.5 的位置,返回是2,此时我们在 BIT 中用 getSum 来返回位置2之前的数字之和,返回几就表示有几个小于 1.5 的数字。讲到这里基本上这种解法的核心内容都讲完了,如果你还是一头雾水,那么就是博主的表述能力的问题了(沮丧脸:()。那么博主只能建议你带实例一步一步去试,看看每一步操作后 BIT 中的结果是啥,下面就列出这些内容:
update(2,1) -> BIT: 0 0 1 0 1 0
update(5,1) -> BIT: 0 0 1 0 1 1
update(3,1) -> BIT: 0 0 1 1 2 1
update(5,1) -> BIT: 0 0 1 1 2 2
update(2,1) -> BIT: 0 0 2 1 3 2
解法一:
class Solution {
public:
int reversePairs(vector<int>& nums) {
int res = , n = nums.size();
vector<int> v = nums, bit(n + );
sort(v.begin(), v.end());
unordered_map<int, int> m;
for (int i = ; i < n; ++i) m[v[i]] = i + ;
for (int i = n - ; i >= ; --i) {
res += getSum(lower_bound(v.begin(), v.end(), nums[i] / 2.0) - v.begin(), bit);
update(m[nums[i]], bit);
}
return res;
}
int getSum(int i, vector<int>& bit) {
int sum = ;
while (i > ) {
sum += bit[i];
i -= (i & -i);
}
return sum;
}
void update(int i, vector<int>& bit) {
while (i < bit.size()) {
bit[i] += ;
i += (i & -i);
}
}
};
fun4LeetCode 大神归纳的第二种方法叫做分割重现关系 (Partition Recurrence Relation),用式子表示是 T(i, j) = T(i, m) + T(m+1, j) + C。这里的C就是处理合并两个部分的子问题,那么用文字来描述就是“已知翻转对的两个数字分别在子数组 nums[i, m] 和 nums[m+1, j] 之中,求满足要求的翻转对的个数”,这里翻转对的两个条件中的顺序条件已经满足,就只需要找到满足大小关系的的数对即可。这里两个数字都是不确定的,如果用暴力搜索肯定会被 OJ 唾弃。但是如果两个子数组是有序的,那么我们可以用双指针的方法在线性时间内就可以统计出符合题意的翻转对的个数。要想办法产生有序的子数组,那么这就和 MergeSort 的核心思想完美匹配了。我们知道混合排序就是不断的将数组对半拆分成子数组,拆到最小的数组后开始排序,然后一层一层的返回,最后原数组也是有序的了。这里我们在混合排序的递归函数中,对有序的两个子数组进行统计翻转对的个数,区间 [left, mid] 和 [mid+1, right] 内的翻转对儿个数就被分别统计出来了,此时还要统计翻转对儿的两个数字分别在两个区间中的情况,那么i遍历 [left, mid] 区间所有的数字,j则从 mid+1 开始检测,假如 nums[i] 大于 nums[j] 的二倍,则这两个数字就是翻转对,此时j再自增1,直到不满足这个条件停止,则j增加的个数就是符合题意的翻转对的个数,所以用当前的j减去其初始值 mid+1 即为所求,然后再逐层返回,这就完美的实现了上述的分割重现关系的思想。整个的写法非常的简洁,实在是太叼了。博主的直觉表明,fun4LeetCode 大神肯定是国人,不要问我为什么,因为这么强的肯定是中国人,哈~
解法二:
class Solution {
public:
int reversePairs(vector<int>& nums) {
return mergeSort(nums, , nums.size() - );
}
int mergeSort(vector<int>& nums, int left, int right) {
if (left >= right) return ;
int mid = left + (right - left) / ;
int res = mergeSort(nums, left, mid) + mergeSort(nums, mid + , right);
for (int i = left, j = mid + ; i <= mid; ++i) {
while (j <= right && nums[i] / 2.0 > nums[j]) ++j;
res += j - (mid + );
}
sort(nums.begin() + left, nums.begin() + right + );
return res;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/493
类似题目:
Count of Smaller Numbers After Self
参考资料:
https://leetcode.com/problems/reverse-pairs/
https://leetcode.com/problems/reverse-pairs/discuss/97287/C%2B%2B-with-iterators
LeetCode All in One 题目讲解汇总(持续更新中...)
[LeetCode] 493. Reverse Pairs 翻转对的更多相关文章
- 第二周 Leetcode 493. Reverse Pairs(HARD)
leetcode 493跟经典的逆序对问题没有什么区别, 首先考虑对数组前半部和后半部求逆序对数,若能保证两段数组都有序,则显然可以在线性时间内求出对数. 所以我们采用归并排序的方法,一方面让数组有序 ...
- 493 Reverse Pairs 翻转对
给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对.你需要返回给定数组中的重要翻转对的数量.示例 1:输入: ...
- leetcode 493 Reverse Pairs
题意:给定一个数组nums,求若 i<j and nums[i] > 2*nums[j] 的逆序对. Note: 数组的长度不会超过50,000 不愧是hard模式的题目,虽然已经知道可以 ...
- [LeetCode] Reverse Pairs 翻转对
Reverse Pairs 翻转对 题意 计算数组里面下标i小于j,但是i的值要大于j的值的两倍的搭配的个数(也就是可能会有多种搭配):网址 做法 这道题显然是不允许使用最简单的方法:两次循环,逐次进 ...
- [LeetCode] 190. Reverse Bits 翻转二进制位
Reverse bits of a given 32 bits unsigned integer. For example, given input 43261596 (represented in ...
- [LintCode] Reverse Pairs 翻转对
For an array A, if i < j, and A [i] > A [j], called (A [i], A [j]) is a reverse pair.return to ...
- 493. Reverse Pairs(BST, BIT, MergeSort)
Given an array nums, we call (i, j) an important reverse pair if i < j and nums[i] > 2*nums[j] ...
- 【leetcode】493. Reverse Pairs
题目如下: 解题思路:本题要求的是数组每个元素和所有排在这个元素后面的元素的值的二倍做比较.我们可以先把数组所有元素的二倍都算出来,存入一个新的数组newlist,并按升序排好.而后遍历nums数组的 ...
- [LeetCode] 7. Reverse Integer 翻转整数
Given a 32-bit signed integer, reverse digits of an integer. Example 1: Input: 123 Output: 321 Examp ...
随机推荐
- oracle导入Excel表文本数据
首先导Excel表数据要先建和Excel表字段对应的表,然后将Excel表另存为Txt文本, 然后在Plsql客户端点击工具->文本导入器 然后这里要选择用户及其表,点击导入数据就可以
- learning rate warmup实现
def noam_scheme(global_step, num_warmup_steps, num_train_steps, init_lr, warmup=True): ""& ...
- Pandas 学习 第9篇:DataFrame - 数据的输入输出
常用的数据存储介质是数据库和csv文件,pandas模块包含了相应的API对数据进行输入和输出: 对于格式化的平面文件:read_table() 对于csv文件:read_csv().to_csv() ...
- opencv 图像旋转
理论 http://www.cnblogs.com/wangguchangqing/p/4045150.html 翻开任意一本图像处理的书,都会讲到图像的几何变换,这里面包括:仿射变换(affine ...
- 大白话说GIT常用操作,常用指令git操作大全
列一下在开发中用的比较多的git指令 git clone https://github.com/chineseLiao/Small-career // 克隆远程仓库到本地 git add . // 把 ...
- maven 学习---Maven配置之pom文件配置包含和排除测试
本文地址:http://blog.csdn.net/wirelessqa/article/details/14057083 包含(Inclusions )默认情况下Surefire Plugin会自动 ...
- 10.InfluxDB-InfluxQL基础语法教程--OFFSET 和SOFFSET子句
本文翻译自官网,官网地址:(https://docs.influxdata.com/influxdb/v1.7/query_language/data_exploration/) OFFSET 和SO ...
- long类型在内存中占8个字节,float类型在内存中占4个字节,为什么long还要比float小呢?
结论:数值范围大小和占用的字节没有关系. float类型的范围: 负数:-3.402823E38~-1.401298E-45 整数:0 正数:1.401298E-45~3.402823E38 long ...
- mysql update运行超时解决方案
问题描述: 今天update(修改)mysql数据库中一张表时,发现时间很长,而且会失败.报错:Error Code: 1205. Lock wait timeout exceeded; try re ...
- Mongodb分片 学习小结
前一篇 https://www.cnblogs.com/frx9527/p/mongodb.html 学会搭建复制集Replication之后,就可以学习分片Sharding了. 教程建议看官方文档: ...