LeetCode刷题Day03——数组(滑动窗口+螺旋矩阵)

简介: 所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。滑动窗口也可以理解为双指针法的一种,只不过这种解法更像是一个窗口的移动。实现滑动窗口,主要确定如下三点:

滑动窗口:

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

滑动窗口也可以理解为双指针法的一种,只不过这种解法更像是一个窗口的移动。

实现滑动窗口,主要确定如下三点:

  • 确定窗口内是什么:一般是确定满足某个条件的最短或最长的区间
  • 确定移动窗口的结束位置:结束位置先向后扩展到某个位置(此时受到某种约束不能继续扩展)确定移动窗口的起始位置:在结束位置确定后,起始位置朝着结束位置收缩(以找寻最优解(求最小区间时)并让结束位置能够继续后移(求最小/最大区间))


一、水果成篮

题目链接:904.水果成篮


/**
 * <pre>
 * <p>滑动窗口,使用两个指针表示左右边界</p>
 * </pre>
 *
 * @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
 * @date 2023/1/4 10:50
 */
public class 水果成篮904 {
    public int totalFruit(int[] fruits) {
        // lst1表示第一个篮子装的水果种类,num1表示装的数量,kind表示已经装了的种类数
        // j指针移动结束时则结束计数并将本轮数量与历史最大数量比较得出最优解
        int lst1 = -1, num1 = 0, lst2 = -1, num2 = 0, kind = 0, ans = 0;
        for (int i=0, j=0; i<fruits.length; i++) {
            // 如果已收集种类数不超过2,则j移动
            while (kind <= 2 && j < fruits.length) {
                if (fruits[j] != lst1 && fruits[j] != lst2) {
                    if (kind == 2) { // 如果已经拿了两种水果,下一棵树是第三种水果则结束循环
                        break;
                    }
                    if (num1 == 0) {
                        lst1 = fruits[j++];
                        num1++;
                    } else {
                        lst2 = fruits[j++];
                        num2++;
                    }
                    kind++;
                } else if (fruits[j] == lst1) {
                    j++;
                    num1++;
                } else {
                    j++;
                    num2++;
                }
                ans = Math.max(j-i, ans);
            }
            // 循环结束时j指向下一个种类
            if (fruits[i] == lst1) {
                num1--;
                if (num1 == 0) { // 第一个盘子已经没装了,这时装的水果种类数减一,意味着j可以继续右移
                    kind--;
                }
            } else if (fruits[i] == lst2) {
                num2--;
                if (num2 == 0) {
                    kind--;
                }
            }
        }
        return ans;
    }
    // 使用Map进行处理,简化代码(逻辑相同)
    // 上法中的lst和num其实就是下面map中的key和value,kind就是map.size()
    // 因为map中封装了很多方法所以写出来的代码更简洁
    public int way2(int[] fruits) {
        Map<Integer, Integer> cnt = new HashMap<>();
        int ans = 0;
        for (int i=0, j=0; i<fruits.length; i++) {
            cnt.put(fruits[i], (cnt.getOrDefault(fruits[i], 0)) + 1);
            while (cnt.size() > 2) {
                cnt.put(fruits[j], cnt.get(fruits[j]) - 1);
                if (cnt.get(fruits[j]) == 0) {
                    cnt.remove(fruits[j]);
                }
                j++;
            }
            ans = Math.max(ans, i-j+1);
        }
        return ans;
    }
}


二、最小覆盖子串

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

/**
 * <pre>
 * <p></p>
 * </pre>
 *
 * @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
 * @date 2023/1/4 13:45
 */
public class 最小覆盖子串76 {
    private Map<Character, Integer> cnt = new HashMap<>();
    private Map<Character, Integer> ori = new HashMap<>();
    public String minWindow(String s, String t) {
        int len1 = s.length();
        int len2 = t.length();
        // 设置需求表,即每个字符需要出现多少次
        for (int i=0; i<len2; i++) {
            ori.put(t.charAt(i), ori.getOrDefault(t.charAt(i), 0) + 1);
        }
        int pos1 = 0, pos2 = 0; // 左、右指针
        int ansL = -1, ansR = -1, len = Integer.MAX_VALUE;
        while (pos2 < len1) {
            // 如果当前字符是t表中的字符,则需要增加cnt哈希表中该字符的数量
            if (ori.containsKey(s.charAt(pos2))) {
                cnt.put(s.charAt(pos2), cnt.getOrDefault(s.charAt(pos2), 0) + 1);
            }
            // 如果当前窗口符合要求(通过判断cnt哈希表中的数量是否满足需求表中规定的数量)
            // 如果满足则尝试收缩左边界以找到最小的满足的情况
            while (check() && pos1 <= pos2) {
                if (pos2 - pos1 + 1 < len) { // 如果新的窗口更小,则更新结果
                    len = pos2 - pos1 + 1;
                    ansL = pos1;
                    ansR = pos2;
                }
                if (ori.containsKey(s.charAt(pos1))) { // 如果值是t中的值则需要将map中的数量减少,以便判断是否还符合需求
                    cnt.put(s.charAt(pos1), cnt.getOrDefault(s.charAt(pos1), 0) - 1);
                }
                pos1++; // 窗口左边界不断收缩
            }
            // 窗口右边界不断扩展
            pos2++;
        }
        return ansL == -1 ? "" : s.substring(ansL, ansR+1);
    }
    public boolean check() {
        for (Map.Entry<Character, Integer> entry : ori.entrySet()) {
            if (cnt.getOrDefault(entry.getKey(), 0) < entry.getValue()) {
                return false;
            }
        }
        return true;
    }
}



