0
点赞
收藏
分享

微信扫一扫

算法题不等式计数问题常见解法-归并排序

类型1:单个边界范围

f(i)<d(j)这种格式的不等式,算法题经常询问我们满足这样的数对有多少。中间的符号也可换成任何等号不等号,也同样适用

怎么计算呢?本质上,使用归并排序就是下面这张图:

  1. 对于绿色块,右侧可能比它大的元素数目是 红色+紫色+黄色,恰好都是某次归并排序的一半的元素

算法题不等式计数问题常见解法-归并排序_区间和

  1. 那么问题转换为,如何在一次归并中,找到比左侧元素小于大于等于的元素数目,也就是两个有序数组 a和b中,找到a中元素比b中元素少的数目
  2. 双循环肯定是可以的,但是这样复杂度会增加,没有利用到有序这个条件。有序+双数组问题,比较容易想到的一个解法是双指针,那么我们可以试试双指针求解:

算法题不等式计数问题常见解法-归并排序_区间和_02

  • 对于上方的指针i我们可以一直往右移动,然后b[j] 在比 a[i] 小的时候,右移,不回头
  • 为什么可以不回头呢?因为 如果 a[i]>a[j] , 同时因为有序的原因,a[i+1]>=a[i],那么我们可以把这个式子连起来得到:a[i+1]>=a[i]>a[j],也就是 a[i+1]>a[j] 恒成立,因此前面已经访问过的j必然满足条件小于新元素 a[i+1],j 不回头后移即可
  • 找到分割点后如何计数,从j到 b[j] 的起点这一段都是比a[i]大的,因此我们有 j-(mid+1)

是不是其实很简单呢?一个 递归+双指针 就全都搞定了。那么试试做一做练习:

​​力扣-315.计算右侧小于当前元素的个数​​

算法题不等式计数问题常见解法-归并排序_i++_03

这题比较麻烦一点就是要知道原索引的位置,这个只要我们把数值和索引一起移动即可

class Solution {
int[] res;
int[] nums;
int[] ranks;
int[] copy;
int[] copyRank;
public List<Integer> countSmaller(int[] nums) {
int n = nums.length;
res = new int[n];

this.nums = nums;
ranks = new int[n];
for(int i = 0; i < n; i++) ranks[i] = i;
copy = new int[n];
copyRank = new int[n];
sort(0,n-1);

List<Integer> ans = new ArrayList<>();
for(int v:res) ans.add(v);
return ans;
}

private void sort(int start, int end){
if(start == end) return;
int mid = (end-start)/2+start;
sort(start,mid);
sort(mid+1,end);
int id = start;
int i = start,j=mid+1;
while(i <= mid){
while (j<=end&&nums[j]<nums[i]) {
copy[id]=nums[j];
copyRank[id++]=ranks[j++];
}
res[ranks[i]] += j-mid-1;
copy[id]=nums[i];
copyRank[id++]=ranks[i++];
}

while(j<=end) {
copy[id]=nums[j];
copyRank[id++]=ranks[j++];
}

for(int t = start; t <= end; t++){
nums[t]=copy[t];
ranks[t]=copyRank[t];
}
}
}


类型2:两个边界范围

和单个边界的类似,同样可以采用归并排序的办法。不同的是,需要使用双指针,同时处理低位和高位。弄不清的情况,也可以直接化简为一个边界判断:f(lower,upper)=f(upper)-f(lower-1)

​​力扣-327. 区间和的个数​​

算法题不等式计数问题常见解法-归并排序_归并排序_04

这题比较麻烦的是有个区间和,需要想深一层。

  1. 区间和问题,可考虑使用前缀和相减去判断
  2. 如果都是正数的话,只需要判断右侧的区间和比当前的大 [lower, upper] 即可,但是本题有负数
  3. 存在负数的情况,可考虑不等式 v[i]+lower<=v[j]<=v[i]+upper 可使用归并排序判断右侧比当前大 [lower,upper] 区间和:

