逆序对问题(LeetCode剑指offer51)+小和问题

👦个人主页zoroxs
image.png

小和问题

在一个数组中,每一个数左边比当前小的数累加起来,就是这数组的小和

image.png

思路一:暴力

暴力查找这个没啥好说的吧,双层for循环直接搞定,累加就可以了

int smallSum(vector<int>& v)
{
	int ans = 0;
	for (size_t i = 1; i < v.size(); i++)
	{
		for (size_t j = 0; j < i; j++)
		{
			ans += v[j] < v[i] ? v[j] : 0;
		}
	}
	return ans;
}

时间复杂度O(N^2)

思路二:归并排序解法

学过归并排序的朋友,应该都了解归并排序是如何变有序的,归并排序变有序的过程可以利用起来求很多问题

我们要找比当前数小的, 不妨换个思路,我们找它的右边有几个比它大的
我们来分析一下
image.png

code
int MergeSortSmallSum(vector<int>& nums)
{
	//不改变原数组,拷贝一个新数组,归并排序还要使用一个临时数组,所以直接开辟两个
	vector<int> data = nums;
	vector<int> tmp;
	tmp.resize(data.size());

	int rangeN = 1;

	int ans = 0;

	int n = data.size();
	while (rangeN < n)
	{
		for (int i = 0; i < n; i += 2 * rangeN)
		{
			int begin1 = i;
			int end1 = i + rangeN - 1;
			int begin2 = i + rangeN;
			int end2 = i + 2 * rangeN - 1;
			int k = begin1;
			//修正
			if (end1 >= n)
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

			//归并
			int j = begin1;
			while (begin1 <= end1 && begin2 <= end2)
			{
				//如果是左组先放,就+=右边剩余个数 乘以当前值
				if (data[begin1] < data[begin2])
				{
					ans += data[begin1] * (end2 - begin2 + 1);
					tmp[j++] = data[begin1++];
				}
				else
				{
					//此处要注意: 如果相等,就要走右组,因为无法确定右边有多少个比它大
					tmp[j++] = data[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = data[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = data[begin2++];
			}


			for (; k < j; ++k)
			{
				data[k] = tmp[k];
			}
		}

		rangeN *= 2;
	}
	return ans;
} 

其实代码也比较简单,归并排序的逻辑, 然后累加了一下,关键是思路,归并的过程中
1个数为1组的时候,就没有小和
2个数为1组的时候,就可能有小和产生, 而产生小和累加之后,这已经变成有序了,下次归并就不用考虑这里

所以每次都把组内的小和求出 ,所以时间复杂度是O(N * logN)

对数器测试

这里我就只放测试代码,不放上面两个函数了,需要的可以去上面copy

//对数器测试--
//申请一个随机vector
vector<int>& getRandomVector(int maxsize)
{
	int size = rand() % maxsize + 2;
	vector<int>* v = new vector<int>;
	v->resize(size);

	for (int i = 0; i < size; ++i)
	{
		v->operator[](i) =(rand() % 100 - rand() % 100);
	}

	return *v;
}


int main(void)
{
	srand((unsigned int)time(nullptr));
	int maxsize = 100;

	int times = 10000;
	int i = 0;
	for (i = 0; i < times; i++)
	{
		vector<int>& v = getRandomVector(maxsize);
		
		if (smallSum(v) != MergeSortSmallSum(v))
		{
			cout << "error" << endl;
			break;
		}

		delete& v;
	}

	if (i == times)
		cout << "Nice" << endl;
	return 0;
}

逆序对问题(LeetCode链接)

image.png

这个题其实思路和上一题非常相似,逆序么,就是每个数右边有几个比它小的,就组成几对,累加起来就OK

我们来分析一下
image.png

code

class Solution {
public:
    int MergeSortReversePairs(vector<int>& nums)
    {
        //归并
        vector<int> data = nums;
        vector<int> tmp = data;

        int rangeN = 1;
        int n = data.size();
        int ans = 0;

        while(rangeN < n)
        {
            for(int i = 0; i < n; i += 2 * rangeN)
            {
                int begin1 = i;
                int end1 = i + rangeN - 1;
                int begin2 = i + rangeN;
                int end2 = i + 2 * rangeN - 1;
                //区间修正
                if(end1 >= n)
                {
                    end1 = n - 1;
                    begin2 = n;
                    end2 = n - 1;
                }
                else if(begin2 >= n)
                {
                    begin2 = n;
                    end2 = n - 1;
                }
                else if(end2 >= n)
                {
                    end2 = n - 1;
                }

                int index = begin1;
                int k = begin1;

                while(begin1 <= end1 && begin2 <= end2)
                {
                    //如果等于走右组
                    if(data[begin1] > data[begin2])
                    {
                        //加上右边有多少对
                        ans += (end2 - begin2 + 1);
                        tmp[index++] = data[begin1++];
                    }
                    else
                    {
                        tmp[index++] = data[begin2++];
                    }
                }

                while(begin1 <= end1)
                {
                    tmp[index++] = data[begin1++];
                }
                while(begin2 <= end2)
                {
                    tmp[index++] = data[begin2++];
                }

                for(; k < index; ++k)
                {
                    data[k] = tmp[k];
                }
            }
            rangeN *= 2;
        }
        return ans;
    }

    int reversePairs(vector<int>& nums) {

        return MergeSortReversePairs(nums);
    }
};

image.png

这道题就过了,不用写对数器了,直接在LeetCode上跑就OK

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
对计数问题在一个数组中,如果存在两个元素 a[i] 和 a[j],且满足 i < j 且 a[i] > a[j],则称这个组合 (a[i], a[j]) 为一个对。解决这个问题的一种常见算法是使用归并排。 下面是使用 C++ 实现对计数问题的代码示例: ```cpp #include <iostream> #include <vector> using namespace std; long long merge(vector<int>& nums, int l, int mid, int r) { vector<int> temp(r - l + 1); int i = l, j = mid + 1; long long count = 0; for (int k = 0; k <= r - l; k++) { if (i > mid) { // 左半部分已经全部放入临时数组 temp[k] = nums[j++]; } else if (j > r) { // 右半部分已经全部放入临时数组 temp[k] = nums[i++]; } else if (nums[i] <= nums[j]) { // 左半部分当前元素小于等于右半部分当前元素 temp[k] = nums[i++]; } else { // 左半部分当前元素大于右半部分当前元素,构成对 temp[k] = nums[j++]; count += mid - i + 1; // 统计对个数 } } // 将临时数组的元素复制回原数组 for (int k = 0; k <= r - l; k++) { nums[l + k] = temp[k]; } return count; } long long mergeSort(vector<int>& nums, int l, int r) { if (l >= r) { return 0; // 当只有一个元素时,没有对 } int mid = l + (r - l) / 2; long long count = 0; count += mergeSort(nums, l, mid); // 左半部分的对个数 count += mergeSort(nums, mid + 1, r); // 右半部分的对个数 count += merge(nums, l, mid, r); // 合并左右两部分,并统计对个数 return count; } long long reversePairs(vector<int>& nums) { return mergeSort(nums, 0, nums.size() - 1); } int main() { vector<int> nums = {7, 5, 6, 4}; long long count = reversePairs(nums); cout << "对个数:" << count << endl; return 0; } ``` 以上代码使用归并排的思想,通过递归将数组分割成两半,然后再合并两个有数组,并统计对的个数。时间复杂度为 O(nlogn),其中 n 是数组的长度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值