代码随想录二刷 Day03 | 209.长度最小的子数组,904.水果成篮,76.最小覆盖子串,59.螺旋矩阵II,54.螺旋矩阵

题目与题解

209.长度最小的子数组

题目链接:209.长度最小的子数组

代码随想录题解:​​​​​​​209.长度最小的子数组

解题思路

        利用滑动窗口实现,基本原理为:首先设置一个左右边界分别为left和right的滑动窗口,都初始化为0,同时记录窗口元素的和sum,用于比对和是否大于target。

        用右边界逐一遍历数组元素,每遍历到一个新值,讲将其加入sum,如果此时sum大于等于target了,左边界就要开始收缩,逐渐前进。每前进一格,sum就要减去nums[left],相当于滑动窗口去掉了最左边的值,直到sum小于target为止,同时计算滑动窗口的大小,记录minLen。

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int minLen = nums.length+1;
        int left = 0, sum = 0;
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            while (sum >= target) {
                minLen = Math.min(minLen, right - left + 1);
                sum -= nums[left++];
            }
        }
        if (minLen > nums.length) return 0;
        return minLen;
    }
}

注意点

        尽管for循环里面套了while循环,但是left边界最多也就遍历一遍数组元素,right也是遍历一遍数组元素,所以最高复杂度为O(n)。

        另外,为了避免复杂化解题,滑动窗口的右边界最好设置为每次循环都前进一格,直到遇到题目要求的特殊情况,再对左边界进行处理和计算,会清晰很多。

904.水果成篮

题目链接:​​​​​​​904.水果成篮

leetcode题解:​​​​​​​904.水果成篮

解题思路

        这题也属于滑动窗口的题目,比上题复杂一些,需要记录两个左边界,分别对应窗口中两种水果最新一次出现的位置,便于新品种出现时更新。

        用type1和type2分别记录在滑动窗口中的水果种类,方便后序比对,初始化为-1。用pos1和pos2分别记录type1和type2水果最近一次出现的位置,同样初始化为-1。用count记录篮子中水果的数量,并用maxCount记录篮子中最多有多少水果。

        从头开始遍历fruits,如果fruits[i]等于type1或者type2,即水果在当前窗口的两个品种里面,count加一,同时根据fruits[i]的具体值记录最新的pos1或者pos2。

        如果fruits[i]不等于type1或者type2,有两种情况:一种是type1和type2还等于-1,需要用fruits[i]初始化一下;另一种就是新品种的水果出现了,此时需要先记录当前水果数量count是否比maxCount要大,更新maxCount,然后根据fruits[i-1]是type1还是type2,更新pos和type。假设fruits[i-1]是type1,窗口左边界更新为pos2+1,count更新为i - pos2,type2更新为fruits[i],pos2更新为i,如果fruits[i-1]是type2也是同理。

        最后返回maxCount即可。

class Solution {
    public int totalFruit(int[] fruits) {
        if (fruits.length <= 2) return fruits.length;
        int count = 0, maxCount = 0;
        int type1 = -1, type2 = -1;
        int pos1 = -1, pos2 = -1;
        int i = 0;
        for (int j = 0; j < fruits.length; j++) {
            if (fruits[j] == type1 || fruits[j] == type2) {
                if (fruits[j] == type1) pos1 = j;
                else pos2 = j;
                count++;
            } else {
                if (type1 == -1) {
                    type1 = fruits[j];
                    count++;
                    pos1 = j;
                } else if (type2 == -1) {
                    type2 = fruits[j];
                    count++;
                    pos2 = j;
                } else {
                    if (fruits[j-1] == type1) {
                        count = j - pos2;
                        pos2 = j;
                        type2 = fruits[j];
                    } else {
                        count = j - pos1;
                        pos1 = j;
                        type1 = fruits[j];
                    }
                }
            }
            maxCount = Math.max(maxCount, count);
        }
        return maxCount;
    }
}

注意点

        这题多了一个左边界,难写了很多。一开始写的时候考虑不周,把type1或type2等于-1的情况写在了前面,导致碰到[0,0,1,1]这个测试用例就没有过。答案有用哈希表的算法,没看懂,就这样吧。

76.最小覆盖子串

题目链接:​​​​​​​76.最小覆盖子串

代码随想录题解:​​​​​​​76.最小覆盖子串

