LeetCode每日一题--327. 区间和的个数(归并排序)

题目:跳转至 327. 区间和的个数
给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lower 和 upper。
区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。
说明:
最直观的算法复杂度是 O(n2) ,请在此基础上优化你的算法。
示例:
输入: nums = [-2,5,-1], lower = -2, upper = 2,
输出: 3
解释: 3个区间分别是: [0,0], [2,2], [0,2],它们表示的和分别为: -2, -1, 2。

class Solution {
public:
    int countRangeSum(vector<int>& nums, int lower, int upper) {

    }
};

思路:
没看懂题目意思,直接看题解…官方解法一归并排序上面有讲解还是没看懂,继续翻题解。
懂了,以上面示例的例子分析,i,j皆为整数数组下标,区间和S(i,j)分别代表的区间及他们的和如下排列:

S(i,j)对应数组值区间和是否落在规定区间内
S(0,0)[-2]-2
S(0,1)[-2,5]3
S(0,2)[-2,5,-1]2
S(1,1)[5]5
S(1,2)[5,-1]4
S(2,2)[2]2

其中,落在[-2,2]中的只有3个区间,即结果为3。

看懂了题意但是题解我还是没搞懂。先跳过去在后半段把归并排序复习了一下。

class Solution {
public:
    int countRangeSumRecursive(vector<long>& sum, int lower, int upper, int left, int right) {
        //递归拆分待排序数列
        if (left == right)
            return 0;
        else {
            int mid = (left + right) / 2;
            //左右边数组分别递归,结算各自区间上的前缀和对数
            int n1 = countRangeSumRecursive(sum, lower, upper, left, mid);
            int n2 = countRangeSumRecursive(sum, lower, upper, mid + 1, right);
            int ret = n1 + n2;
            // 首先统计下标对的数量,sum中每一个右边元素减去左边元素等于一段区间和
            int i = left;
            int l = mid + 1;  //初始均指向右数组的起始节点
            int r = mid + 1;  //如果区间和小于lower,l右移,直到l指向的区间和大于等于lower,同理,r也是指向a第一个大于upper的位置
            while (i <= mid) {
                while (l <= right && sum[l] - sum[i] < lower) l++;
                while (r <= right && sum[r] - sum[i] <= upper) r++;
                ret += (r - l); //此时,[l...r)符合条件在[lower,upper]中
                i++;  //继续下一个左区域
            }
            //要做到上述结果,左右两数组必须有序,因此在每一次的双指针遍历后都应该将归并两个数组
            vector<long> sorted(right - left + 1);
            int p1 = left, p2 = mid + 1;
            int p = 0;
            while (p1 <= mid || p2 <= right) {
                if (p1 > mid) {
                    sorted[p++] = sum[p2++];
                } else if (p2 > right) {
                    sorted[p++] = sum[p1++];
                } else {
                    if (sum[p1] < sum[p2]) {
                        sorted[p++] = sum[p1++];
                    } else {
                        sorted[p++] = sum[p2++];
                    }
                }
            }
            //修改待排序数组
            for (int i = 0; i < sorted.size(); i++) {
                sum[left + i] = sorted[i];
            }
            return ret;
        }
    }
    //转前缀和后不再对i,j位置敏感,只要排序后的前缀和数组中有一对前缀和满足sum[j]-sum[i]属于[lower,upper]即算一个序号对,i,j位置并不影响
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        long s = 0;
        vector<long> sum{0}; 
        for(auto& v: nums) {  //nums里面的值累加计算各自下标往前的前缀和
            s += v;
            sum.push_back(s);
        }
        //利用归并排序,并在合并时结算答案
        return countRangeSumRecursive(sum, lower, upper, 0, sum.size() - 1);
    }
};


复习一下归并排序:
合并是指把两个有序数组arr1和arr2按顺序放入一个更大的数组arr中,每次按下标比较两个数组对应的数据中的较小值(从小到大排列),依次存入arr中。
而要获取有序数组arr1和arr2,可以利用分治法,把原数组按下标从中分割,会出现左右两个数组,对获取到的数组继续对半切分,直至最后切分到的数组中只剩下一个数据值,单个数据自然有序。两两合并到只剩一个数组,归并排序就完成了。

//递归版
#include <iostream>
#include <vector>
using namespace std;

template<typename T>
void merge(vector<T>& arr,int left,int mid,int right){
	//合并arr中下标left至mid,mid+1至right的部分
	vector<T> temp(right-left+1);  //定义合并后的数组
	vector<T> leftarr(arr.begin()+left,arr.begin()+mid+1);  //获取分割后的左侧数组,初始值为下标left到mid对应的数据
	vector<T> rightarr(arr.begin()+mid+1,arr.begin()+right+1);  //获取分割后的右侧数组,初始值为下标mid+1到right对应的数据
	int i=0,j=0,k=left;  //i,j分别指向分割后的两个数组的下标,k指向合并后的原数组(直接在原本数组上变动)
	int leftsize=mid-left+1,rightsize=right-mid;  //获取各自数组大小
	while(i<leftsize && j<rightsize)  //两个数组都还有值时
		arr[k++]=leftarr[i]<rightarr[j]?leftarr[i++]:rightarr[j++]; //选出存入较小值
	while(i<leftsize)  //一个数组没有值后直接循环存入另一个数组内的有序值
		arr[k++]=leftarr[i++];
	while(j<rightsize)
		arr[k++]=rightarr[j++];
}

template<typename T>
void mergeSort(vector<T>& arr,int left,int right){
	if(left>=right)  //结束条件,当只有一个数据时停止分割
		return;
	int mid=(left+right)/2;
	mergeSort(arr,left,mid);  //递归处理,把数组分割成左右两部分
	mergeSort(arr,mid+1,right);
	merge(arr,left,mid,right);  //合并
}

int main()
{
	vector<int> arr={1,8,7,8,0,3,9};
	mergeSort(arr,0,arr.size()-1);
	for(auto& x:arr)
		cout<<x<<endl;
   return 0;
}
//迭代版
#include <iostream>
#include <vector>
using namespace std;

template<typename T>
void mergeSort(vector<T>& arr,int len){
	vector<T> temp(arr.begin(),arr.end());  //把arr的值赋给temp
	for(int i=1;i<len;i*=2){  //逐级扩展,每次比较2,4,8...个
		for(int start=0;start<len;start+=2*i){  //对每一个组内数据进行排序
			int left=start;  //定义组的左中右三个指针值,加上min防越界
			int mid=min(start+i,len);
			int right=min(start+2*i,len);
			int k=left;  //k为对应在原数组中的下标
			int left1=left,right1=mid;  //数组被分为左右两个数组,比较下标对应的值大小存在原数组上
			int left2=mid,right2=right;
			while(left1<right1 && left2<right2)
				arr[k++]=temp[left1]<temp[left2]?temp[left1++]:temp[left2++];
			while(left1<right1)  //当一个数组值用完,另一个数组还有值时直接存放
				arr[k++]=temp[left1++];
			while(left2<right2)
				arr[k++]=temp[left2++];
		}
		temp=arr;	//重新赋值给temp
	}
}

int main()
{
	vector<int> arr={1,9,7,8,0,3,2};
	mergeSort(arr,arr.size());
	for(auto& x:arr)
		cout<<x<<endl;
   return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值