归并排序及练习题

归并排序复杂度:

T(N) = 2*T(N/2) + O(N^1) 根据master可知时间复杂度为O(N*logN), merge过程需要辅助数组,所以额外空间复杂度为O(N), 归并排序的实质是把比较行为变成了有序信息并传递,比O(N^2)的排序快 

// 递归方法实现
public static void mergeSort1(int[] arr) {
   if (arr == null || arr.length < 2) {
      return;
   }
   process(arr, 0, arr.length - 1);
}

// 请把arr[L..R]排有序
// l...r N
// T(N) = 2 * T(N / 2) + O(N)
// 复杂度O(N * logN)
// 使L位置到R位置有序
public static void process(int[] arr, int L, int R) {
   if (L == R) { // base case
      return;
   }
   //中间位置:L+(R-L)/2
   int mid = L + ((R - L) >> 1);
   //使L到mid位置有序
   process(arr, L, mid);
   //使mid+1位置到R位置有序
   process(arr, mid + 1, R);
   //使L位置到R位置有序
   merge(arr, L, mid, R);
}

public static void merge(int[] arr, int L, int M, int R) {
   //建立一个辅助数组,长度和原数组一样
   int[] help = new int[R - L + 1];
   int i = 0;
   //两个指针p1,p2,分别指向左半部分的第一个位置,和右半部分的第一个位置
   int p1 = L;
   int p2 = M + 1;
   //p1,p2都未出界
   while (p1 <= M && p2 <= R) {
      help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
   }
   // 要么p1越界了,要么p2越界了
   while (p1 <= M) {
      help[i++] = arr[p1++];
   }
   while (p2 <= R) {
      help[i++] = arr[p2++];
   }
   //将辅助数组中的数组赋予原数组
   for (i = 0; i < help.length; i++) {
      arr[L + i] = help[i];
   }
}

1)整体是递归,左边排好序+右边排好序+merge让整体有序

2)让其整体有序的过程里用了排外序方法

3)利用master公式来求解时间复杂度

4)当然可以用非递归实现

// 非递归方法实现
public static void mergeSort2(int[] arr) {
   if (arr == null || arr.length < 2) {
      return;
   }
   int N = arr.length;
   // 步长
   int mergeSize = 1;
   while (mergeSize < N) { // log N
      // 当前左组的,第一个位置
      int L = 0;
      while (L < N) {
         if (mergeSize >= N - L) {
            break;
         }
         //中间位置
         int M = L + mergeSize - 1;
         //右半部分可能长度比左半部分要短
         int R = M + Math.min(mergeSize, N - M - 1);
         merge(arr, L, M, R);
         L = R + 1;
      }
      // 防止溢出
      if (mergeSize > N / 2) {
         break;
      }
      mergeSize <<= 1;
   }
}

在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和,求数组小和

public static int smallSum(int[] arr) {
   if (arr == null || arr.length < 2) {
      return 0;
   }
   return process(arr, 0, arr.length - 1);
}

public static int process(int[] arr, int l, int r) {
   if (l == r) {
      return 0;
   }
   // l < r
   int mid = l + ((r - l) >> 1);
   //左边排序产生的小和总量+右边排序产生的小和总量+加上自己merge过程中产生的小和总量
   return 
         process(arr, l, mid) 
         + 
         process(arr, mid + 1, r) 
         + 
         merge(arr, l, mid, r);
}

public static int merge(int[] arr, int L, int m, int r) {
   int[] help = new int[r - L + 1];
   int i = 0;
   int p1 = L;
   int p2 = m + 1;
   //去掉三处,就是归并排序
   int res = 0;
   while (p1 <= m && p2 <= r) {
      //去掉三处,就是归并排序
      res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
      //左组和右组相等时,先copy右组
      help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
   }
   while (p1 <= m) {
      help[i++] = arr[p1++];
   }
   while (p2 <= r) {
      help[i++] = arr[p2++];
   }
   for (i = 0; i < help.length; i++) {
      arr[L + i] = help[i];
   }
   //去掉三处,就是归并排序
   return res;
}

在一个数组中, 任何一个前面的数a,和任何一个后面的数b, 如果(a,b)是降序的,就称为逆序对,返回数组中所有的逆序对 

