10.有序数组的平方(977)
题目
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
已按 非递减顺序 排序
进阶:
- 请你设计时间复杂度为
O(n)
的算法解决本问题
思路及题解
-
解法一:暴力解法,不再赘述
class Solution { public: vector<int> sortedSquares(vector<int>& nums) { for(int i=0;i<nums.size();i++){ nums[i] *= nums[i]; } sort(nums.begin(),nums.end()); return nums; } };
-
解法二:双指针法
- 数组 nums 已经按照升序排列,那么,我们可以找到负数和非负数的分界线,因为非负数的平方按升序排列,负数的平方按降序排列
- 找到分界线后,只需要两个指针,一个往非负数方向走,另一个往负数方向走,将它们的平方比较大小,较小的放入新建的Vector中,同时指针移动,这样最后新建的Vector中的数据就会按升序排列
- 注意指针到达边界的判断条件
class Solution { public: vector<int> sortedSquares(vector<int>& nums) { vector<int> newNums; //找出分界点 //不能写 while(nums[i]<0) i++; 特定输入i会越界,如 nums = [-1] int negative = -1; for (int i = 0; i < nums.size(); ++i) { if (nums[i] < 0) { negative = i; } else { break; } } int i = negative; int j = i + 1; while(i>=0 || j<nums.size()){ if(i<0){//左边指针已经走完 newNums.push_back(nums[j]*nums[j]); j++; } else if(j == nums.size()){//右边指针已经走完 newNums.push_back(nums[i]*nums[i]); i--; } else if(nums[i]*nums[i] < nums[j]*nums[j]){//放入较小的数 newNums.push_back(nums[i]*nums[i]); i--; } else{//数字大小相同时,随便放一个数 newNums.push_back(nums[j]*nums[j]); j++; } } return newNums; } };
-
解法三:优化的双指针法
- 可设置对撞指针,从两边的数字开始添加。由于两边的数字的平方是较大的数,因此添加到新的数组中时要逆序添加,且较大的数需要先加入。这样做的好处是不用判断边界条件,当左右指针交错时,所有的数已经放入,循环停止
class Solution { public: vector<int> sortedSquares(vector<int>& nums) { vector<int> newNums(nums.size()); for(int i=0,j=nums.size() - 1, pos = nums.size() - 1;i<=j;pos--){ if(nums[i]*nums[i] > nums[j]*nums[j]){ newNums[pos] = nums[i]*nums[i]; i++; } else{ newNums[pos] = nums[j]*nums[j]; j--; } } return newNums; } };
11.长度最小的子数组(209)
题目
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其总和大于等于 target
的长度最小的 连续
子数组
[numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度**。**如果不存在符合条件的子数组,返回 0
。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105
进阶:
- 如果你已经实现
O(n)
时间复杂度的解法, 请尝试设计一个O(n log(n))
时间复杂度的解法。
思路及题解
-
解法一:暴力解法
- 两层 for 循环,不断查找符合条件的子序列(该解法在力扣会超时)
class Solution { public: int minSubArrayLen(int s, vector<int>& nums) { int result = INT32_MAX; // 最终的结果 int sum = 0; // 子序列的数值之和 int subLength = 0; // 子序列的长度 for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为i sum = 0; for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为j sum += nums[j]; if (sum >= s) { // 一旦发现子序列和超过了s,更新result subLength = j - i + 1; // 取子序列的长度 result = result < subLength ? result : subLength; break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break } } } // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列 return result == INT32_MAX ? 0 : result; } };
-
解法二:滑动窗口
-
涉及求连续的子数组问题时,可以考虑滑动窗口
-
滑动窗口,其实也是一种双指针。不断调整两个指针之间的距离,得出我们想要的结果
-
首先要思考 如果用一个for循环,那么参数 i 应该表示 滑动窗口的起始位置,还是终止位置。
如果 i 表示的是起始位置,那么怎么遍历剩下的终止位置呢?另一个指针只能从这个指针的位置开始遍历,那么这又变成了暴力解法
-
所以,一层for循环的参数应该表示滑动窗口的终止位置
-
首先慢指针和快指针都指向初始位置。循环开始,快指针向前不断移动,知道经过的数字的和 sum 大于等于 target ,此时移动慢指针,并且不断更新最小数组长度 result 以及经过的数字的和 sum。若某次移动慢指针后的和小于 sum,则继续移动快指针,直到 sum 再次大于 target,如此循环往复
-
result 的初始值设为一个很大的值(最好是解法一中Int的最大值),如果最后这个值没改变,说明没有符合条件的子序列,返回0
-
这种方法大大减少了比较子数组的次数,滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
-
不要以为for里放一个while就以为是O(n2), 主要是看每一个元素被操作的次数,每个元素在滑动后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
class Solution { public: int minSubArrayLen(int target, vector<int>& nums) { int i = 0,result = 1000000000,sum = 0; for(int j=0;j<nums.size();j++){ sum += nums[j]; while(sum >= target){ int subLen = j - i + 1; result = min(result,subLen); sum -= nums[i]; i++; } } return result == 1000000000 ? 0 : result; } };
-