【Leetcode】-代码随想录算法训练营Day2 | 977.有序数组的平方,209.长度最小的子数组,59.螺旋矩阵II

双指针

Leetcode题目-977.Squares of a Sorted Array

链接: 977. Squares of a Sorted Array

特殊条件

  • non-decreasing order
  • 结果取the squares of each number,而且负数的平方>0

思路

根据给的example可以明显看出,当数组中有负数的时候,经过平方处理,其原有的“非递减排列”会得到改变,从而需要重新排序。如果通过先平方再排序的思路解题,其时间复杂度最小也为O(n+nlog n)
寻求最优解,进行进一步分析。因为绝对值越大的数,平方越大,且此数组条件为非递减,所以数组平方后的最大值,只可能存在于最左侧或最右侧。可以定义两个指针,分别指向数组的最左侧和最右侧,通过比较两个指针指向方向的值的平方,就可以得出数组平方后的最大值。取出最大值后,向中间移动指针,即获得新的子数组,重复上述做法即可一步一步得出每个子数组的最大值,直到原数组取完,得到原数组元素的非递增序列。
但因为题目要求输出的是非递减序列,且我们已知数组长度,即按照从后往前的顺序填入上述获得的元素即可。

代码实现

class Solution {
        public int[] sortedSquares(int[] nums) {
            // left为左指针,right为右指针,k是新数组下标(从后往前放入)
            int left = 0, right = nums.length - 1, k = nums.length - 1;
            int[] sortedNums = new int[nums.length];
            // 这里是否要取等号? 
            // 需要!因为每次是两个元素中通过比较挑一个出来,不取等号,最后一次比较仍然是两个挑一个,剩下的那个元素就被遗漏了
            while (left <= right) {
                // 这里是否取等号?
                // 都可以,因为相等的情况,不管你取左边的数还是右边的数,值一样,而且被剩下的参与下一轮比较的值也一样
                if (nums[left] * nums[left] >= nums[right] * nums[right]) {
                    sortedNums[k--] = nums[left] * nums[left];
                    left++;
                } else {
                    sortedNums[k--] = nums[right] * nums[right];
                    right--;
                }
            }

            return sortedNums;
        }
    }

总结

时间复杂度:O(n)
空间复杂度:O(1)
通过对原数组和返回数组的分析,确定两个指针的初始位置,移动方向和移动条件。注意两个条件的取等含义。
代码随想录文章讲解:传送门
代码随想录视频讲解:传送门

滑动窗口(双指针变种)

Leetcode题目-209. Minimum Size Subarray Sum

链接: 209. Minimum Size Subarray Sum

思路

此题结合实例理解,可以知道我们最后得到的是输入数组的一个子数组,所以我们需要维护两个指针分别指向子数组的开始位置start和结束位置end。构造出一个滑动的窗口。
如果通过两个循环,遍历所有两个指针可能的所有情况,即一个for循环滑动窗口的起始位置,一个for循环滑动窗口的终止位置,这种暴力解法,时间复杂度为O(n^2)。但是由于我们只需要满足条件的最小长度,有很多无用的子数组可以排除,因而考虑只用一个for循环来解决问题。
那么这个for循环应该应该是循环end(即滑动窗口的终止位置),因为循环start的话,end仍需要通过遍历后续所有元素。

滑动窗口的滑动规则:移动end,计算窗口内元素和,如小于target则继续移动end,否则考虑缩小start以寻求最小值。

且本题需要输出的是所有满足条件里的最小值,所以还需要维护一个结果值,每次获得了满足条件的子数组,通过end-start+1可得到其长度,比较其是否为最小长度,如果是更新结果值,不是则继续寻找直到结束。

