摘要
包括了的归并的排序算法的原理。如果涉及到数组 链表有序同时时间复杂度为nlog(n) 那就需要考虑到到归并排序算法。
一、算法练习题
剑指 Offer 51. 数组中的逆序对
归并排序与逆序对是息息相关的。归并排序体现了 “分而治之” 的算法思想,具体为:
分: 不断将数组从中点位置划分开(即二分法),将整个数组的排序问题转化为子数组的排序问题;
治: 划分到子数组长度为 1 时,开始向上合并,不断将 较短排序数组 合并为 较长排序数组,直至合并至原数组时完成排序;
合并阶段本质上是合并两个排序数组 的过程,而每当遇到左子数组当前元素>右子数组当前元素 时,意味着 左子数组当前元素 至 末尾元素与右子数组当前元素构成了若干「逆序对」 。 因此,考虑在归并排序的合并阶段统计「逆序对」数量,完成归并排序时,也随之完成所有逆序对的统计。
class Solution {
int count = 0;
public int reversePairs(int[] array) {
// 长度小于2则无逆序对
if (array.length < 2) {
return 0;
}
// 进入归并
mergeSort(array, 0, array.length - 1);
return count;
}
private void mergeSort(int[] array, int left, int right) {
// 找分割点
int mid = (left + right) >> 1;
if (left < right) {
// 左子数组
mergeSort(array, left, mid);
// 右子数组
mergeSort(array, mid + 1, right);
// 并
merge(array, left, mid, right);
}
}
private void merge(int[] array, int left, int mid, int right) {
// 创建临时数组,长度为此时两个子数组加起来的长度
int[] arr = new int[right - left + 1];
// 临时数组的下标起点
int index = 0;
// 保存在原数组的起点下标值
int s = left;
// 左子数组的起始指针
int l = left;
// 右子数组的起始指针
int r = mid + 1;
while (l <= mid && r <= right) {
// 当左子数组的当前元素小的时候,跳过,无逆序对
if (array[l] <= array[r]) {
// 放入临时数组
arr[index] = array[l];
// 临时数组下标+1
index++;
// 左子数组指针右移
l++;
} else { // 否则,此时存在逆序对
// 放入临时数组
arr[index] = array[r];
// 逆序对的个数为 左子数组的终点- 当前左子数组的当前指针
count += mid - l + 1;
// 临时数组+1
index++;
// 右子数组的指针右移
r++;
}
}
// 左子数组还有元素时,全部放入临时数组
while (l <= mid){
arr[index++] = array[l++];}
// 右子数组还有元素时,全部放入临时数组
while (r <= right) {
arr[index++] = array[r++];
}
// 将临时数组中的元素放入到原数组的指定位置
for (int num : arr) {
array[s++] = num;
}
}
}
315. 计算右侧小于当前元素的个数
(这个题目没有理解怎么实现)
package 归并算法;
import java.util.Arrays;
import java.util.List;
/**
* @Classname countSmaller315
* @Description TODO
* @Date 2022/2/15 20:59
* @Created by xjl
*/
public class countSmaller315 {
class TreeNode {
int value;//
int count;// 表示的左子树元素的个数
TreeNode left;
TreeNode right;
public TreeNode(int value) {
this.value = value;
}
}
public List<Integer> countSmaller(int[] nums) {
Integer[] res = new Integer[nums.length];
Arrays.fill(res, 0);
TreeNode root = null;
for (int i = nums.length - 1; i >= 0; i--) {
root = create(root, new TreeNode(nums[i]), res, i);
}
return Arrays.asList(res);
}
private TreeNode create(TreeNode root, TreeNode node, Integer[] res, int i) {
if (root == null) {
root = node;
return root;
}
if (root.value >= node.value) {
root.count++;
root.left = create(root.left, node, res, i);
} else {
res[i] += 1 + root.count;
root.right = create(root.right, node, res, i);
}
return root;
}
}
方法二:归并排序
package 归并算法;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Classname countSmaller315
* @Description TODO
* @Date 2022/2/15 20:59
* @Created by xjl
*/
public class countSmaller315 {
List<Integer> ans = new ArrayList<>(); //记录最终的结果
int[] index; //原数组的索引数组,存储着原数组中每个元素对应的下标
int[] count; //记录题目中所求的count[i]
public List<Integer> countSmaller2(int[] nums) {
int len = nums.length;
index = new int[len];
count = new int[len];
for (int i = 0; i < nums.length; i++) {
index[i] = i;
}
mergeSort(nums, 0, nums.length - 1);
return Arrays.stream(count).boxed().collect(Collectors.toList());
}
public void mergeSort(int[] a, int l, int r) {
if (l >= r) {
return;
}
int mid = (l + r) >> 1;
mergeSort(a, l, mid);
mergeSort(a, mid + 1, r);
merge(a, l, mid, r);
}
private void merge(int[] nums, int start, int mid, int end) {
int P1 = start;
int P2 = mid + 1;
int cur = 0;
int[] tmp = new int[end - start + 1]; //临时数组用于存储一次归并过程中排序好的元素,
int[] tmpIndex = new int[end - start + 1];//临时数组的索引数组,存储这临时数组中每个元素对应的下标
while (P1 <= mid && P2 <= end) {
if (nums[P1] > nums[P2]) {
count[index[P1]] += end - P2 + 1; //右半部分小于nums[P1]元素的数目
tmpIndex[cur] = index[P1]; //记录元素位置的改变
tmp[cur] = nums[P1];
P1++;
} else {
tmp[cur] = nums[P2];
tmpIndex[cur] = index[P2];
P2++;
}
cur++;
}
while (P1 <= mid) {
tmp[cur] = nums[P1];
tmpIndex[cur] = index[P1];
P1++;
cur++;
}
while (P2 <= end) {
tmp[cur] = nums[P2];
tmpIndex[cur] = index[P2];
P2++;
cur++;
}
for (int i = 0; i < end - start + 1; i++) {
nums[i + start] = tmp[i];
index[i + start] = tmpIndex[i];
}
}
}
327. 区间和的个数
493. 翻转对
package 归并算法;
import org.junit.Test;
/**
* @Classname reversePairs493
* @Description TODO
* @Date 2022/2/16 22:05
* @Created by xjl
*/
public class reversePairs493 {
@Test
public void test() {
int i = reversePairs(new int[]{2, 4, 3, 5, 1});
System.out.println(i);
}
int count = 0;
public int reversePairs(int[] nums) {
mergeSort(nums, 0, nums.length - 1);
return count;
}
void mergeSort(int[] nums, int l, int r) {
if (l >= r) {
return;
}
int mid = (l + r) / 2;
mergeSort(nums, l, mid);
mergeSort(nums, mid + 1, r);
calculate(nums, l, mid, r);
sort(nums, l, mid, r);
}
void calculate(int[] nums, int l, int mid, int r) {
for (int i = l, j = mid + 1; i <= mid; i++) {
while (j <= r && nums[i] > 2L * nums[j]) {
j++;
}
count += j - (mid + 1);
}
}
void sort(int[] nums, int l, int mid, int r) {
int[] temp = new int[r - l + 1];
int i = l, j = mid + 1;
int index = 0;
while (i <= mid && j <= r) {
temp[index++] = nums[i] > nums[j] ? nums[j++] : nums[i++];
}
while (i <= mid) {
temp[index++] = nums[i++];
}
while (j <= r) {
temp[index++] = nums[j++];
}
for (int k = 0; k < temp.length; k++) {
nums[l + k] = temp[k];
}
}
}
剑指 Offer 25. 合并两个排序的链表
package 归并算法;
import java.util.List;
/**
* @Classname mergeTwoLists25
* @Description TODO
* @Date 2022/2/16 23:33
* @Created by xjl
*/
public class mergeTwoLists25 {
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dumy = new ListNode(-1);
ListNode curr=dumy;
ListNode l = l1;
ListNode r = l2;
while (l != null && r != null) {
if (l.val>=r.val){
curr.next=r;
r=r.next;
}else {
curr.next=l;
l=l.next;
}
curr=curr.next;
}
while (l!=null){
curr.next=l;
l=l.next;
curr=curr.next;
}
while (r!=null){
curr.next=r;
r=r.next;
curr=curr.next;
}
return dumy.next;
}
}
88. 合并两个有序数组
package 归并算法;
import org.junit.Test;
/**
* @Classname merge88
* @Description TODO
* @Date 2022/2/16 23:02
* @Created by xjl
*/
public class merge88 {
/**
* @description 倒序的数组合并 从后面向前开始
* @param: nums1
* @param: m
* @param: nums2
* @param: n
* @date: 2022/2/16 23:02
* @return: void
* @author: xjl
*/
public void merge(int[] nums1, int m, int[] nums2, int n) {
int index = m + n - 1;
int l = m - 1;
int r = n - 1;
while (l >= 0 && r >= 0) {
if (nums1[l] >= nums2[r]) {
nums1[index--] = nums1[l--];
} else {
nums1[index--] = nums2[r--];
}
}
while (l >= 0) {
nums1[index--] = nums1[l--];
}
while (r >= 0) {
nums1[index--] = nums2[r--];
}
}
@Test
public void test(){
merge(new int[]{0},0,new int[]{1},1);
}
}
23. 合并K个升序链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
return merge(lists, 0, lists.length - 1);
}
public ListNode merge(ListNode[] lists, int l, int r) {
if (l == r) {
return lists[l];
}
if (l > r) {
return null;
}
int mid = (l + r) >> 1;
return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
}
/**
* @description 两个链表的合并
* @param: a
* @param: b
* @date: 2022/2/17 0:13
* @return: 归并算法.mergeKLists23.ListNode
* @author: xjl
*/
public ListNode mergeTwoLists(ListNode a, ListNode b) {
if (a == null || b == null) {
return a != null ? a : b;
}
ListNode head = new ListNode(0);
ListNode tail = head, aPtr = a, bPtr = b;
while (aPtr != null && bPtr != null) {
if (aPtr.val < bPtr.val) {
tail.next = aPtr;
aPtr = aPtr.next;
} else {
tail.next = bPtr;
bPtr = bPtr.next;
}
tail = tail.next;
}
tail.next = (aPtr != null ? aPtr : bPtr);
return head.next;
}
}
148. 排序链表
同样的也是的归并排序算法
切分
合并链表
package 归并排序算法;
public class sortList148v2 {
public class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
//快慢指针切割
ListNode fast = head.next, slow = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode tmp = slow.next;
slow.next = null;
// 合并链表
ListNode left = sortList(head);
ListNode right = sortList(tmp);
ListNode result=mergetwolist(left,right);
return result;
}
/**
* 链表和合并
*
* @param l1
* @param l2
* @return
*/
private ListNode mergetwolist(ListNode l1, ListNode l2) {
ListNode dumy = new ListNode(-1);
ListNode curr = dumy;
while (l1 != null && l2 != null) {
if (l1.val> l2.val){
curr.next=l2;
l2=l2.next;
}else {
curr.next=l1;
l1=l1.next;
}
curr=curr.next;
}
while (l1!=null){
curr.next=l1;
curr=curr.next;
l1=l1.next;
}
while (l2!=null){
curr.next=l2;
curr=curr.next;
l2=l2.next;
}
return dumy.next;
}
}
912. 排序数组
就是的一个的归并排序的原理
class Solution {
public int[] sortArray(int[] nums) {
if (nums.length < 2) {
return nums;
}
split_array(nums, 0, nums.length - 1);
return nums;
}
public void split_array(int[] nums, int left, int right) {
if (left >= right) {
return;
}
int mid = (left + right) >> 1;
split_array(nums, left, mid);
split_array(nums, mid + 1, right);
merge(nums, left, mid, right);
}
public void merge(int[] nums, int left, int mid, int right) {
int[] array = new int[right - left + 1];
int index = 0;
int s = left;
int l = left;
int r = mid + 1;
while (l <= mid && r <= right) {
if (nums[l] <= nums[r]) {
// 放入临时数组
array[index] = nums[l];
// 临时数组下标+1
index++;
// 左子数组指针右移
l++;
} else { // 否则,此时存在逆序对
// 放入临时数组
array[index] = nums[r];
// 临时数组+1
index++;
// 右子数组的指针右移
r++;
}
}
// 左子数组还有元素时,全部放入临时数组
while (l <= mid) {
array[index++] = nums[l++];
}
// 右子数组还有元素时,全部放入临时数组
while (r <= right) {
array[index++] = nums[r++];
}
// 将临时数组中的元素放入到原数组的指定位置
for (int num : array) {
nums[s++] = num;
}
}
}
剑指 Offer II 077. 链表排序
package 归并排序算法;
public class sortList148v2 {
public class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
//快慢指针切割
ListNode fast = head.next, slow = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode tmp = slow.next;
slow.next = null;
// 合并链表
ListNode left = sortList(head);
ListNode right = sortList(tmp);
ListNode result=mergetwolist(left,right);
return result;
}
/**
* 链表和合并
*
* @param l1
* @param l2
* @return
*/
private ListNode mergetwolist(ListNode l1, ListNode l2) {
ListNode dumy = new ListNode(-1);
ListNode curr = dumy;
while (l1 != null && l2 != null) {
if (l1.val> l2.val){
curr.next=l2;
l2=l2.next;
}else {
curr.next=l1;
l1=l1.next;
}
curr=curr.next;
}
while (l1!=null){
curr.next=l1;
curr=curr.next;
l1=l1.next;
}
while (l2!=null){
curr.next=l2;
curr=curr.next;
l2=l2.next;
}
return dumy.next;
}
}
剑指 Offer II 078. 合并排序链表
package 归并算法;
/**
* @Classname mergeKLists23
* @Description TODO
* @Date 2022/2/17 0:06
* @Created by xjl
*/
public class mergeKLists23 {
public class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
return merge(lists, 0, lists.length - 1);
}
public ListNode merge(ListNode[] lists, int l, int r) {
if (l == r) {
return lists[l];
}
if (l > r) {
return null;
}
int mid = (l + r) >> 1;
return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
}
/**
* @description 两个链表的合并
* @param: a
* @param: b
* @date: 2022/2/17 0:13
* @return: 归并算法.mergeKLists23.ListNode
* @author: xjl
*/
public ListNode mergeTwoLists(ListNode a, ListNode b) {
if (a == null || b == null) {
return a != null ? a : b;
}
ListNode head = new ListNode(0);
ListNode tail = head, aPtr = a, bPtr = b;
while (aPtr != null && bPtr != null) {
if (aPtr.val < bPtr.val) {
tail.next = aPtr;
aPtr = aPtr.next;
} else {
tail.next = bPtr;
bPtr = bPtr.next;
}
tail = tail.next;
}
tail.next = (aPtr != null ? aPtr : bPtr);
return head.next;
}
}