目录
一、数组排序
题目:
思路:归并排序
代码:
cpp
class Solution {
public:
void msort(vector<int>& nums, int *tmp, int left, int right)
{
if(left >= right) return;
int mid = (right+left)/2;
msort(nums, tmp, left, mid);
msort(nums, tmp, mid+1, right);
int begin1 = left, end1 = mid;
int begin2 = mid+1, end2 = right;
int index = begin1;
while(begin1 <= end1 && begin2 <= end2)
{
if(nums[begin1] < nums[begin2])
tmp[index++] = nums[begin1++];
else
tmp[index++] = nums[begin2++];
}
while(begin1 <= end1) tmp[index++] = nums[begin1++];
while(begin2 <= end2) tmp[index++] = nums[begin2++];
memcpy(&nums[0]+left, tmp+left, sizeof(int)*(end2-left+1));//begin1会变化,用left
}
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
int *tmp = new int[n]{0};
msort(nums, tmp, 0, n-1);
delete[] tmp;
return nums;
}
};
二、交易逆序对的总数
题目:
思路:在归并排序的基础上统计逆序对数量
- 为什么两个子数组是有序的?因为上次合并就已经排好序了。是两个子数组各自里面的元素有序
- nums[begin1] <= nums[begin2]:不确定比nums[begin2]大的位置出现在哪,可能还有部分也是小于nums[begin2]或者相等,所以没法直接统计出比nums[begin2]大的元素个数
- nums[begin1] > nums[begin2]:说明该位置的元素比nums[begin2]大,可以组成逆序对,同时nums[begin1]后面的元素也都满足条件。
- 为什么比较的是nums[begin2]?因为逆序对是前大后小,直接用后面的子数组中的nums[begin2]作比较可以减少重复计算。
代码:
cpp
class Solution {
public:
void msort(vector<int>& nums, int *tmp, int left, int right, int &ret)
{
if(left >= right) return;
int mid = (left+right)/2;
msort(nums, tmp, left, mid, ret);
msort(nums, tmp, mid+1, right, ret);
int begin1 = left, end1 = mid;
int begin2 = mid+1, end2 = right;
int index = begin1;
while(begin1 <= end1 && begin2 <= end2)
{
if(nums[begin1] <= nums[begin2])
{
tmp[index++] = nums[begin1++];
}
else
{
ret += end1 - begin1 + 1;
tmp[index++] = nums[begin2++];
}
}
while(begin1 <= end1) tmp[index++] = nums[begin1++];
while(begin2 <= end2) tmp[index++] = nums[begin2++];
memcpy(&nums[0]+left, tmp+left, sizeof(int)*(end2-left+1));
}
int reversePairs(vector<int>& record) {
int n = record.size();
int *tmp = new int[n];
int ret = 0;
msort(record, tmp, 0, n-1, ret);
delete[] tmp;
return ret;
}
};
三、计算右侧小于当前元素的个数
题目:
思路:
上一题是:找该位置元素前面有多少个元素比它大
本题:找该位置元素后面有多少个元素比它小,然后在该位置填个数,最后返回新的数组
采用降序找小的策略:
- 因为是降序,所以谁大就谁先填入临时数组
- nums[begin1] <= nums[begin2]:begin2到end2还有部分大于等于nums[begin1],所以nums[begin1]大于剩余的部分是不确定的,不能直接统计有多少个比nums[begin1]小
- nums[begin1] > nums[begin2]:说明此时begin2到end2闭区间都是比nums[begin1]要小的,直接统计个数即可
- 为什么以nums[begin1]作为比较?因为begin1这个点靠左,减少重复计算
- begin1到end1也是降序,为什么不加上这里的?因为'降序'其实是非升序,即可能出现相同的元素,直白的说就是begin1到end1这部分的元素万一都是和nums[begin1]相同怎么办,所以不加上这里的,而一旦nums[begin1] > nums[begin2],那么begin2到end2的个数就是确定的
重点:hash对应下标加上个数
- 原数组nums和临时数组(交换元素位置用的)tmpnums
- 记录下标的数组index(对应原数组的下标)和临时数组tmpindex
- 之前都是nums和tmp可以改变数组元素的位置(这里tmp对应tmpnums)
- 下标的位置也要同步改变,index数组(每个元素是下标)和tmpindex
- hash数组记录个数,作为返回对象。它的数组下标是从0开始,对应原数组的下标
- 如果没有同步下标就导致某个位置的个数加错了
- 同步了之后,就可以找到原来的下标,然后在hash的该位置加上个数
以上图为例:原数组的元素5是下标0,那么它的右侧小于它的元素个数,应该加在hash下标为0的位置上。当元素5的位置发生改变,如果下标没有同步,它的下标就变成了3,就导致最后加错。下标也同步移动后,begin1指向位置是nums的5,那么对应指向的位置index的0,这才是要hash要加上个数的下标位置。
代码:
cpp
class Solution {
vector<int> hash;// 个数--返回对象
vector<int> index;// 记录下标
vector<int> tmpnums;// 移动nums的临时数组
vector<int> tmpindex;// 移动index的临时数组
public:
void msort(vector<int>& nums, int left, int right)
{
if(left >= right) return;
int mid = (left+right)/2;
msort(nums, left, mid);
msort(nums, mid+1, right);
int begin1 = left, end1 = mid;
int begin2 = mid+1, end2 = right;
int k = begin1;
while(begin1 <= end1 && begin2 <= end2)
{
if(nums[begin1] <= nums[begin2])
{
tmpindex[k] = index[begin2];// 同步
tmpnums[k++] = nums[begin2++];
}
else
{
int count = right-begin2+1;
hash[index[begin1]] += count;// 找到对应下标位置再加count
tmpindex[k] = index[begin1];// 同步
tmpnums[k++] = nums[begin1++];
}
}
while(begin1 <= end1)
{
tmpindex[k] = index[begin1];// 同步
tmpnums[k++] = nums[begin1++];
}
while(begin2 <= end2)
{
tmpindex[k] = index[begin2];// 同步
tmpnums[k++] = nums[begin2++];
}
memcpy(&nums[0]+left, &tmpnums[0]+left, sizeof(int)*(end2-left+1));// 同步
memcpy(&index[0]+left, &tmpindex[0]+left, sizeof(int)*(end2-left+1));
}
vector<int> countSmaller(vector<int>& nums) {
int n = nums.size();
hash.resize(n); // 初始化
index.resize(n);// 初始化
tmpnums.resize(n);// 初始化
tmpindex.resize(n);// 初始化
for(int i=0; i<n; i++) index[i] = i;// 原数组下标先对应
msort(nums, 0, n-1);
return hash;
}
};
四、翻转对
题目:
思路:在逆序对的基础上改变应该条件:计算的是前面的元素比后面的元素的2倍大的个数
计算翻转对与合并分开写
注意:用两层for循环不好控制,因为从begin1到end1和从begin2到end2只遍历一遍,也就是说那个完了(begin1>end1或begin2>end2)就结束了。在这过程中,满足题目条件时ret+=(...),然后再begin2++;但是如果用两层for循环ret+=后还是begin1会往后走(本来应该暂时停下的,让begin2++了),可是多个限制条件break,就跳到了外循环;假如内循环是正常走完的,不是break跳出来的,那么根据前面说的,begin1>end1结束,所以内循环结束的下面理所应当要有一个break。但是假如是因为内循环的break到这里来的(ret+=后,begin1还是小于end1的),即内循环还没结束,外循环也没有,就直接跳出了,会出现各种问题。
cpp
for(int i=begin2;i<=end2;i++)
{
for(int j=begin1;j<=end1;j++)
{
if(nums[i] < nums[j]/2.0)
{
ret += end1-j+1;
break;
}
}
break;
}
代码:
cpp
class Solution {
public:
void msort(vector<int>& nums, int *tmp, int left, int right, int &ret)
{
if(left >= right) return;
int mid = (left+right)/2;
msort(nums, tmp, left, mid, ret);
msort(nums, tmp, mid+1, right, ret);
int begin1 = left, end1 = mid;
int begin2 = mid+1, end2 = right;
int k = begin1;
// 计算翻转对 - 两个子数组已经是上次合并后排好序的
while(begin2 <= end2)
{
// nums[begin1] <= nums[begin2]*2 会超出范围
while(begin1 <= end1 && nums[begin2] >= nums[begin1]/2.0)
begin1++;
if(begin1 > end1) break;
ret += end1-begin1+1;
begin2++;
}
// 合并
begin1 = left, begin2 = mid+1;
while(begin1 <= end1 && begin2 <= end2)
{
if(nums[begin1] <= nums[begin2])
{
tmp[k++] = nums[begin1++];
}
else
{
tmp[k++] = nums[begin2++];
}
}
while(begin1 <= end1)
tmp[k++] = nums[begin1++];
while(begin2 <= end2)
tmp[k++] = nums[begin2++];
memcpy(&nums[0]+left, tmp+left, sizeof(int)*(end2-left+1));
}
int reversePairs(vector<int>& nums) {
int n = nums.size();
int *tmp = new int[n];
int ret = 0;
msort(nums, tmp, 0, n-1, ret);
return ret;
}
};