给你一个按 非递减顺序 排序的整数数组 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)
的算法解决本问题
看到题目第一个想法,直接每个元素求平方,然后用无敌的sort()解决问题。元素遍历时间复杂度是O(n),sort()排序是
O(nlogn),搞定。
但是不知道是LeetCode没有import Array包还是怎么的,不能识别sort函数,那行吧,就用双指针吧。
其实看到题目有“顺序”这个关键词,不管是顺序还是逆序,只要是递增或者递减(可有重复元素),就该想到双指针了。我的想法是在负数区设置一个向左移动的负指针,正数区设置一个向右移动的正指针,每次移动指针就比较指针指向的数的平方的大小,将小的数放到按顺序新数组里,不就解决了吗?
接下来考虑指针“碰壁”的情况。当负指针指向第一个元素,或者正指针指向最后一个元素后,对应指针就不能移动了,否则会出现索引超出数组范围的情况。“碰壁”后,只需要继续移动另一个指针,把指针指向元素求平方后依次放入新数组即可。
不过还需要考虑几种特殊情况。首先题目里nums数组的长度是能为1的,而正负指针要能指向两个数。另外数组完全可以完全没有负数或者完全没有正数,这种情况下负指针或正指针就不存在了,也要单独考虑。
class Solution {
public int[] sortedSquares(int[] nums) {
int n = nums.length;
if(n == 1){
nums[0] *= nums[0];
return nums;
}
if (nums[0] >= 0){
for(int i = 0; i < n; i++){
nums[i] *= nums[i];
}
return nums;
}
int[] newnums = new int[n];
if(nums[n - 1] < 0){
for(int i = 0; i < n; i++){
newnums[i] = nums[n - 1 - i] * nums[n - 1 - i];
}
return newnums;
}
int pos = -1;
for(int i = 0; i < n; i++){
if(nums[i] >= 0){
pos = i;
break;
}
}
int neg = pos - 1;
int i = 0;
while (neg >= 0 && pos <= n - 1){
if (nums[pos] > -nums[neg]){
newnums[i++] = nums[neg] * nums[neg];
neg--;
} else {
newnums[i++] = nums[pos] * nums[pos];
pos++;
}
}
if (neg == -1){
while(pos < n){
newnums[i++] = nums[pos] * nums[pos];
pos++;
}
}
if (pos == n){
while(neg >= 0){
newnums[i++] = nums[neg] * nums[neg];
neg--;
}
}
return newnums;
}
}
看起来比较复杂!后面看了其他人的解答,才发现双指针完全可以不从中间移动到两边,而是从两边移动到中间,这样边界问题就可以不用考虑得那么复杂。
class Solution {
public int[] sortedSquares(int[] nums) {
int right = nums.length - 1;
int left = 0;
int[] result = new int[nums.length];
int index = result.length - 1;
while (left <= right) {
if (nums[left] * nums[left] > nums[right] * nums[right]) {
result[index--] = nums[left] * nums[left];
++left;
} else {
result[index--] = nums[right] * nums[right];
--right;
}
}
return result;
}
}
(参考:代码随想录)
双指针的题,从中间到两边,从两边到中间的都有,以后还是要更灵活地考虑问题。
另外学到个小技巧,
newnums[i++] = nums[pos] * nums[pos];
pos++;
这种写法略繁琐,但两个pos++的话指针就会移动两次了。但其实完全可以
newnums[i++] = nums[pos] * nums[pos++];
哈哈!确实比较刁钻,只写一个++,一开始没想到确实脑筋还不够灵活。
给定一个含有 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))
时间复杂度的解法。
看到连续子数组,基本就可以想到是要用滑动窗口了。简单来说就是设置左右两个指针,分别作为窗口的左右边界。先设置初始窗口,左边界为第一个元素,右边界为第一个满足条件的右边界元素。然后向右移动左指针,直到不满足条件,再向右移动右指针,直到满足条件。不断更新满足条件的lenmin,直到结束。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left = -1, right = -1;
int n = nums.length;
if (n == 1){
if (nums[0] >= target)
return 1;
else
return 0;
}
int sum = 0;
while (sum < target && right < n - 1){
sum += nums[++right];
}
if (sum < target)
return 0;
int lentmp = right - left;
int lenmin = lentmp;
while (left < right){
while(sum >= target){
sum -= nums[++left];
lentmp--;
}
lenmin = lenmin <= (lentmp + 1) ? lenmin : (lentmp + 1);
while (sum < target && right < n - 1){
sum += nums[++right];
lentmp++;
}
if (right == n - 1){
while(sum >= target){
sum -= nums[++left];
lentmp--;
}
lenmin = lenmin <= (lentmp + 1) ? lenmin : (lentmp + 1);
break;
}
}
return lenmin;
}
}
一开始没想好结束循环的条件,一直无限循环,直到debug才发现。后面实在想不出怎么结束,只好暴力解除循环,代码看起来就非常inelegant。如果一开始就想到会出现难以结束循环的情况,应该直接用for循环:
class Solution {
public int minSubArrayLen(int s, int[] nums) {
int left = 0;
int sum = 0;
int result = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= s) {
result = Math.min(result, right - left + 1);
sum -= nums[left++];
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
(参考:代码随想录)
至于进阶中说尝试设计 O(n log(n))
时间复杂度的解法,想不出来。问了大佬,说是先得到前缀和数组构成非递减数组,然后采用二分查找。暂时想不出来,先放放,哈哈。
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
示例 1:
输入:n = 3 输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:
输入:n = 1 输出:[[1]]
提示:
1 <= n <= 20
这道题就有点略折磨了,边界问题搞得欲仙欲死,需要勤debug才能发现问题在哪:
class Solution {
public int[][] generateMatrix(int n) {
int[][] res = new int[n][n];
int left = 0;
int right = n - 1;
int up = 0;
int down = n - 1;
int digit = 1;
int i, j;
while (digit <= n * n){
for (i = left; i <= right; i++){
res[up][i] = digit++;
}
for (j = up + 1; j <= down; j++){
res[j][right] = digit++;
}
for (i = right - 1; i >= left; i--){
res[down][i] = digit++;
}
for (j = down - 1; j > up; j--){
res[j][left] = digit++;
}
left++;
right--;
up++;
down--;
}
return res;
}
}
思路就是设置四条边的边界。把数字顺序看成蚂蚁在矩阵格子上爬行的路径,那么把蚂蚁正在爬行的四条边看成left,right,up,down。当蚂蚁在up、down边上爬行的时候,索引i会变化。当蚂蚁在left、right边上爬行时,索引j变化。再具体考虑每条边的边界,避免撞格子和漏格子。首先在up边爬行时,边界是left和right。当蚂蚁爬完up边,改在right边爬行时,由于 (up,right) 这个格子已经被爬过了,起点应该从up+1开始,到down结束。在down边爬行时, (down,right) 这个格子被爬过了,从right-1开始,到left结束。最后爬行的是left边,注意,不仅(down,left)格子被爬过,(up,left)这个格子也在最开始被蚂蚁爬过,所以应从down - 1开始,到up+1结束。这样一来,蚂蚁就爬完了最外层一圈,开始爬次外层。次外层边界是left+1,right-1,up+1,down-1,除了边界发生了变化,和在最外层爬行没什么不同。循环!
循环终止条件本来想写left和right重合或相贴,up和down重合或相贴时结束,分成n为奇数和n为偶数两种情况(后面想了想倒也不必,循环条件设置成left<=right&&up<=down即可,n为奇数时会空跑一次循环,结果也没问题)。后面一想,数字变成时结束循环不就完事了?模拟了一下,果然不管n是奇数还是偶数还是1,都能成功结束循环,搞定!