leetcode 315 计算右侧小于当前元素的个数(分治算法/二叉排序树)

已知两个已排序数组,将这两个数组合并为一个排序数组

void merge_sort_two_vec(vector<int>& sub_vec1, vector<int>& sub_vec2, vector<int>& vec) {
	int i = 0;
	int j = 0;
	while (i < sub_vec1.size() && j < sub_vec2.size()) {
		if (sub_vec1[i] <= sub_vec2[j]) {
			vec.push_back(sub_vec1[i]);
			i++;
		}
		else{
			vec.push_back(sub_vec2[j]);
			j++;
		}
	}
	for (; i < sub_vec1.size(); i++) {
		vec.push_back(sub_vec1[i]);
	}
	for (; j < sub_vec2.size(); j++) {
		vec.push_back(sub_vec2[j]);
	}
}

测试代码如下:

int main() {
	int test1[] = { 2,5,8,20 };
	int test2[] = { 1,3,5,7,30,50 };
	vector<int> sub_vec1;
	vector<int> sub_vec2;
	vector<int> vec;
	for (int i = 0; i < 4; i++) {
		sub_vec1.push_back(test1[i]);
	}
	for (int i = 0; i < 6; i++) {
		sub_vec2.push_back(test2[1]);
	}
	merge_sort_two_vec(sub_vec1, sub_vec2, vec);
	for (int i = 0; i < vec.size(); i++) {
		printf("[%d]", vec[i]);
	}
	printf("\n");
	return 0;
}

再参考leetcode第21题合并两个有序链表的思路:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode temp_head(0);      //设置临时头节点temp_head
        ListNode *pre = &temp_head;    //使用pre指针指向temp_head
        while(l1 && l2){
            if(l1->val < l2->val){    //l1和l2同时不为空时,对它们进行比较
                pre->next = l1;
                l1 = l1->next;
            }
            else{
                pre->next = l2;
                l2 = l2->next;
            }
            pre = pre->next;
        }
        if(l1){   //如果l1有剩余
            pre->next = l1;
        }
        if(l2){    //如果l2有剩余
            pre->next = l2;
        }
        return temp_head->next;
    }
};

分治算法:
将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解后进行合并,就可以得到原问题的解
一般步骤:
1、分解,将要解决的问题划分成若干规模较小的同类问题;
2、求解,当子问题划分得足够小时,用简单的方法解决
3、合并,按原问题的要求,将子问题的解逐层合并构成原问题的解
归并排序复杂度分析:
设有n个元素,n个元素归并排序的时间为T(n)
总时间 = 分解时间 + 解决子问题时间 + 合并时间
分解时间:即对yu原来问题拆分为两个字子问题的时间,复杂度O(n)
解决子问题时间:解决两个子问题的时间2T(n/2)
合并时间:即对两个已排序数组归并的时间,复杂度O(n)
T(n) = 2T(n/2) + 2O(n) = O(n + 2n/2 + 4n/4 +……+n*1) = O(nlogn)

void merge_sort(vector<int>& vec){
	if (vec.size() < 2) {         //子问题足够小时,直接求解
		return;
	}
	int mid = vec.size() / 2;
	vector<int> sub_vec1;
	vector<int> sub_vec2;
	for (int i = 0; i < mid; i++) {
		sub_vec1.push_back(vec[i]);
	}
	for (int i = mid; i < vec.size(); i++) {
		sub_vec2.push_back(vec[i]);
	}
	merge_sort(sub_vec1);    //对拆解后的两个子问题进行求解
	merge_sort(sub_vec2);
	vec.clear();
	merge_sort_two_vec(sub_vec1, sub_vec2, vec);   //合并,将子问题的解进行合并
}

测试程序如下:

int main() {
	vector<int> vec;
	int test[] = { 5,-4,-9,0,12,34,2,1,10,-10 };
	for (int i = 0; i < 10; i++) {
		vec.push_back(test[i]);
	}
	merge_sort(vec);
	for (int i = 0; i < vec.size(); i++) {
		printf("[%d] ", vec[i]);
	}
	printf("\n");
	return 0;
}

**给定一个整数数组 nums,按要求返回一个新数组 counts。**数组 counts 有该性质: counts[i] 的值是 nums[i]右侧小于nums[i]的元素的数量
示例:
输入: [5,2,6,1]
输出: [2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.
思考:
最暴力的方法,即对每个元素扫描其右侧比它小的数,累加个数。假设数组元素个数为N,算法复杂度O(N^2)(该算法在leetcode上无法通过,超时)
算法思路:在归并两排数组时,当需要将前一个数组元素的指针i指向的元素插入时,对应的count[i],即为指向后一个数组的指针j的值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        vector<pair<int,int>> vec;
        vector<int> count;
        for (int i = 0; i < nums.size(); i++){
            vec.push_back(make_pair(nums[i], i));  //将nums[i]与i绑定为pair<nums[i],i>
            count.push_back(0);
        }
        merge_sort(vec, count);
        return count;
    }
