LeetCode315之计算右侧小于当前元素的个数(相关话题:归并排序、数状数组)

题目描述

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

示例 1:

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

输入:nums = [-1]
输出:[0]
示例 3:

输入:nums = [-1,-1]
输出:[0,0]
 

提示:

1 <= nums.length <= 105
-104 <= nums[i] <= 104
通过次数50,775提交次数121,503

思路分析

这题的解题思路和之前写过的这篇文章有异曲同工之处快手高频面试题之计算数组小和(相关话题:归并排序,双指针)_击水三千里的专栏-CSDN博客

 代码实现

public class LeetCode315计算右侧小于当前元素的个数 {

		public List<Integer> countSmaller(int[] nums) {

		int n = nums.length;
		List<Integer> ans = new ArrayList();
		
        //index用来记录数字原来对应的下标
		int[] index = new int[n];
        int[] tmpIndex = new int[n];

	
		int[] tmpNum = new int[n];
		//ansArr 数组记录每个元素右侧小于它的元素数量
		int[] ansArr = new int[n];

		for (int i = 0; i < n; i++) {
			index[i] = i;
		}

		mergeSort(0, n - 1, ansArr, tmpNum, nums,tmpIndex, index);

		for (int i = 0; i < n; i++)
			ans.add(ansArr[i]);

		return ans;
	}

	private void mergeSort(int l, int r, int[] ansArr, int[] tmpNum, int[] nums,  int[] tmpIndex, int[] index) {

		// 终止条件
		if (l >= r)
			return;

		// 递归划分
		int m = (l + r) / 2;
		mergeSort(l, m, ansArr, tmpNum, nums, tmpIndex,index);
		mergeSort(m + 1, r, ansArr, tmpNum, nums,tmpIndex, index);

		// 合并阶段
		int i = l, j = m + 1;

        //每次归并前先保留[l,r]内的数组和下标备份
		for (int k = l; k <= r; k++){
            tmpNum[k] = nums[k];
            tmpIndex[k]= index[k];
        }
			

		for (int k = l; k <= r; k++) {
			if (i == m + 1) {
                //把j上的数据放到有序数组
				nums[k] = tmpNum[j];
                //把j上的数据下标放到有序数组对应的下标数组
				index[k]= tmpIndex[j];
				j++;
			} else if (j == r + 1) {
				nums[k] = tmpNum[i];
				index[k]= tmpIndex[i];
				i++;
			} else if (tmpNum[i] > tmpNum[j]) {
				//这里的下标为什么要用tmpIndex而不用index?
				//因为index在本轮归并中会改变原来的值,所以只能先用之前的"备份"
				//当左半部分的元素 tmpNum[i] 大于右半部分的元素 tmpNum[j] 时,意味着 tmpNum[i] 右侧至少有 r - j + 1 个元素是小于它的(因为右半部分是排序好的)
                ansArr[tmpIndex[i]] = ansArr[tmpIndex[i]] + r - j + 1;
				nums[k] = tmpNum[i];
				index[k]= tmpIndex[i];
            
				i++;
			} else {
				nums[k] = tmpNum[j];
				index[k]= tmpIndex[j];
				j++;
			}
		}
	}
	
	
	public static void main(String[] args) {

		int nums[] = { 5,2,6,1 };
		LeetCode315计算右侧小于当前元素的个数 lc = new LeetCode315计算右侧小于当前元素的个数();
		List<Integer> result= lc.countSmaller(nums);
		for (int i = 0; i < result.size(); i++) {
			System.out.print(result.get(i)+" ");
		}

	}
}

博主总结

  1. 这题花了1天还没独立写出来,只是从别人题解中得到了相应思路的启发。刷hard题可以极致锻炼思维,可是成本太大,性价比不高。套用以前的解题模板可以大大简化思考的路径。

  2. 隔了若干天后继续原来基础上的思考,通过没过的测试用例结合断点调试独立发现问题并且做了出来。
  3. 调试过程中需要排除外界干扰,深度思考,找出问题后有种成就感的喜悦。

相似题目

力扣148. 排序链表(归并排序)

剑指 Offer 51. 数组中的逆序对

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据与后端架构提升之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值