977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

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) 的算法解决本问题

 看到题目第一个想法,直接每个元素求平方,然后用无敌的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++];

哈哈!确实比较刁钻,只写一个++,一开始没想到确实脑筋还不够灵活。

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)) 时间复杂度的解法。

 看到连续子数组,基本就可以想到是要用滑动窗口了。简单来说就是设置左右两个指针,分别作为窗口的左右边界。先设置初始窗口,左边界为第一个元素,右边界为第一个满足条件的右边界元素。然后向右移动左指针,直到不满足条件,再向右移动右指针,直到满足条件。不断更新满足条件的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)) 时间复杂度的解法,想不出来。问了大佬,说是先得到前缀和数组构成非递减数组,然后采用二分查找。暂时想不出来,先放放,哈哈。

59. 螺旋矩阵 II

给你一个正整数 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_{}^{2}时结束循环不就完事了?模拟了一下,果然不管n是奇数还是偶数还是1,都能成功结束循环,搞定!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值