算法-滑动窗口与前缀和算法

前缀和算法

B站-前缀和算法讲解

剑指 Offer II 012. 左右两边子数组的和相等

剑指 Offer II 012. 左右两边子数组的和相等
给你一个整数数组 nums ,请计算数组的 中心下标 。

数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。

如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。

如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。

https://leetcode.cn/problems/tvdfij/

思路:

假如数组的所有和为 totalnums[i] 的和为 sum,那么右边的就是 total - sum - nums[i],又因为 total - sum - nums[i] = sum,那么 total = 2*sum +nums[i]

 class Solution {
    public int pivotIndex(int[] nums) {
        int total = Arrays.stream(nums).sum();
        int sum = 0;
        for (int i = 0; i < nums.length; ++i) {
            if (2 * sum + nums[i] == total) {
                return i;
            }
            sum += nums[i];
        }
        return -1;
    }
}
  • 时间:O(n)
  • 空间:O(1)

剑指 Offer II 057. 值和下标之差都在给定的范围内

剑指 Offer II 057. 值和下标之差都在给定的范围内
给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。

如果存在则返回 true,不存在返回 false。

https://leetcode.cn/problems/7WqeDu/

思路:

在这里插入图片描述

class Solution {
    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        // 特殊情况
        if (nums.length < 2 || k == 0) return false;

        // 保存长度为 k 的窗口内的数
        TreeSet<Long> treeSet = new TreeSet<>();
        for (int i = 0; i < nums.length; i++) {
            // 由于 cur - floor 可能会出现溢出情况,所以用 Long 来表示
            long cur = nums[i];
            // 获取比 cur 小的数中最接近 cur 的数
            Long floor = treeSet.floor(cur);
            // 获取比 cur 大的数中最接近 cur 的数
            Long ceiling = treeSet.ceiling(cur);

            // 如果窗口中最接近 cur 的数 与 cur 的差 <= t, 说明存在这样的两个数,返回 true
            if (floor != null && cur - floor <= t) return true;
            if (ceiling != null && ceiling - cur <= t) return true;

            // 将当前数添加到窗口中
            treeSet.add(cur);
            // 剔除窗口最左边的元素
            if (i >= k) {
                treeSet.remove(Long.valueOf(nums[i - k]));
            }
        }
        return false;
    }
}
  • 时间:O(n*log(min(n,k)))
  • 空间:O(min(n,k))

剑指 Offer II 009. 乘积小于 K 的子数组

剑指 Offer II 009. 乘积小于 K 的子数组
给定一个正整数数组 nums和整数 k ,请找出该数组内乘积小于 k 的连续的子数组的个数。

https://leetcode-cn.com/problems/ZVAVXX/

class Solution {
    public int numSubarrayProductLessThanK(int[] nums, int k) {
        if (nums == null) {
            return -1;
        }
        int len = nums.length;
        if (len <= 0) {
            return 0;
        }
        int left = 0;
        int res = 0;
        int product = 1;
        //滑动窗口表示含义:滑动窗口表示以当前元素为结尾,且乘积 < k 的子数组
        for (int right = 0; right < len; right++) {
            // 如果当前元素大于 k,那么在其滑动窗口内就不存在子数组满足条件
            if (nums[right] >= k) {
                product = 1;
                left = right + 1;
            } else {
                product *= nums[right];
                while (product >= k) {
                    product /= nums[left];
                    left++;
                }
                // 见:https://leetcode-cn.com/problems/ZVAVXX/solution/c-jie-shi-xia-yuan-shu-mei-you-jie-shi-d-26kx/
                res += right - left + 1;
            }
        }
        return res;
    }
}
  • 时间:O(n)
  • 空间:O(1)

剑指 Offer II 008. 和大于等于 target 的最短子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

https://leetcode-cn.com/problems/2VG8Kg/

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int len = nums.length;
        if (len == 0) {
            return 0;
        }
        int ans = Integer.MAX_VALUE;
        int left = 0, right = 0;
        int sum = 0;
        while (right < len) {
            sum += nums[right];
            while (sum >= target) {
                ans = Math.min(ans, right - left + 1);
                sum -= nums[left];
                left++;
            }
            right++;
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }
}
  • 双指针解法 时间 O(n) 空间O(1)

