文章目录
- [1. 颜色划分(LC75)](#1. 颜色划分(LC75))
- [2. 快速排序(LC912)](#2. 快速排序(LC912))
- [3. 快速选择算法(LC215)](#3. 快速选择算法(LC215))
- [4. 最小的K个数(LCR159)](#4. 最小的K个数(LCR159))
- [5. 归并排序(LC912)](#5. 归并排序(LC912))
- [6. 数组中的逆序对(LCR170)](#6. 数组中的逆序对(LCR170))
- [7. 计算右侧小于当前元素的个数(LC315)](#7. 计算右侧小于当前元素的个数(LC315))
- [8. 翻转对(LC493)](#8. 翻转对(LC493))
1. 颜色划分(LC75)
题目描述

解题思路
使用三个指针:i遍历数组,left表示0区域的最右端,right表示2区域的最左端。三个指针把数组划分为4个区域:

- 当前值为0:交换
nums[left+1]nums[i],接着left和i向后走一步 - 当前值为1:
i向后走一步 - 当前值为2:交换
nums[right-1]和nums[i],right向左走一步,i不可以移动,因为nums[right-1]是未被扫描的元素
代码实现
java
void swap(int a,int b,int[] nums){
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
}
public void sortColors(int[] nums) {
int i=0,left=-1,right=nums.length;
while(i<right){
if(nums[i]==0){
swap(i,left+1,nums);
left++;
i++;
}else if(nums[i]==2){
swap(i,right-1,nums);
right--;
}else
i++;
}
}
2. 快速排序(LC912)
题目描述

解题思路
当元素都相同时,时间复杂度会大打折扣,因此优化成数组分三块。(O(n)采用上个题的思想:
选定一个基准值,左边是小于基准值,右边大于基准值,中间是基准值。
- 当前值小于基准值:交换
nums[left+1]nums[i],接着left和i向后走一步 - 当前值等于基准值:
i向后走一步 - 当前值大于基准值:交换
nums[right-1]和nums[i],right向左走一步,i不可以移动,因为nums[right-1]是未被扫描的元素
接着递归排序子区间
优化点:随机取基准值:
代码实现
java
class Solution {
Random random = new Random();
public int[] sortArray(int[] nums) {
sort(nums,0,nums.length-1);
return nums;
}
void sort(int[] nums,int l,int r){
if(l>=r)
return;
int key = nums[random.nextInt(r - l +1)+l];
int i = l,left = l-1,right = r+1;
while(i<right){
if(nums[i]<key){
swap(nums,i,left+1);
i++;
left++;
}else if(nums[i]>key){
swap(nums,i,right-1);
right--;
}else
i++;
}
//[l,left] [left+1,right-1] [right,r]
sort(nums,l,left);
sort(nums,right,r);
}
void swap(int[] nums,int a,int b){
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
}
}
3. 快速选择算法(LC215)
题目描述

解题思路
- 思路一:堆排序,建立容量为K的小根堆,堆顶元素就是第K大的元素。时间复杂度O(n*log2n)
- 思路二:快速选择算法,数组分三块+随机选择基准值 时间复杂度O(n)
- 随机选定一个随机值,数组分为小于,等于,大于基准值三部分:
- 假设三个部分的元素个数分别是
a,b,c- 如果
c>=k,说明第K大的元素在右区间,在[right,r]里找第K个大的元素 - 如果
b+c>=k,说明第K大的元素在中间区间,直接返回当前的基准值 - 前两种情况都不成立:说明第K大的元素在左区间, 在
[l,left]找第k-b-c大的元素
- 如果
代码实现
java
class Solution {
Random random = new Random();
public int findKthLargest(int[] nums, int k) {
return find(nums,0,nums.length-1,k);
}
int find(int[] nums,int l,int r,int k){
if(l>=r)
return nums[l];
int key = nums[random.nextInt(r-l+1)+l];
int i=l,left=l-1,right=r+1;
while(i<right){
if(nums[i]<key){
swap(nums,i,left+1);
left++;
i++;
}else if(nums[i]>key){
swap(nums,i,right-1);
right--;
}else
i++;
}
//[l,left] [left+1,right-1] [right,r]
int a = left-l+1;
int b = right - left -1;
int c = r - right +1;
if(c>=k)
return find(nums,right,r,k);
else if(c+b>=k)
return key;
else
return find(nums,l,left,k-b-c);
}
void swap(int[] nums,int a,int b){
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
}
}
4. 最小的K个数(LCR159)
题目描述

解题思路
- 解法一:堆排序,建立容量为K的大根堆,堆内的元素就是最小的K个数。时间复杂度O(n*log2k)
- 解法二:快速选择算法,数组分三块+随机选择基准值。时间复杂度O(n)
- 随机选定一个随机值,数组分为小于,等于,大于基准值三部分:
- 假设三个部分的元素个数分别是
a,b,c- 如果
a>k,说明最小的k个元素在左区间,在[l,left]里找最小的k个元素 - 如果
b+a>=k,说明最小的K个元素右端点在中间区间,直接返回当前的基准值 - 前两种情况都不成立:说明最小的K个元素右端点在右区间, 在
[l,left]找前k-b-a个小的元素
- 如果
代码实现
java
class Solution {
Random random = new Random();
public int[] inventoryManagement(int[] stock, int cnt) {
int index = find(stock,0,stock.length-1,cnt);
return Arrays.copyOfRange(stock,0,index+1);
}
int find(int[] stock,int l,int r,int cnt){
int left = l-1;
int right = r+1;
int i = l;
int key = stock[random.nextInt(r-l+1)+l];
while(i<right){
if(stock[i]<key){
swap(stock,i,left+1);
left++;
i++;
}else if(stock[i]>key){
swap(stock,i,right-1);
right--;
}else
i++;
}
//[l,left] [left+1,right-1] [right,r]
int a = left - l + 1;
int b = right - left - 1;
if(a>cnt)
return find(stock,l,left,cnt);
else if(a+b>=cnt)
return cnt - a + left;
else
return find(stock,right,r,cnt-a-b);
}
void swap(int[] nums,int a,int b){
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
}
}
5. 归并排序(LC912)
题目描述

解题思路
- 把数组分解到两个长度为两个元素
- 有序数组合并
优化点:把临时数组改成全局变量,减少频繁创建和销毁数组的时间花销
代码实现
java
class Solution {
int[] tmp;
public int[] sortArray(int[] nums) {
tmp = new int[nums.length];
sort(nums,0,nums.length-1);
return nums;
}
void sort(int[] nums,int left,int right){
if(left>=right)
return;
int mid = left + (right-left)/2;
sort(nums,left,mid);
sort(nums,mid+1,right);
//合并有序数组
int i = left;
int j = left;
int k = mid+1;
while(j<=mid&&k<=right)
tmp[i++] = nums[j]<nums[k]?nums[j++]:nums[k++];
while(j<=mid)
tmp[i++] = nums[j++];
while(k<=right)
tmp[i++] = nums[k++];
//拷贝到原数组
for (i = left; i <= right; i++)
nums[i] = tmp[i];
}
}
6. 数组中的逆序对(LCR170)
题目描述

解题思路
- 思路一:暴力枚举,双重循环
- 思路二:利用归并排序
- 把数组分为两部分,左半部找到a个逆序对,右半部分找到b个逆序对
- 左右两部分分别排序,在左区间和右区间分别找出一个数,构成逆序对
- 整个数组的逆序对总个数就是左区间的逆序对个数+右区间的逆序对个数+左右区间各取一个得到的逆序对个数
使用归并过程中,第一步可以在递归过程中实现,最关键是处理第二步:
- 两个区间分别排序后,
j指针遍历右区间,在左区间(i指针维护)找比nums[j]大的个数nums[i] <=nums[j],说明不可以构成逆序对,则i++nums[i]>nums[j]说明左区间中从nums[i]到结尾,都可以与nums[j]构成逆序,ret += mid - i + 1,j++- 当某个区间指针走到头,说明已经找完所有的逆序对,接下来只需要完成排序,
ret不需要改动
拓展:
降序数组解决问题:在左区间内找到比右区间当前值小的个数
代码实现
java
class Solution {
int[] tmp ;
public int reversePairs(int[] record) {
tmp = new int[record.length];
return merge(record,0,record.length-1);
}
int merge(int[] nums,int left,int right){
if(left>=right)
return 0;
int ret = 0;
//左半部分的逆序对+右半部分的逆序对+左右各取一个构成的逆序对
int mid = left + (right-left)/2;
ret += merge(nums,left,mid);
ret += merge(nums,mid+1,right);
int i = left;
int j = mid+1;
int k = left;
while(i<=mid && j<=right){
if(nums[i]<=nums[j])
tmp[k++] = nums[i++];
else {
ret+=mid-i+1;
tmp[k++] = nums[j++];
}
}
//此时逆序对已经找全,只需要完善排序
while(i<=mid)
tmp[k++] = nums[i++];
while(j<=right)
tmp[k++] = nums[j++];
for(k = left;k <= right;k++)
nums[k] = tmp[k];
return ret;
}
}
7. 计算右侧小于当前元素的个数(LC315)
题目描述

解题思路
本质上是找逆序对,采用归并排序做逆序排序,i遍历左区间,在右区间内(j维护)找到比nums[i]小的个数
nums[i] <= nums[j],则tmp[k]=nums[j],j++,k++nums[i] > nums[j],说明nums[i]大于j后面所有的值,数量为right-j+1,tmp[k] = nums[i],i++,k++;- 注意:
cur1和cur2是排序后的结果,不是原始的下标。要想找到原始下标,需要创建index数组,大小与原数组相等,存放0到n-1,表示原数组的下标。- 原数组排序时,
index对应下标跟随原数组交换位置。

=400x) - 因为要同时给两个数组排序,临时数组
tmp也要创建两个。 - 找到原始下标,
ret对应位置加上count就是最终结果。
代码实现
java
class Solution {
int[] ret;
int[] index;
int[] tmpNums;
int[] tmpIndex;
public List<Integer> countSmaller(int[] nums) {
int n = nums.length;
ret = new int[n];
index = new int[n];
tmpNums = new int[n];
tmpIndex = new int[n];
//初始化index
for(int i = 0;i<n;i++)
index[i] = i;
merge(nums,0,n-1);
List<Integer> list = new ArrayList<>();
for(int x:ret)
list.add(x);
return list;
}
void merge(int[] nums,int left,int right){
if(left>=right)
return;
int mid = left+(right-left)/2;
merge(nums,left,mid);
merge(nums,mid+1,right);
int i = left;
int j = mid+1;
int k = left;
//降序排序
while(i<=mid && j<=right){
if(nums[i]<=nums[j]){
tmpNums[k] = nums[j];
tmpIndex[k++] = index[j++];
}
else{
ret[index[i]] += right - j + 1;
tmpNums[k] = nums[i];
tmpIndex[k++] = index[i++];
}
}
while(i<=mid){
tmpNums[k]=nums[i];
tmpIndex[k++] = index[i++];
}
while(j<=right){
tmpNums[k]=nums[j];
tmpIndex[k++] = index[j++];
}
for(i = left;i <= right;i++){
nums[i] = tmpNums[i];
index[i] = tmpIndex[i];
}
}
}
8. 翻转对(LC493)
题目描述

解题思路
与计算逆序对类似,采用分治的思想,分别计算左半部分,右半部分内部翻转对,再从左右两数组挑出一个元素找到翻转对,结果相加。
与逆序对的解法不同之处:不可以直接排序,而是找2倍关系。利用单调性使用同向双指针
- 计算翻转对
- 策略一:计算当前元素后面有多少元素的2倍比当前值小(降序排序)
- 如果
nums[i] < nums[j]*2:j++ - 如果
nums[i] >= nums[j]*2:ret+=right - j + 1;i++;接下来j不需要回退,因为mid+1到j之间元素比i对应的元素大,一定比i到mid之间元素大。
- 如果
- 策略二:计算当前元素之前有多少元素的一半比当前值大(升序排序)
- 如果
nums[i]/2 < nums[j]*:i++; - 如果
nums[i]/2 >= nums[j]:ret+=mid - i + 1;j++
- 如果
- 策略一:计算当前元素后面有多少元素的2倍比当前值小(降序排序)
- 合并两个有序数组。前面的题排序和具体操作可以合并写,而这个题两者需要分开写
代码实现
java
class Solution {
int[] tmp;
public int reversePairs(int[] nums) {
int n = nums.length;
tmp = new int[n];
return merge(nums, 0, n - 1);
}
int merge(int[] nums, int left, int right) {
if (left >= right)
return 0;
int ret = 0;
int mid = left + (right - left) / 2;
ret += merge(nums, left, mid);
ret += merge(nums, mid + 1, right);
int i = left;
int j = mid + 1;
//计算翻转对
while (i <= mid) {
while(j<=right && nums[i] <= (long)nums[j] * 2)//防止溢出
j++;
if(j>right)
break;
ret += right - j + 1;
i++;
}
//降序排序
i = left;
j = mid + 1;
int k = left;
while (i <= mid && j <= right) {
if (nums[i] > nums[j])
tmp[k++] = nums[i++];
else
tmp[k++] = nums[j++];
}
while (i <= mid)
tmp[k++] = nums[i++];
while (j <= right)
tmp[k++] = nums[j++];
for(k = left;k<=right;k++)
nums[k] = tmp[k];
return ret;
}
}