三、长度最小的子数组

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


/**
 * <pre>
 * <p>滑动窗口求最小窗口O(n),也可用前缀和+二分O(nlogn)</p>
 * </pre>
 *
 * @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
 * @date 2023/1/4 14:59
 */
public class 长度最小的子数组209 {
    public int minSubArrayLen(int target, int[] nums) {
        int sum = 0, ans = Integer.MAX_VALUE;
        for (int i=0, j=0; j<nums.length; j++) {
            sum += nums[j];
            while (sum >= target && i <= j) {
                ans = Math.min(ans, j - i + 1);
                sum -= nums[i++];
            }
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }
    // 求出前缀和sums,区间[i, j]的和=sums[j]-sums[i]
    // 要使得区间>target,则遍历i,每次使用二分找到最小的j使得sum[i]+target<sum[j]
    public int minSubArrayLen2(int target, int[] nums) {
        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        int ans = Integer.MAX_VALUE;
        int[] sums = new int[n + 1];
        // 为了方便计算,令 size = n + 1 
        // sums[0] = 0 意味着前 0 个元素的前缀和为 0
        // sums[1] = A[0] 前 1 个元素的前缀和为 A[0]
        // 以此类推
        for (int i = 1; i <= n; i++) {
            sums[i] = sums[i - 1] + nums[i - 1];
        }
        for (int i = 1; i <= n; i++) {
            int sum = target + sums[i - 1];
            int bound = Arrays.binarySearch(sums, sum);
            if (bound < 0) {
                bound = -bound - 1;
            }
            if (bound <= n) {
                ans = Math.min(ans, bound - (i - 1));
            }
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }
}


四、螺旋矩阵

题目链接:54.螺旋矩阵


/**
 * <pre>
 * <p>每一个循环处理一圈的数据,之后圈不停向内缩小</p>
 * </pre>
 *
 * @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
 * @date 2023/1/4 15:48
 */
public class 螺旋矩阵54 {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> res = new ArrayList<>();
        int left = 0, right = matrix[0].length - 1, top = 0, bottom = matrix.length - 1;
        while (left <= right && top <= bottom) {
            for (int column=left; column<=right; column++) {
                res.add(matrix[top][column]);
            }
            for (int row=top+1; row<=bottom; row++) {
                res.add(matrix[row][right]);
            }
            if (left<right && top<bottom) { // 避免剩下一行多列或一列多行时额外输出(因为这两种情况在上面已经输出了)
                for (int column=right-1; column>left; column--) {
                    res.add(matrix[bottom][column]);
                }
                for (int row=bottom; row>top; row--) {
                    res.add(matrix[row][left]);
                }
            }
            left++;
            right--;
            top++;
            bottom--;
        }
        return res;
    }
}



五、螺旋矩阵II

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


/**
 * <pre>
 * <p>做法与逻辑矩阵相同</p>
 * </pre>
 *
 * @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
 * @date 2023/1/4 16:22
 */
public class 螺旋矩阵II59 {
    public int[][] generateMatrix(int n) {
        int left = 0, right = n-1, top = 0, bottom = n-1, value = 1;
        int[][] res = new int[n][n];
        while (left <= right && top <= bottom) {
            for (int col=left; col<=right; col++) {
                res[top][col] = value++;
            }
            for (int row=top+1; row<=bottom; row++) {
                res[row][right] = value++;
            }
            if (left<=right && top<=bottom) {
                for (int col=right-1; col>=left; col--) {
                    res[bottom][col] = value++;
                }
                for (int row=bottom-1; row>top; row--) {
                    res[row][left] = value++;
                }
            }
            left++;
            right--;
            top++;
            bottom--;
        }
        return res;
    }
}


相关文章
|
1月前
【LeetCode 26】239.滑动窗口最大值
【LeetCode 26】239.滑动窗口最大值
32 1
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
1月前
|
C++
Leetcode第54题(螺旋矩阵)
这篇文章介绍了LeetCode第54题“螺旋矩阵”的解题思路和C++的实现代码,该题目要求按照顺时针螺旋顺序返回给定矩阵中的所有元素。
14 1
Leetcode第54题(螺旋矩阵)
|
20天前
|
机器学习/深度学习 人工智能 自然语言处理
280页PDF,全方位评估OpenAI o1,Leetcode刷题准确率竟这么高
【10月更文挑战第24天】近年来,OpenAI的o1模型在大型语言模型(LLMs)中脱颖而出,展现出卓越的推理能力和知识整合能力。基于Transformer架构,o1模型采用了链式思维和强化学习等先进技术,显著提升了其在编程竞赛、医学影像报告生成、数学问题解决、自然语言推理和芯片设计等领域的表现。本文将全面评估o1模型的性能及其对AI研究和应用的潜在影响。
16 1
|
1月前
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
19 4
|
1月前
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
18 0
Leetcode第三十三题(搜索旋转排序数组)
|
1月前
|
算法 C++
Leetcode第53题(最大子数组和)
这篇文章介绍了LeetCode第53题“最大子数组和”的动态规划解法,提供了详细的状态转移方程和C++代码实现,并讨论了其他算法如贪心、分治、改进动态规划和分块累计法。
57 0
|
1月前
|
C++
【LeetCode 12】349.两个数组的交集
【LeetCode 12】349.两个数组的交集
16 0
|
1月前
【LeetCode 05】螺旋矩阵II总结
【LeetCode 05】螺旋矩阵II总结
14 0
|
1月前
【LeetCode 04】滑动窗口法总结
【LeetCode 04】滑动窗口法总结
17 0