class Solution {
long[] psum;
int lower;
int upper;
long ans = 0;
long[] temp;
public int countRangeSum(int[] nums, int lower, int upper) {
int n = nums.length;
psum = new long[n+1];
for(int i = 0; i < n; i++) psum[i+1]=psum[i]+nums[i];
this.lower = lower;
this.upper = upper;
temp = new long[n+1];
sort(0,n);
return (int)ans;
}

private void sort(int start, int end){
if(start >= end) return;

int mid = (end-start)/2+start;
sort(start,mid);
sort(mid+1,end);
int idLow = mid+1;
int idHigh = mid+1;
for(int i = start; i <= mid; i++){
while(idLow<=end&&psum[i]+lower>psum[idLow]) idLow++;
while(idHigh<=end&&psum[i]+upper>=psum[idHigh]) idHigh++;
ans += Math.max(0,idHigh-idLow);
}
int id = start,j=mid+1;
for(int i = start; i <= mid; i++){
while(j<=end&&psum[j]<psum[i]) temp[id++]=psum[j++];
temp[id++]=psum[i];
}

while(j<=end)temp[id++]=psum[j++];
System.arraycopy(temp,start,psum,start,end-start+1);
}
}

其他练习眼力:​​493.翻转对​​

算法题不等式计数问题常见解法-归并排序_归并排序_05

不等式=>归并排序,所以会了么?看到不等式就想归并的可行性,一眼题就是这样产生的

另一题练习眼力:​​力扣-1649.通过指令创建有序数组​​

算法题不等式计数问题常见解法-归并排序_区间和_06

两个不等式:min(cnt(a[i]<a[j]),cnt(a[i]>a[j])),不等式+数目=>归并

类型3:不等式变换

有时候,题目故意把不等式写的看起来比较复杂度样子,此时尝试变换,得到 f(i)[比较运算符]d(j)这样的式子,就可以尝试使用归并排序,比如:​​力扣-2426. 满足不等式的数对数目​​

算法题不等式计数问题常见解法-归并排序_i++_07

nums1[i] - nums1[j] <= nums2[i] - nums2[j] + diff,这个转成:nums1[i] - nums2[i]-diff <= nums1[j] - nums2[j],然后对于任意位置的num1[i]-nums2[i],这个很好求,可转变为一个新的数组,arr[i]-diff<=arr[j],其中arr[i]=nums1[i]-nums2[i]

然后开始分析这个式子:arr[i]-diff<=arr[j],假设两个指针都放在右侧,左边减小依然满足式子,可以对应左移右侧的指针j。arr[j]右侧的元素arr[k]都大于等于arr[j],必然满足这个式子,分析完毕,继续用归并排序:

class Solution {
int[] arr;
long ans=0L;
int diff;
int[] temp;
public long numberOfPairs(int[] nums1, int[] nums2, int diff) {
//nums1[i]-nums2[i]<=nums1[j]-nums2[j]+diff
//a[i]-diff<=a[j]
int n = nums1.length;
arr = new int[n];
temp = new int[n];
this.diff = diff;
for(int i = 0; i < n; i++) arr[i] = nums1[i]-nums2[i];
sort(0,n-1);
return ans;
}

private void sort(int start, int end){
if(start == end) return;
int mid = (end-start)/2+start;
sort(start,mid);
sort(mid+1,end);

int j = end;
for(int i = mid; i >= start; i--){
while (j>=mid+1&&arr[i]-diff<=arr[j]) --j;
ans += end-j;
}

int id = start;
j = mid+1;
for(int i = start; i <= mid; i++){
while(j<=end &&arr[i]>arr[j]) temp[id++]=arr[j++];
temp[id++]=arr[i];
}
while(j<=end) temp[id++]=arr[j++];
System.arraycopy(temp,start,arr,start,end-start+1);
}
}


举报

相关推荐

0 条评论