剑指 Offer II 010. 和为 k 的子数组

https://leetcode-cn.com/problems/QTMn0o/

首先计算数组各个位置的前缀和并保存在一个HashMap 中,然后使用各个位置的 前缀和减去 k,得出的结果如果在 hashMap 中存在,那么就说明存在这样的一个子数组。

在这里插入图片描述

// 1.枚举实现
class Solution {
    public int subarraySum(int[] nums, int k) {
        if (nums == null) {
            return 0;
        }
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            int sum = 0;
            for (int j = i; j < nums.length; j++) {
                sum += nums[j];
                if (sum == k) {
                    res++;
                }
            }
        }
        return res;
    }
}

// 2.前缀和 实现
class Solution02 {
    public int subarraySum(int[] nums, int k) {
        if (nums == null) {
            return 0;
        }
        int res = 0;
        HashMap<Integer, Integer> mp = new HashMap<>();
        mp.put(0, 1);
        int pre = 0;
        for (int i = 0; i < nums.length; i++) {
            // 求真实的前缀和
            pre += nums[i];
            // 
            if (mp.containsKey(pre - k)) {
                res += mp.get(pre - k);
            }
            mp.put(pre, mp.getOrDefault(pre, 0) + 1);
        }
        return res;
    }
}
- 时间:O(n)
- 空间:O(n)

最长重复子数组

https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/
给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。

思路1:DP

在这里插入图片描述

// 动态规划
class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        if (nums1 == null || nums2 == null) {
            return 0;
        }
        int len1 = nums1.length;
        int len2 = nums2.length;
        int[][] dp = new int[len1 + 1][len2 + 1];
        int res = -1;

        for (int i = len1 - 1; i >= 0; i--) {
            for (int j = len2 - 1; j >= 0; j--) {
                if (nums1[i] == nums2[j]) {
                    // 这里的DP方程,我真的是 没理解。。。。
                    dp[i][j] = dp[i + 1][j + 1] + 1;
                } else {
                    dp[i][j] = 0;
                }
                res = Math.max(res, dp[i][j]);
            }
        }
        return res;
    }
}
  • 时间:O(m*n)
  • 空间:O(m*n)

思路2:滑动窗口(具体思路见题解)

class Solution02 {
    public int findLength(int[] A, int[] B) {
        int n = A.length, m = B.length;
        int ret = 0;
        // 数组 B 不动, A 动
        for (int i = 0; i < n; i++) {
            int len = Math.min(m, n - i);
            int maxlen = maxLength(A, B, i, 0, len);
            ret = Math.max(ret, maxlen);
        }
        // 数组 A 不动, B 动
        for (int i = 0; i < m; i++) {
            int len = Math.min(n, m - i);
            int maxlen = maxLength(A, B, 0, i, len);
            ret = Math.max(ret, maxlen);
        }
        return ret;
    }

    // 求长度 len 中最长相等的长度
    public int maxLength(int[] A, int[] B, int addA, int addB, int len) {
        int ret = 0, k = 0;
        for (int i = 0; i < len; i++) {
            if (A[addA + i] == B[addB + i]) {
                k++;
            } else {
                k = 0;
            }
            ret = Math.max(ret, k);
        }
        return ret;
    }
}
  • 时间:O((m+n)*min(m,n))
  • 空间:O(1)

11. 盛最多水的容器

https://leetcode-cn.com/problems/container-with-most-water/

思路:双指针O(n) 解法

初始化双指针分列水槽左右两端,循环每轮将短板向内移动一格,并更新面积最大值,直到两指针相遇时跳出;即可获得最大面积

// 2. O(n) 双指针 解法
class Solution02 {
    public int maxArea(int[] height) {
        if (height == null) {
            return 0;
        }
        int res = 0;
        int left = 0;
        int right = height.length - 1;
        while (left < right) {
            res = Math.max(res, Math.min(height[left], height[right]) * (right - left));
            // 向内移动 较短边
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值