解题思路

        这题够复杂,就算边看答案边抄也是看了好几遍才看懂,涉及到的步骤太多了,即使有想法也要想到如何用合适的数据结构去实现,抄一下官方答案的思路仅供学习,代码是选了一版能看懂的抄下来了。

        可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 right指针,和一个用于「收缩」窗口的 left 指针。在任意时刻,只有一个指针运动,而另一个保持静止。我们在 s 上滑动窗口,通过移动 right 指针不断扩张窗口。当窗口包含 t 全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。

        如何判断当前的窗口包含所有 t 所需的字符呢?我们可以用一个哈希表tMap表示 t 中所有的字符以及它们的个数,用一个哈希表sMap动态维护窗口中所有的字符以及它们的个数,如果这个动态表中包含 t 的哈希表中的所有字符,并且对应的个数都不小于 t 的哈希表中各个字符的个数,那么当前的窗口是「可行」的。

        具体实现的时候,除了要维护窗口左右边界的值,还需要用minLen记录最终的最小覆盖长度,用valid记录当前窗口的字符数量是否符合t的数量要求,用start记录获得最小窗口时其左边界的大小,方便最后返回s的子字符串。

class Solution {
    public String minWindow(String s, String t) {
        if (t.length() > s.length()) return "";
        Map<Character, Integer> tMap = new HashMap<>();
        for (int i = 0; i < t.length(); i++) {
            char c = t.charAt(i);
            tMap.put(c, tMap.getOrDefault(c, 0) + 1);
        }
        int left = 0, right = 0;
        int minLen = s.length()+1;
        int start = 0;
        int valid = 0;
        Map<Character, Integer> sMap = new HashMap<>();
        while (right < s.length()) {
            char c = s.charAt(right);
            if (tMap.containsKey(c)) {
                sMap.put(c, sMap.getOrDefault(c, 0) + 1);
                if (sMap.get(c).equals(tMap.get(c))) valid++;
            }
            while (valid == tMap.size()) {
                if (right - left + 1 < minLen) {
                    start = left;
                    minLen = right - left + 1;
                }
                char l = s.charAt(left);
                if (tMap.containsKey(l)) {
                    sMap.put(l, sMap.get(l)-1);
                    if (sMap.get(l) < tMap.get(l)) valid--;
                }
                left++;
            }
            right++;
        }
        return minLen == s.length()+1 ? "": s.substring(start, start + minLen);
    }
}

注意点

        hard题,要是考到就算了。

59.螺旋矩阵II

题目链接:​​​​​​​59.螺旋矩阵II

代码随想录题解:​​​​​​​59.螺旋矩阵II

解题思路

        因为做过一次,题目印象还是很深刻的。这里主要得想到每次如何去遍历当前圈的四个方向的值,以及跳出循环的时机。

        事实上,因为要求的螺旋矩阵是正方形的,所以其四条边都是一样长,也就是说每条边每次可以按规律遍历相同的长度,写起来会容易很多。主要需要注意每次遍历的边界,下标顺序要写对,以及当n为奇数时需要额外计算一下中心的数值。

class Solution {
	public int[][] generateMatrix(int n) {
		int[][] matrix = new int[n][n];
		if (n % 2 == 1) matrix[n/2][n/2] = n*n;
		int i = 1;
		for (int round = 0; round < n/2; round++) {
			for(int j1 = round; j1 < n - round - 1; j1++) {
				matrix[round][j1] = i++;
			}
			for(int j2 = round; j2 < n - round - 1; j2++) {
				matrix[j2][n - round - 1] = i++;
			}
			for(int j3 = n - round - 1; j3 > round; j3--) {
				matrix[n - round - 1][j3] = i++;
			}
			for(int j4 = n - round - 1; j4 > round; j4--) {
				matrix[j4][round] = i++;
			}
		}
		return matrix;
	}
}

注意点

        边界不要多写,下标要自己试一试,很容易写错行列位置。

54.螺旋矩阵

题目链接:54.螺旋矩阵

代码随想录题解:​​​​​​​54.螺旋矩阵

解题思路

        被上一题绕进去了,本来想通过一样的写法完成,但是发现不好把控循环停止的时机。所以最简单的方法还是按照官方的题解,设置上下左右四个边界,每次遍历每条边界所在边的同时更新边界的值,就绝对不会出错了。

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> list = new ArrayList<>();
        if (matrix.length == 0) return list;
        int up = 0, down = matrix.length - 1;
        int left = 0, right = matrix[0].length - 1;
        while (true) {
            for (int i = left; i <= right; i++) list.add(matrix[up][i]);
            if (++up > down) break;
            for (int i = up; i <= down; i++) list.add(matrix[i][right]);
            if (--right < left) break;
            for (int i = right; i >= left; i--) list.add(matrix[down][i]);
            if (--down < up) break;
            for (int i = down; i >= up; i--) list.add(matrix[i][left]);
            if (++left > right) break;
        }
        return list;
    }
}

注意点

        模拟过程就行。

今日收获

        复习了一下滑动窗口法和螺旋矩阵的题目。

        滑动窗口注意点:右边界不断扩展,左边界根据需求收缩,收缩的同时计算并记录所需窗口的大小、起始位置等参数。

        螺旋矩阵注意点:根据边界的限制和矩阵的长宽规律,模拟并按顺序打印或赋值即可,要注意i下标和边界的位置需要符合要求。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值