leetcode
leetcode 601 ~ 650
最长重复子数组

最长重复子数组

难度:

标签:

题目描述

代码结果

运行时间: 59 ms, 内存: 16.2 MB


/*
 * 思路:
 * 使用Java Stream API虽然不如使用动态规划来得直接,但可以先构造DP数组,再使用Stream操作来找到最大值。
 */
import java.util.Arrays;
import java.util.stream.IntStream;
 
public class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
            }
        }
        return Arrays.stream(dp).flatMapToInt(IntStream::of).max().orElse(0);
    }
}

解释

方法:

这个题解使用了二分查找和滚动哈希的思路。首先定义了一个 check 函数,用于判断是否存在长度为 length 的公共子数组。check 函数中,分别计算 nums1 和 nums2 的长度为 length 的子数组的哈希值,并使用滚动哈希的方法快速计算出所有可能的子数组哈希值,然后判断是否存在相同的哈希值,如果存在则说明存在长度为 length 的公共子数组。接下来使用二分查找的方法,初始化 left 为 0,right 为 min(len(nums1), len(nums2)),然后进行二分查找,如果 check(mid) 为 True,说明存在长度为 mid 的公共子数组,则将 ans 更新为 mid,并将 left 更新为 mid + 1,否则将 right 更新为 mid - 1。最终返回 ans 即为最长重复子数组的长度。

时间复杂度:

O((n + m) * log(min(n, m)))

空间复杂度:

O(n)

代码细节讲解

🦆
哈希冲突在滚动哈希中是如何被处理的?在本题解中是否有额外的机制来处理可能的哈希碰撞?
在滚动哈希中,哈希冲突是指不同的数据生成了相同的哈希值。通常冲突的处理方式包括使用双哈希或更复杂的数据结构来减少冲突概率。在本题解中,没有特殊机制直接处理哈希冲突。题解依赖单一的哈希函数和大的模数来尽力减少冲突,但如果真的发生冲突,则可能会错误地认为两个不同的子数组相同。因此,虽然使用滚动哈希在大多数情况下效率高且错误率低,但它并不能完全保证不出现误判。
🦆
您使用的 `base` 和 `mod` 值是如何选取的?这些值对哈希的效率和准确性有何影响?
在滚动哈希中,`base` 和 `mod` 的选择对哈希函数的性能和冲突率有重要影响。`base` 通常选择一个与数据规模相关的较大素数,以确保数据被均匀映射,减少哈希碰撞的概率。`mod` 通常也选择一个大素数,这样可以使得哈希值均匀分布在一个大的范围内,进一步减少碰撞。在本题解中,`base` 选择为 131,`mod` 为 10**9 + 9,这是常用的组合,可以在处理大规模数据时保持较好的性能和较低的冲突概率。
🦆
为什么选择二分查找来确定最长的公共子数组长度?直接使用动态规划或其他方法是否可行?
二分查找在这里被用于优化查找最长公共子数组的长度,因为它可以有效地缩小搜索范围,从而减少不必要的计算。动态规划也是解决此类问题的一种常见方法,特别是在空间复杂度可以接受的情况下,它可以提供确切的结果。然而,动态规划的时间和空间复杂度通常是 O(n*m),对于大数据量可能不够高效。因此,如果问题允许一定的误差(如可能的哈希冲突),二分查找配合滚动哈希可以提供更快的解决方案。
🦆
在二分查找过程中,如果`check(mid)`函数返回True,为什么选择将`left`更新为`mid + 1`而不是`mid`?
在二分查找中,当`check(mid)`返回True,意味着至少存在一个长度为`mid`的公共子数组。此时,为了查找是否存在更长的公共子数组,需要将搜索范围向更大的长度调整,即将`left`设置为`mid + 1`。这样做是为了尝试找到可能存在的更长子数组,而不是停留在当前已找到的长度。如果`mid`已经是最长长度,随后的查找将自然无法找到更长的,最终`left`将超过`right`,循环结束,返回之前找到的最大值。

相关问题

长度最小的子数组

给定一个含有 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)) 时间复杂度的解法。