leetcode_315_计算右侧小于当前元素的个数

这个题需要用到:

①逆序对的解决思路。

②归并的分治策略

③特殊的排序,不动原数组nums,而是另开辟一个保存下标的数组index。并且排序也是在这个index数组上进行的,原数组只是用来比较。


题目:

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是  nums[i] 右侧小于 nums[i] 的元素的数量。

示例:

输入: [5,2,6,1]
输出: [2,1,1,0] 
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


思路:这个题目很难,主要是需要使用到逆序对这种策略。在刚看到题目的时候有点懵,主要还是接触的太少了。后来看题目提示,根据提示现学了有关逆序对策略以及树状数组的相关知识,奈何树状数组太麻烦了233333,最后选择了使用逆序对的策略来解决。

首先介绍逆序对是个什么问题:

如果给定一个数组序列,nums[0.....n]。对于 o<=i,j < n,如果i<j,而且nums[i] > nums[j],则称nums[i]与nums[j]构成一对逆序对。

如给定: [2, 1, 4, 6, -1]。此时逆序对有:2与1,2与-1,1与-1,4与-1,6与-1。总共五对。

接下来介绍策略:

因为在更新当前位置逆序对数目时,由于使用分治策略,所以“原数组”分为前半段有序序列和后半段有序序列,此时只需要使用两个指针,使用归并排序的想法,只是在排序的过程中增加一行更新当前位置的大于的右边元素的数目。

如:[5, 2, 6, 1]

划分为 5,2 | 6,1

左指针从5处开始,右指针从6处开始,

如果nums[left] <= nums[right],则和归并排序无二致。

如果nums[left] > nums[right],又因为右半段是已经有序的,所以right后面的数也肯定小于nums[left],

假设右半段序列的右端点为right_len,则此时值为5处的数目更新为 right_len - right。

Note:最好绘图。绘图出来特别好理解。。。

对应的更新2处的数目。


说的不怎么清楚,大家最好画图去理解,还是很好理解的。

主要理解:

①排序时不动原数组,而是动下标索引数组。

②因为在更新的时候,左右两端子序列已经是有序的了,所以可以直接根据right指针的下标位置与整个右半段子序列的长度的关系来更新当前位置处的大于右边的元素数目。


实现代码:

JAVA版:

package leetcode;

/*
USER:LQY
DATE:2020/7/11
TIME:8:25
*/
import java.util.ArrayList;
import java.util.List;

public class leetcode_315_important {

    public List<Integer>countSmaller(int []nums){
        List<Integer> ans = new ArrayList<>();
        int len = nums.length;
        if(len == 0) return ans;
        count = new int[len];
        temp = new int[len];
        index = new int[len];
        //初始化index数组
        for(int i = 0;i < len;i++)
            index[i] = i;

        if(len%2 == 0){
            //偶数
            suber(nums, 0, len-1);
        }else{
            //奇数长度最后一个另外处理
            suber(nums, 0, len-2);
            //再处理最后一个
            int last = nums[len-1];
            for(int i = 0;i < len-1;i++){
                if(nums[i] > last){
                    count[i]++;
                }
            }
        }
        for(int i: count){
            System.out.print(i+"\t");
            ans.add(i);
        }
        System.out.println();

        return ans;
    }

    private int []count;
    private int []index;  //存储原始数组的下标。排序的时候交换下标而原数组不变
    private int []temp;  //和一般的归并排序一样,需要一个额外的数组空间
    public void suber(int []nums, int left, int high){
        if(left >= high) return;
        int mid = (left + high) >> 1;  //用移位计算比较好

        suber(nums, left, mid);
        suber(nums, mid+1, high);
        mergeAndCount(nums, left, mid+1, high);

    }
    public void mergeAndCount(int []nums, int left, int mid, int right){

        //这样会超时
//        for(int i = left;i < mid;i++){
//            int lval = nums[i];
//            for(int j = mid;j <= right;j++){
//                if(lval > nums[j]) count[i]++;
//            }
//        }
        int i = left;
        int j = mid;
        int newi = left;
        while(i<mid && j<=right){
            if(nums[index[i]] <= nums[index[j]]){
                temp[newi++] = index[j++];
            }else{
                count[index[i]] += right - j + 1;   //这个地方是与传统归并排序唯一不同的地方。也是用来统计的关键
                temp[newi++] = index[i++];
            }
        }
        while(i < mid){
            temp[newi++] = index[i++];
        }
        while(j <= right){
            temp[newi++] = index[j++];
        }
        //此时,从left->right的index对应的temp数组已经排好序。
        // 这一趟排完序后将temp中的部分有序数列复制到index中
        for(int k = left;k <= right;k++){
            index[k] = temp[k];
        }
    }



    public static void main(String[] args) {
        int[] nums = new int[]{5183, 2271, 3067, 539, 8939, 2999, 9264, 737, 3974};
        leetcode_315_important solution = new leetcode_315_important();
        List<Integer> countSmaller = solution.countSmaller(nums);
        System.out.println(countSmaller);
    }
}

Python版:

class solution_315:
    
    def countSmaller(self, nums: list[int]) ->list[int]:
        lens = len(nums)
        ans = [0 for _ in range(lens)]
        
        if(lens == 0):
            return ans
        count = [None for _ in range(lens)]
        temp = [None for _ in range(lens)]
        index = [i for i in range(0, lens)]
        #初始化index
        for i in range(lens):
            index[i] = i
        
        self.suber(nums, index, temp, count, 0, len-1)
        for i in count:
            ans.add(i)
            
        return ans
        
    def suber(self, nums, index, temp, count, left, right):
        if(left >= right):
            return
        mid = (left + right) >> 1
        
        self.suber(nums, index, temp, count, left, mid)
        self.suber(nums, index, temp, count, mid+1, right)
        
        self.mergeAndCount(nums, index, temp, count, left, mid+1, right)
    
    #从大到小排序
    def mergeAndCount(self, nums, index, temp, count, left, mid, right):
        
        j = mid
        newi = i = left
        while(i<mid and j<=right):
            if(nums[index[i]] <= nums[index[j]]):
                temp[newi] = index[j]
                newi += 1
                j += 1
            else:
                count[index[i]] += right - j + 1
                temp[newi] = index[i]
                newi += 1
                i += 1
        
        while(i < mid):
            temp[newi] = index[i]
            newi += 1
            i += 1
        while(j <= right):
            temp[newi] = index[j]
            newi += 1
            j += 1
        
        for k in range(left, right+1):
            index[k] = temp[k]
        
            
        
            
    

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值