题目:给你一个整数数组
nums
以及两个整数lower
和upper
。求数组中,值位于范围[lower, upper]
(包含lower
和upper
)之内的 区间和的个数 。区间和
S(i, j)
表示在nums
中,位置从i
到j
的元素之和,包含i
和j
(i
≤j
)。https://leetcode.cn/problems/count-of-range-sum/description/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china
思路:求区间和问题,将原始数组转换为累加和数组,任意两个区间范围,使用递归方式将求解范围划分为更新的部分,类似归并排序思路。
public static int countRangeSum240215(int[] nums, int lower, int upper) {if(nums == null || nums.length<=0)return 0;// 累加数组int[] acc = new int[nums.length];acc[0] = nums[0];for (int i = 1; i < nums.length; i++) {acc[i] = nums[i]+acc[i-1];}return process240215(acc,0,acc.length-1,upper,lower);}// 递归作用是将数组范围划分为更小的部分public static int process240215(int[] acc,int left,int right,int upper,int lower){if (left == right){return acc[left]<=upper && acc[right]>=lower?1:0;}int m = left + ((right-left)>>1);// 将问题范围划分为更小部分,在调用之后 [left,mid] [mid+1,right] 是递增的int sub = process(acc,left,m,upper,lower)+process(acc,m+1,right,upper,lower);// 当前范围计算符合问题的数量int cur = merge240215(acc,left,m,right,upper,lower);return cur+sub;}// 以mid为分界线前后都是递增的,计算复合条件的数量,同时将前后两个部分合并为一个递增序列// 左边的部分为[left,mid] 右边的部分为[mid+1,right] 因为递归调用的时候mid放在前面部分// mid左右两部分合并为递归序列不影响最终结果,因为求的是原始数组两个位置子区间累加和public static int merge240215(int[] acc,int left,int mid ,int right,int upper,int lower){int ans = 0;int winL = left;int winR = left;for(int i = mid+1;i<=right;i++){// acc[i] - acc[winL] <= upperwhile (acc[i]-acc[winL]>upper){winL++;}// acc[i] - acc[winR] >= lowerwhile (acc[i]-acc[winR]>=lower){winR++;}// winR-winL 就是最终的数量 不用+1 因为计算大于等于lower的时候最终的winR是小于lower的位置,所以直接相减不用加一// 下一次进入该for遍历 acc[i] 比acc[i-1] 大,所以可以接着上次计算的winL 和 winR继续用ans += (winR-winL);}// 合并为递增序列int l = left;int r = mid;int index = 0;int[] help = new int[right - left + 1];for (;l<=mid && r<=right;){if(acc[l]<acc[r]){help[index] = acc[l++];}else{help[index] = acc[r++];}index++;}while (l<=mid){help[index++] = acc[l++];}while (r<=right){help[index++] = acc[r++];}return ans;}
问题:归并排序
思路:将待排序范围划分为更小的部分,如果只有一个代表当前范围的子数组已排序直接返回,在上次对子范围进行合并为排序数组.两种实现方式:递归方式,另外使用栈模拟递归调用
// 归并排序// 递归方式实现// 将数组划分为更小的范围,然后再合并的时候对子数组进行排序public static void mergeSort1(int[] arr) {if(arr == null || arr.length<=0)return;process(arr, 0,arr.length-1);}public static void process(int[] arr,int left,int right){if(left >= right)return;int index = ((int)(Math.random()*(right-left+1)))+left;process(arr,left,index);process(arr,index+1,right);merge(arr,left,index,right);}// 将[left,mid] (mid,right] 合并为排序数组// 两个子范围是已经排序了的public static void merge(int[] arr,int left,int mid,int right){int index = 0;int[] help = new int[right - left + 1];int l = left;int r = mid+1;while (l<=mid && r<=right){if(arr[l]<arr[r]){help[index] = arr[l++];}else {help[index] = arr[r++];}index++;}while (l<=mid){help[index++] = arr[l++];}while (r <= right) {help[index++] = arr[r++];}index = 0;for (int i = 0; i < help.length; i++) {arr[left+i] = help[i];}}// 非递归方式// 栈模拟递归调用public static void mergeSort2(int[] arr) {if(arr == null || arr.length<=1)return;Stack<Flash> stack = new Stack<>();Flash flash = new Flash(0, arr.length - 1, 1);stack.add(flash);while (!stack.isEmpty()){Flash cur = stack.pop();if(cur.count == 1){// cur对应的递归范围第一次访问,对应的是该范围的process方法第一次访问// 创建左右子范围然后push到stackint left = cur.left;int right = cur.right;if(left >= right)continue;int index = ((int)(Math.random()*(right-left+1)))+left;// 这里给子范围的count赋值1 实际上这里不算调用子范围,也就是未进入到范围对应的process方法体里Flash l = new Flash(left, index, 1);Flash r = new Flash(index + 1, right, 1);// 用于合并已排序子数组cur.setMid(index);cur.count++;// 先将当前的压入堆栈 因为在左右子范围处理完后需要回到当前范围,将来左右已排序子数组合并stack.push(cur);stack.push(r);stack.push(l);}else if (cur.count == 2){// 啥也不做cur.count++;stack.push(cur);}else {// 左右子范围的process方法都执行完成// 左右子范围子数组都是有序的,将两个子数组合并为一个有序数组int left = cur.left;int right = cur.right;int index = cur.getMid();merge(arr,left,index,right);}}}static class Flash{private int left;private int right;private int count;private int mid;public Flash(int left, int right, int count) {this.left = left;this.right = right;this.count = count;}public int getMid(){return mid;}public void setMid(int mid){this.mid = mid;}}
题目:在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。
例子: [1,3,4,2,5]
1左边比1小的数:没有
3左边比3小的数:1
4左边比4小的数:1、3
2左边比2小的数:1
5左边比5小的数:1、3、4、 2
所以数组的小和为1+1+3+1+1+3+4+2=16
思路:归并排序,在合并子范围的时候计算小数和,归并在这里作用就是缩小范围,而左右子范围排序后并不影响最终结果,并且排序后可以加快计算速度不用重新从左子数组开始遍历
public static int smallSum(int[] arr) {if(arr == null|| arr.length<=0)return 0;return process(arr,0,arr.length-1);}public static int process(int[] arr,int left,int right){if(left >= right)return 0;int index = ((int)(Math.random()*(right-left+1)))+left;int l = process(arr,left,index);int r = process(arr,index+1,right);int cur = merge(arr,left,index,right);return l+r+cur;}// 将[left,mid] (mid,right] 合并为排序数组// 两个子范围是已经排序了的public static int merge(int[] arr,int left,int mid,int right){int ans = 0;// 计算[left,mid] (mid,right] 范围的小数和int wind = mid+1;int L = left;int pre = 0;while (wind<=right){// 因为左右两个子数组是递增的 所以pre是定义在while外边 // 每次更新wind,在上次计算的pre基础上累加while (L <= mid && arr[L]<arr[wind]){pre+=arr[L++];}ans+=pre;wind++;}// 将两子数组合并为一个排序数组int index = 0;int[] help = new int[right - left + 1];int l = left;int r = mid+1;while (l<=mid && r<=right){if(arr[l]<arr[r]){help[index] = arr[l++];}else {help[index] = arr[r++];}index++;}while (l<=mid){help[index++] = arr[l++];}while (r <= right) {help[index++] = arr[r++];}index = 0;for (int i = 0; i < help.length; i++) {arr[left+i] = help[i];}return ans;}
问题:
在一个数组中,
任何一个前面的数a,和任何一个后面的数b,
如果(a,b)是降序的,就称为逆序对
返回数组中所有的逆序对
思路:归并算法逻辑,用于将数组划分为更新的逻辑,在合并的时候处理大小比较逻辑
public static int reverPairNumber(int[] arr) {if(arr == null || arr.length<=0)return 0;return process(arr,0,arr.length-1);}public static int process(int[] arr,int left,int right){if(left >= right)return 0;int index = left + ((right - left)>>1);int l = process(arr,left,index);int r = process(arr,index+1,right);int cur = merge(arr,left,index,right);return l+r+cur;}// 将[left,mid] (mid,right] 合并为排序数组// 两个子范围是已经排序了的public static int merge(int[] arr,int left,int mid,int right){int ans = 0;// 计算[left,mid] (mid,right] 范围的小数和int wind = right;int L = mid;while (wind>mid&&L>=left){if(arr[wind]<arr[L]){ans += (wind-mid);L--;}else{wind--;}}// 将两子数组合并为一个排序数组int index = 0;int[] help = new int[right - left + 1];int l = left;int r = mid+1;while (l<=mid && r<=right){if(arr[l]<arr[r]){help[index] = arr[l++];}else {help[index] = arr[r++];}index++;}while (l<=mid){help[index++] = arr[l++];}while (r <= right) {help[index++] = arr[r++];}index = 0;for (int i = 0; i < help.length; i++) {arr[left+i] = help[i];}return ans;}