目录
303. Range Sum Query - Immutable
304. Range Sum Query 2D - Immutable
1109. Corporate Flight Bookings
209. Minimum Size Subarray Sum
1456. Maximum Number of Vowels in a Substring of Given Length
数组求和问题
303. Range Sum Query - Immutable
(1) Range Sum Query - Immutable - LeetCode
初始思路是直接计算累加和
class NumArray {
public:
NumArray(vector<int>& nums) {
for(int elem:nums)
arr.push_back(elem);
}
int sumRange(int left, int right) {
int sum = 0;
for(int i=left;i<=right;i++){
sum = sum + arr[i];
}
return sum;
}
private:
vector<int> arr;
};
由于使用了一个for循环,每调用一次sumRange()的时间复杂度是O(n)
实现时间复杂度O(1)的方法:使用累加和数组
class NumArray {
public:
NumArray(vector<int>& nums) {
accusum.push_back(0);
for(int elem:nums){
accusum.push_back(accusum.back()+elem);
}
}
int sumRange(int left, int right) {
return accusum[right+1]-accusum[left];
}
private:
vector<int> accusum;
};
实现了求任意子数组的和
同类题处理二维数组:
304. Range Sum Query 2D - Immutable
(1) Range Sum Query 2D - Immutable - LeetCode
class NumMatrix {
public:
NumMatrix(vector<vector<int>>& matrix) {
//第0行和第0列设置为0
//累积和数组从第1行和第1列开始
col = matrix[0].size()+1;
row = matrix.size()+1;
accusum = vector<vector<int>>(row,vector<int>(col,0));//初始化为0
for(int i=1;i<row;i++){
for(int j=1;j<col;j++){
accusum[i][j] = accusum[i-1][j]+accusum[i][j-1]-accusum[i-1][j-1]+matrix[i-1][j-1];
}
}
}
int sumRegion(int row1, int col1, int row2, int col2) {
row1++;col1++;row2++;col2++;
return accusum[row2][col2]-accusum[row2][col1-1]-accusum[row1-1][col2]+accusum[row1-1][col1-1];
}
private:
vector<vector<int>> accusum;//前[0,0,i,j]矩形累积和
int col;//累积和数组的列数
int row;//累计和数组的行数
};
涉及子数组时的思考方向:
560. Subarray Sum Equals K
(1) Subarray Sum Equals K - LeetCode
前面的303实现了任意一个子数组的求和,而本题是查找所有可能的子数组的和
利用累积和数组可以想到暴力解法:
public:
int subarraySum(vector<int>& nums, int k) {
//穷举法——基于累积数组
//创建累积数组
vector<int> accusum ;
accusum.push_back(0);//前0个元素的和为0
for(int elem:nums){
accusum.push_back(accusum.back()+elem);
}
int count= 0;
//基于累积数组进行查找
for(int i=1;i<accusum.size();i++){
for(int j=0;j<i;j++){
int res = accusum[i]-accusum[j];
if(res==k) count++;
}
}
return count;
}
因为有两层循环进行遍历,所以时间复杂度是O(n^2),过高未通过
是否能消掉一层循环?
由k与累积和的关系:k == accusum[i]-accusum[j];可见,两次查找 accusum数组代价 高
修改后:accusum[j] = accusum[i]-k;这样关于任意子数组和的查找问题就转换为累积数组的查找问题
考虑使用哈希,只要累计和数组中存在一个元素值等于 accusum[i]-k则说明存在子数组和为k
按照i遍历时,对于每个accusum[i],存在m个元素值等于accusum[i]-k则说明存在m个子数组和为k。可以用哈希记录元素值出现的次数,这样只用找到这个元素就找到了对应的出现次数,元素即累计和的元素,有[accusum[i]:出现次数]
在哈希中找到这个元素的时间为O(1)
边求sum[i]边更新哈希表:
所求sum已经出现过→出现次数+1
所求sum未出现过→加入哈希表,出现次数1
同时查找哈希表:存在key为accusum[i]-k的哈希表项目则说明存在子数组和为k,将出现次数
最终遍历完整个数组
int subarraySum(vector<int>& nums, int k) {
map<int,int> res_sum;
res_sum[0]++;//初始化sum=0
int count = 0;
int sum = 0;
for(int i=0;i<nums.size();i++){
//计算累积数组
sum = nums[i]+sum;
//查找map
if(res_sum[sum-k])
count = count + res_sum[sum-k];
//更新map——放在查找map后面,防止[[1],0]的特殊情况
res_sum[sum]++;
}
return count;
}
将对不同子区间同时加一个数转换为O(1)时间内的差分数组处理
原理是一个子区间同时加一个数时,这整个子区间元素之间的差值不变,子区间首元素与其前一个元素之间的差值改变,子区间末尾元素与其后一个元素之间的差值也改变
利用区间首元素diff[i]与其前一个元素diff[i-1]之间的差值改变diff,与diff[i-1]可以diff[i]:
利用整个区间首元素可以求整个数组最终的值——累加数组的技巧
1094. Car Pooling
bool carPooling(vector<vector<int>>& trips, int capacity) {
//使用一个动态数组保存每个trip上的乘客数量
//计算trip上的 乘客数量相当于在做子数组动态求和→使用差分数组的 思路
vector<int> diff(1001,0);
for(int i=0;i<trips.size();i++){
int num= trips[i][0];
int start = trips[i][1];
int end = trips[i][2] - 1;//toi时这一站已经下车了
diff[start] += num;
if(end+1 < diff.size()) diff[end+1] -= num;
}
for(int i=1;i<diff.size();i++){
diff[i] += diff[i-1];
if(diff[i-1] > capacity) return false;
}
return true;
}
1109. Corporate Flight Bookings
(1) Corporate Flight Bookings - LeetCode
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
//差分数组计算最后的累加情况
vector<int> diff(n+1,0);//下标0不使用
for(int i=0;i<bookings.size();i++){
//子数组范围
int start = bookings[i][0];
int end = bookings[i][1];
//所加的值
int k = bookings[i][2];
//处理累加数组
diff[start] += k;
if(end<n) diff[end+1] -= k;
}
//通过差分数组求arr
for(int i=1;i<diff.size();i++){
diff[i] += diff[i-1];
diff[i-1] = diff[i];
}
diff.pop_back();
return diff;
}
考虑双指针
11. Container With Most Water
(1) Container With Most Water - LeetCode
class Solution {
public:
int maxArea(vector<int>& height) {
//头尾指针读取
int begin = 0;
int end = height.size()-1;
int len = 0;
int hi = 0;
int res = 0;
while(begin < end){
len = end - begin;
hi = findMin(height[begin],height[end]);
res = findMax(res,len*hi);
//小的往前找到一个比它大的
while(height[begin] <= hi && begin < end)
begin++;
while(height[end] <= hi && begin < end)
end--;
}
return res;
}
int findMin(int a,int b){
return a>b?b:a;
}
int findMax(int a,int b){
return a>b?a:b;
}
};
209. Minimum Size Subarray Sum
(1) Minimum Size Subarray Sum - LeetCode
初始思路:
i维护子数组的初始位置,j维护子数组的结束位置。
保持向前遍历,并判断i,j移动位置:
i和j维护的子数组区间累计和sum<target,继续向前加入一个元素,所以j++。
sum>=target,满足条件,更新length。同时扔掉一个后再继续
time complexity:O(n)
int minSubArrayLen(int target, vector<int>& nums) {
if(nums.size()==0) return 0;
//双指针维护子数组长度
int i = 0;
int j = 0;
int length = 0;
int sum = nums[0];
while(j<nums.size()){
if(sum >= target){
if(length==0 || length > j-i+1){
length = j-i+1;
}
sum -= nums[i];
i++;//仍掉一个,继续向前检索
}
else{
j++;
if(j<nums.size()){
sum += nums[j];
}
}
}
return length;
}
1456. Maximum Number of Vowels in a Substring of Given Length
(1) Maximum Number of Vowels in a Substring of Given Length - LeetCode
借鉴字符串的子串匹配思路,双指针跳跃判断
runcode成功但是admit超时
int maxVowels(string s, int k) {
int vowels[26] = {1,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1};
//双指针——借鉴字符串匹配的思路
int res = 0;
int first = 0;//first维护第一个vowels
while(first < s.size()){
int cur_res = 0;
while(first<s.size()){
if(vowels[s[first]-'a']==1){
cur_res = 1;
break;
}
first++;
}//找到first维护的第一个vowels
int next = first;//next维护下一个vowels
bool next_change = false;
for(int i=1;i<k;i++){
if(first+i<s.size() && vowels[s[first+i]-'a']==1){
cur_res++;
if(!next_change){
next = first + i;//如果存在下一个vowels,first下一步跳转到此
next_change = true;
}
}
}
if(cur_res > res) res = cur_res;
if(!next_change){//k个中不存在vowels
first += k;
}
else{
first = next;
}
}
return res;
}
一步一步往前挪,不跳
转换为此前的子数组求和,由于固定长度,不需要双指针动态维护
往前走增加一个,为保证length,后面扔一个
int maxVowels(string s, int k) {
//转换为固定长度的子数组求和问题-元音个数和正好为sum
int vowels[26] = {1,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1};
int res = 0;
for(int i=0,cur_res=0;i<s.size();i++){
cur_res += vowels[s[i]-'a'];//增加前面一个
if(i>=k){
//往前移动丢掉后面一个,增加前面一个
cur_res -= vowels[s[i-k]-'a'];
}
if(res<cur_res) res = cur_res;
}
return res;
}
数组逆转
344. Reverse String
思路一:
数组可获取下标——使用尾指针方便,故可考虑首尾指针
void reverseString(vector<char>& s) {
int last = s.size()-1;
int first = 0;
while(first < last){
char temp = s[first];
s[first] = s[last];
s[last] = temp;
first++;
last--;
}
}
二维数组
48. Rotate Image
通过观察可得,顺时针旋转90度相当于先按列逆转,再将第i列放到第i行
第i列放到第i行==>联系镜像对称,只用交换对角线
void rotate(vector<vector<int>>& matrix) {
//按列进行逆转
int col = matrix[0].size();//列数
int row = matrix.size();//行数
for(int i=0;i<col;i++){
for(int first = 0,last = row-1;first<last;first++,last--){
int temp = matrix[first][i];
matrix[first][i] = matrix[last][i];
matrix[last][i] = temp;
}
}
//对角线交换——只用遍历上对角线
for(int i=0;i<row;i++){
for(int j=i+1;j<col;j++){
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
由于这两个操作互逆,考虑能否先对角线交换进行镜像对称再逆转
镜像对称处理后由于行列已互换,故原来的按列逆转现在可以换位按行逆转
思路二:镜像对称+按行逆转
仍基于此思路,逆时针旋转的处理思路则正好是先按行逆转,再镜像对称
总结:
逆时针旋转90度:镜像对称+按行逆转
顺时针旋转90度:按行旋转+镜像对称
54. Spiral Matrix
螺旋遍历就是按照4个方向:右下左上一圈,直到遍历完所有元素
循环圈数:(row+1)/2
基于此思路代码:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int row = matrix.size();
int col = matrix[0].size();
int n = row*col;
vector<int> res;
//按圈循环——(row+1)/2次
int toright = 0;
int toleft = row-1;
int up = 0;
int down = col-1;
while(n){
//遍历一圈
//1.toright从[i,i]开始
for(int i=toright;i<=down;i++){
res.push_back(matrix[toright][i]);
n--;
}
if(n==0) break;
//2.down
for(int i=toright+1;i<=toleft;i++){
res.push_back(matrix[i][down]);
n--;
}
if(n<=0) break;
//3.toleft
for(int i=down-1;i>=up;i--){
res.push_back(matrix[toleft][i]);
n--;
}
if(n<=0) break;
//4.up
for(int i=toleft-1;i>=toright+1;i--){
res.push_back(matrix[i][up]);
n--;
}
//圈不断缩窄
toright++;
toleft--;
up++;
down--;
}
return res;
}
观察到,一圈中的4个方向循环:右下左上存在共性
每次只是方向不同+遍历起点和终点不同。
方向改变可以维护一个数组
遍历位置如何确定?
观察四个方向遍历,发现:
5*4数组:
右4下4左3上3
右2下2左1上1
5*5数组:
右5下4左4上3
右3下2左2上1
右1
规律:
水平方向遍历:右col左col-1右col-2左col-3直到0;
竖直方向遍历:下row-1上row-2下row-3上row-4直到0;