private:
    void merge_sort_two_vec(vector<pair<int,int>> &sub_vec1,
                            vector<pair<int,int>> &sub_vec2,
                            vector<pair<int,int>> &vec,
                            vector<int> &count){
        int i = 0;
	    int j = 0;
	    while (i < sub_vec1.size() && j < sub_vec2.size()) {
		    if (sub_vec1[i].first <= sub_vec2[j].first) {
                count[sub_vec1[i].second] += j;
			    vec.push_back(sub_vec1[i]);
			    i++;
		    }
		    else{
		    	vec.push_back(sub_vec2[j]);
			    j++;
		    }
	    }
	    for (; i < sub_vec1.size(); i++) {
            count[sub_vec1[i].second] += j;
		    vec.push_back(sub_vec1[i]);
	    }
	    for (; j < sub_vec2.size(); j++) {
		    vec.push_back(sub_vec2[j]);
	    }
    }
    void merge_sort(vector<pair<int,int>> &vec, vector<int> &count){
        if(vec.size() < 2){   //子问题足够小时,直接求解
            return;
        }
        int mid = vec.size() / 2;
        vector<pair<int,int>> sub_vec1;
        vector<pair<int,int>> sub_vec2;
        for (int i = 0; i < mid; i++){   //对原问题进行分解
            sub_vec1.push_back(vec[i]);  //拆分为两个规模相同的数组
        }
        for (int i = mid; i < vec.size(); i++){
            sub_vec2.push_back(vec[i]);
        }
        merge_sort(sub_vec1, count);  //对拆解后的两个子问题进行求解排序
        merge_sort(sub_vec2, count);
        vec.clear();
        //将子问题的解进行合并
        merge_sort_two_vec(sub_vec1, sub_vec2, vec, count); 
    }
};

测试程序如下:

int main() {
	vector<int> vec;
	int test[] = { 5,-4,-9,0,12,34,2,1,10,-10 };
	for (int i = 0; i < 10; i++) {
		vec.push_back(test[i]);
	}
	Solution solve;
	vector<int> result = solve.countSmaller(vec);
	for (int i = 0; i < result.size(); i++) {
		printf("[%d] ", result[i]);
	}
	printf("\n");
	return 0;
}

思考:将元素按照原数组逆置后的顺序插入到二叉查找树中,如何在元素插入时,计算已有多少个元素比当前插入元素小
算法思路:
设置变量count_small = 0, 记录在插入过程中,有多少个元素比插入节点值小;
若待插入节点值小于等于当前节点node值,node->count++,递归将该节点插入到当前节点左子树
若待插入节点值大于当前节点的node值,count_small += node->count + 1(当前节点左子树数量+1),递归将该节点插入到当前节点右子树。

struct BSTNode{
    int val;
    int count;
    BSTNode *left;
    BSTNode *right;
    BSTNode(int x) : val(x), left(NULL), right(NULL), count(0) {} 
};
//在插入节点时,当待插入节点insert_node小于等于当前node时count++
class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        std::vector<int> result;    //最终逆序数数组
        std::vector<BSTNode*> node_vec;   //创建的二叉树查找树节点池
        std::vector<int> count;    //从后向前插入过程中,比当前节点值小的count_small数组
        for (int i = nums.size() - 1; i >= 0; i--){
            node_vec.push_back(new BSTNode(nums[i]));
        }
        count.push_back(0);   //第一个节点count_small = 0
        for (int i = 1; i < node_vec.size(); i++){ //将第2到第n个节点插入到以第一个节点为根的二叉排序树中,在插入过程中计算每个节点的count_small
            int count_small = 0;
            BST_insert(node_vec[0], node_vec[i], count_small);
            count.push_back(count_small);
        }
        for (int i = node_vec.size() - 1; i >= 0; i--){
            delete node_vec[i];
            result.push_back(count[i]);
        }
        return result;
    }
private:
    void BST_insert(BSTNode* node, BSTNode* insert_node, int &count_small) {
	    if (insert_node->val <= node->val) {   //count_small二叉排序树中比insert_node值小的节点个数
            node->count++;                     //若是出现相等的情况则插入左边
	    	if (node->left) {
		    	BST_insert(node->left, insert_node, count_small);
		    }
		    else {
		    	node->left = insert_node;
		    }
	    }
	    else {
            count_small += node->count + 1;
	    	if (node->right) {
			    BST_insert(node->right, insert_node, count_small);
		    }
		    else {
			    node->right = insert_node;
	    	}
	    }
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值