public static int reverPairNumber(int[] arr) {
   if (arr == null || arr.length < 2) {
      return 0;
   }
   return process(arr, 0, arr.length - 1);
}
public static int process(int[] arr, int l, int r) {
   if (l == r) {
      return 0;
   }
   // l < r
   int mid = l + ((r - l) >> 1);
   return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r);
}

public static int merge(int[] arr, int L, int m, int r) {
   int[] help = new int[r - L + 1];
   int i = help.length - 1;
   int p1 = m;
   int p2 = r;
   int res = 0;
   while (p1 >= L && p2 > m) {
      res += arr[p1] > arr[p2] ? (p2 - m) : 0;
      help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--];
   }
   while (p1 >= L) {
      help[i--] = arr[p1--];
   }
   while (p2 > m) {
      help[i--] = arr[p2--];
   }
   for (i = 0; i < help.length; i++) {
      arr[L + i] = help[i];
   }
   return res;
}

在一个数组中, 对于每个数num,求有多少个后面的数 * 2 依然<num,求总个数 

public static int biggerTwice(int[] arr) {
   if (arr == null || arr.length < 2) {
      return 0;
   }
   return process(arr, 0, arr.length - 1);
}

public static int process(int[] arr, int l, int r) {
   if (l == r) {
      return 0;
   }
   // l < r
   int mid = l + ((r - l) >> 1);
   return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r);
}

public static int merge(int[] arr, int L, int m, int r) {
   //先算有多少个
   // [L....M]   [M+1....R]
   int ans = 0;
   // 目前囊括进来的数,是从[M+1, windowR)
   int windowR = m + 1;
   for (int i = L; i <= m; i++) {
      while (windowR <= r && arr[i] > (arr[windowR] * 2)) {
         windowR++;
      }
      ans += windowR - m - 1;
   }
   
   //再merge
   int[] help = new int[r - L + 1];
   int i = 0;
   int p1 = L;
   int p2 = m + 1;
   while (p1 <= m && p2 <= r) {
      help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
   }
   while (p1 <= m) {
      help[i++] = arr[p1++];
   }
   while (p2 <= r) {
      help[i++] = arr[p2++];
   }
   for (i = 0; i < help.length; i++) {
      arr[L + i] = help[i];
   }
   return ans;
}

给定一个数组arr,两个整数lower和upper, 返回arr中有多少个子数组的累加和在[lower,upper]范围上 

public static int countRangeSum(int[] nums, int lower, int upper) {
   if (nums == null || nums.length == 0) {
      return 0;
   }
   long[] sum = new long[nums.length];
   sum[0] = nums[0];
   for (int i = 1; i < nums.length; i++) {
      sum[i] = sum[i - 1] + nums[i];
   }
   return process(sum, 0, sum.length - 1, lower, upper);
}

public static int process(long[] sum, int L, int R, int lower, int upper) {
   if (L == R) {
      return sum[L] >= lower && sum[L] <= upper ? 1 : 0;
   }
   int M = L + ((R - L) >> 1);
   return process(sum, L, M, lower, upper) + process(sum, M + 1, R, lower, upper)
         + merge(sum, L, M, R, lower, upper);
}

public static int merge(long[] arr, int L, int M, int R, int lower, int upper) {
   int ans = 0;
   int windowL = L;
   int windowR = L;
   // [windowL, windowR)
   for (int i = M + 1; i <= R; i++) {
      long min = arr[i] - upper;
      long max = arr[i] - lower;
      while (windowR <= M && arr[windowR] <= max) {
         windowR++;
      }
      while (windowL <= M && arr[windowL] < min) {
         windowL++;
      }
      ans += windowR - windowL;
   }
   long[] help = new long[R - L + 1];
   int i = 0;
   int p1 = L;
   int p2 = M + 1;
   while (p1 <= M && p2 <= R) {
      help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
   }
   while (p1 <= M) {
      help[i++] = arr[p1++];
   }
   while (p2 <= R) {
      help[i++] = arr[p2++];
   }
   for (i = 0; i < help.length; i++) {
      arr[L + i] = help[i];
   }
   return ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值