【小白爬Leetcode547】区域和检索 - 数组可修改 Range Sum Query - Mutable

这篇博客主要介绍了LeetCode 307题目的解决方案,包括暴力解法和线段树方法。暴力解法通过额外数组记录前缀和,但修改和查询的时间复杂度均为O(n)。线段树则提供了一种在对数时间内完成区间查询和修改的方法,虽然需要额外的空间,但在复杂场景下更优。
摘要由CSDN通过智能技术生成

【小白爬Leetcode547】区域和检索 - 数组可修改 Range Sum Query - Mutable

Leetcode307 m e d i u m \color{#FF4500}{medium} medium
点击进入原题链接:单词搜索II Word SearchII

Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.

The update(i, val) function modifies nums by updating the element at index i to val.
在这里插入图片描述
Constraints:

  • The array is only modifiable by the update function.
  • You may assume the number of calls to update and sumRange function is distributed evenly.
  • 0 <= i <= j <= nums.length - 1

思路一 暴力解法

用一个数组sum记录前i个元素的和,那么区间[i,j]和就是sum[j]-sum[i],在创建数组的时候需要遍历一遍整个数组,复杂度O(n);修改元素i的时候要修改sum[i]以及之后的所有元素,复杂度为O(n);求区间和直接拿两项sum[j]-sum[i]相减,时间复杂度为O(1)。但由于题目说修改和求和的数量均等,那么总体时间复杂度为O(n)。
空间复杂度: 额外用了一个数组记录前i项和,因此是O(n)。

class NumArray {
public:
   NumArray(vector<int>& nums) :_nums(nums) {
       if(nums.empty()) return;
   	//initialize O(n)
   	sum.push_back(nums[0]);
   	for (int i = 1; i < nums.size(); i++) {
   		sum.push_back(sum[i - 1] + nums[i]);
   	}
   }
   
   void update(int i, int val) {
       //
       for(int j=i;j<sum.size();j++){
           sum[j] -=(_nums[i]-val);
       }
       _nums[i] = val;
   }
   
   int sumRange(int i, int j) {
       if(i==0) return sum[j];
       return sum[j]-sum[i-1];
   }
private:
   vector<int> sum; //前i项和
   vector<int>& _nums; 
};

这里有个需要注意的地方,vector<int>& _nums;是一个引用,他作为类成员变量,必须要在构造函数的初始化列表里赋值NumArray(vector<int>& nums) :_nums(nums) {...}

思路二 线段树

线段树是一种非常灵活的数据结构,它可以用于解决多种范围查询问题,比如在对数时间内从数组中找到最小值、最大值、总和、最大公约数、最小公倍数等。
在这里插入图片描述

这里可以将数组空间进行二分,左孩子为左半边数组元素的和,右孩子为右半边数组元素的和。

这里我是用递归法实现的,数组中间会存在大量空的0(比如上图中的下表9,10,13,14…都是空节点)。为什么呢?因为在产生新的左孩子和右孩子的时候,默认前面的堂兄弟节点都是存在的。以上图中下标11的节点为例,下表11直接由节点5产生(5*2+1),而没有考虑节点9和节点10是空的(因为下标4已经是叶节点了),因此会产生一些空节点。
最坏的情况下,需要给数组预留的空间是满二叉树的情况。如果有N个数字,一般来说预留4*N的空间是足够的。

class NumArray {
public:
	NumArray(vector<int>& nums) :_nums(nums) {
		if (nums.empty()) return;
		//initialize O(n)
		SegmentTree = vector<int>(4 * nums.size(), 0);//一般线段树数组的大小是线性数组的4倍
		//build a segment tree
		SegmentTree_builder(0, 0, nums.size() - 1, SegmentTree, nums);
	}

	void update(int i, int val) {
		int d = val - _nums[i];
		_nums[i] = val;
		SegmentTree_update(i, d, 0, 0, _nums.size() - 1, SegmentTree);
	}

	int sumRange(int i, int j) {
		return SegmentTree_search(i, j, 0, 0, _nums.size() - 1, SegmentTree);
	}
private:
	void SegmentTree_builder(int pos, int left, int right, vector<int>& SegmentTree, vector<int>& nums) {
		if (left == right) {
			SegmentTree[pos] = nums[left];
			return;
		}
		int mid = (left + right) / 2;
		//左子树
		SegmentTree_builder(2 * pos + 1, left, mid, SegmentTree, nums);
		//右子树
		SegmentTree_builder(2 * pos + 2, mid + 1, right, SegmentTree, nums);
		//本节点等于左右节点之和
		SegmentTree[pos] = SegmentTree[2 * pos + 1] + SegmentTree[2 * pos + 2];
	}
	void SegmentTree_update(const int& i, const int& d, int pos, int left, int right, vector<int>& SegmentTree)
	{
		SegmentTree[pos] += d; //更新当前节点
		int mid = (left + right) / 2;
		if (i == left && i==right) return;
		if (i <= mid) SegmentTree_update(i, d, 2 * pos + 1, left, mid, SegmentTree);
		else SegmentTree_update(i, d, 2 * pos + 2, mid + 1, right, SegmentTree);
	}
	int SegmentTree_search(const int& i, const int&j, int pos, int left, int right, vector<int>& SegmentTree)
	{
		if (i > right || j < left) return 0; //无交集
		if (i <= left && j >= right) return SegmentTree[pos]; //查找的空间被要求的空间所包含

		int mid = (left + right) / 2;
		int left_val = SegmentTree_search(i, j, 2 * pos + 1, left, mid, SegmentTree);
		int right_val = SegmentTree_search(i, j, 2 * pos + 2, mid + 1, right, SegmentTree);
		return left_val + right_val;
	}

	vector<int> SegmentTree;
	vector<int>& _nums;
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值