代码实现

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
    	// sum记录当前窗口内的元素和
        // len记录滑动过程中的满足条件窗口的最小长度
        // len初始值为MAX_VALUE是为了检查是否有len从未被赋值的情况,即无满足条件的子数组(len初始值也可为其他比nums.length大的数)
        int start = 0, sum = 0, len = Integer.MAX_VALUE;
        for (int end = 0; end < nums.length; end++) {
            sum += nums[end];
            // 如果窗口满足条件,则记录窗口长度
            // 并尝试缩小窗口(移动窗口起始处)看当前是否有更小长度的字数组
            while (sum >= target) {
                len = Math.min(len, end - start + 1);
                sum -= nums[start];
                start++;
            }
        }

        if (len < Integer.MAX_VALUE) return len;
        else return 0;
    }
}

总结

时间复杂度:O(n)(这里每个元素被操作两次,所以是O(2n)O(n)
空间复杂度:O(1)
我感觉滑动窗口可以算是双指针的一个特例,窗口的两端可以看成是前后指针。因为题目求最小,所以滑动窗口减少了一些冗余情况的遍历,降低了时间复杂度。
代码随想录文章讲解:传送门
代码随想录视频讲解:传送门

模拟过程

Leetcode题目-59. Spiral Matrix II

链接: 59. Spiral Matrix II

思路

此题没有太多便利的方法,主要是模拟数据填充的循环过程,主要要理清循环的轮次关系、每次循环的边界问题与循环时偏移量的变化。写之前要想好自己定义的变量都是什么意思,遍历的规则,才能更明确上述关系。
比如,遍历每条边的时候,是否包含边的第一个节点和最后一个节点?最好的处理是包含第一个节点,不包含最后一个节点,这样每条边的处理将一样且不会遗漏这一轮的元素。

代码实现

实现一:

将每次顺时针转一圈视为一次循环,分别画四条边

class Solution {
    public int[][] generateMatrix(int n) {
        int i, j;    // 移动的坐标
        int startX = 0, startY = 0; // 循环的起始点,初识为(0,0)
        int round = 0;  // 循环轮次
        int num = 1;    // 进行填充的数字
        int offset = 1; // 每次循环边界收缩量
        int[][] matrix = new int[n][n];
        while (round < n / 2) {
            // 每条边遍历左开右闭区间,即包括开始节点,不包括终止节点
            // 一开始是向右走,即朝着j++的方向走
            for (j = startY; j < n - offset; j++) {
                matrix[startX][j] = num++;
            }
            // 向下走,即朝着i++的方向走
            for (i = startX; i < n - offset; i++) {
                matrix[i][j] = num++;
            }
            // 向左走,即朝着j--的方向走
            for (; j > startY; j--) {
                matrix[i][j] = num++;
            }
            // 向上走,即朝着i--的方向走
            for (; i > startX; i--) {
                matrix[i][j] = num++;
            }

            // 处理下一圈循环变化
            // 这里startX和startY的变化其实是同步的,所以可以合成成一个变量start
            startX++;
            startY++;
            // 这里offset的变换更新与round其实也是同步的,只不过offset比round大1,也可以考虑合成一个变量
            offset++;
            round++;
        }

        // 如果n为奇数,还要填充最中间的数
        if (n % 2 != 0) matrix[n / 2][n / 2] = num;

        return matrix;
    }
}

实现二:

与实现一相比,更直接地利用四个变量判断四条边的动态变化。思路其实类似。

class Solution {
    public int[][] generateMatrix(int n) {
        int[][] matrix = new int[n][n];
        int num = 1;
        int left = 0, right = n - 1, top = 0, bottom = n - 1;
        while (num <= n * n) {
            for (int i = left; i <= right; i++) matrix[top][i] = num++;
            top++;
            for (int i = top; i <= bottom; i++) matrix[i][right] = num++;
            right--;
            for (int i = right; i >= left; i--) matrix[bottom][i] = num++;
            bottom--;
            for (int i = bottom; i >= top; i--) matrix[i][left] = num++;
            left++;
        }

        return matrix;
    }
}

总结

针对一些写入模式清晰但复杂的题,可以考虑直接模拟过程,但模拟过程中要格外注意各种边界条件。

你可能感兴趣的:(Leetcode-代码随想录,leetcode,算法,矩阵,数据结构,java)