首页 > 试题广场 >

子数组查找

[编程题]子数组查找
  • 热度指数:1263 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M
  • 算法知识视频讲解
给定长度为N的整数数组,以及M个互不相同的整数,请找出包含这M个整数的最短子数组。

例如:给定数组[4 2 1 3],包含数字集{2, 3}的最短子数组是[2 1 3],包含数字集{1, 3}的最短子数组是[1 3]。

输入描述:
第一行一个正整数T(T<=10),表示T个测试样例;
对于每个测试样例,
输入正整数N(N<=100,000),表示数组长度;
接下来输入N个正整数,所有整数都>=0且<=1,000,000,000;
输入正整数M(M<=N),表示M个互不相同的整数;
接下来输入M个整数,表示要查询的整数,已保证互不相同。

有部分测试样例满足N<=1,000。


输出描述:
输出T行,每行一个正整数,表示最短子数组的长度。如果不存在,输出0
示例1

输入

1
4
4 2 1 3
2
2 3

输出

3
双指针求解,初始状态下每个数欠一个(用哈希表记录,每个目标数的value为1),总负债为目标数的个数。首先右移右指针,遇到目标数就把它在负债表中的计数自减,当某个目标数的计数刚好减为0时,总负债自减1,窗口的右边界在扩张的过程中如果继续增加这个数并不减小总的负债。当总负债为0时更新子数组长度,然后收缩左边界使数组的长度尽可能短,如果左边界丢弃的是目标数,就在负债表中增加它的计数,但是只有在这个数的计数刚好达到1时增加总负债,窗口左边界再重复滑出这个数不影响总负债。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.HashMap;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = Integer.parseInt(br.readLine());
        while(T-- > 0){
            int n = Integer.parseInt(br.readLine());
            String[] strArr = br.readLine().split(" ");
            int[] arr = new int[n];
            for(int i = 0; i < n; i++) arr[i] = Integer.parseInt(strArr[i]);
            int m = Integer.parseInt(br.readLine());
            strArr = br.readLine().split(" ");
            HashMap<Integer, Integer> debt = new HashMap<>();
            for(int i = 0; i < m; i++) debt.put(Integer.parseInt(strArr[i]), 1);
            System.out.println(solve(arr, debt));
        }
    }
    
    private static int solve(int[] arr, HashMap<Integer, Integer> debt) {
        int all = debt.size();      // 总负债,刚开始每个数字欠一个
        int left = 0, right = 0;
        int minLen = arr.length + 1;
        while(right < arr.length){
            if(debt.containsKey(arr[right])){      // 只考虑目标数,不是目标数直接右移右指针
                debt.put(arr[right], debt.get(arr[right]) - 1);
                if(debt.get(arr[right]) == 0) all --;    // arr[right]已经不欠了,总负债-1
                while(all == 0) {
                    // 总负债为0,更新一下长度,同时收缩左边界
                    minLen = Math.min(minLen, right - left + 1);
                    if(debt.containsKey(arr[left])){
                        // 如果左端点的数是目标数,就要增加负债
                        debt.put(arr[left], debt.get(arr[left]) + 1);
                        if(debt.get(arr[left]) == 1) all++;     // 重复丢失arr[left]不会增加总负债
                    }
                    left ++;
                }
            }
            right ++;
        }
        // 长度一直没变过说明没有包含目标数的子数组
        return minLen == arr.length + 1? 0: minLen;
    }
}

编辑于 2021-11-22 17:44:18 回复(1)
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
const int max_size=100000;
int min_array(vector<int>& nums,unordered_map<int,int>& sets,int m)
{
	int min_length = max_size+1;
	int i=0, j=0;//初始窗口
	int count = 0;//窗口中已经包含的数字集中元素的个数
	
	while(j<nums.size())//窗口右边往右扩张
	{
		if (sets.find(nums[j])!=sets.end())//如果该数在数字集中
		{
			if(sets[nums[j]]==0)//且该数出现次数为0
				count++;//则包含元素个数++
			sets[nums[j]]++;//该元素出现次数++
            while (count==m)//当包含元素个数=数字集个数时,
            {//说明已经包含了数字集所有元素,开始移动i,缩小窗口
                if (sets.find(nums[i])!=sets.end())//当遇到数字集中的元素时
                {
                    if (j - i + 1 < min_length)//更新最小长度
                        min_length = j - i + 1;
                    if (sets[nums[i]] > 0)//该元素出现次数--
                        sets[nums[i]]--;
                    if(sets[nums[i]]==0)//若某元素出现次数=0;表面已经到达有效窗口的极限位置
                    {
                        count--;//包含元素个数--,表明还差了一个元素。下次再遇到该元素时,又是有效窗口
                        i++;
                        break;
                    }
                }
                i++;
            }
		}
		j++;
	}

	if (min_length == max_size + 1)
		return 0;
	else
		return min_length;
}

int main()
{
	int T;
	cin >> T;
	int n,m;
	int num;
	for (int i = 0; i < T; i++)
	{
        vector<int> nums;
		unordered_map<int, int> sets;//数字集
		cin >> n;
		for (int j = 0; j < n; j++)
		{
			cin >> num;
			nums.push_back(num);
		}
		cin >> m;
		for (int j = 0; j < m; j++)
		{
			cin >> num;
			sets[num] = 0;//初始化数字集中每个数字的出现次数
		}

		cout << min_array(nums,sets,m) << endl;
	}
	return 0;
}


发表于 2020-09-03 17:02:49 回复(0)
求最小长度,应该就想到二分,所以二分长度,check的时候滑动窗口,类似莫队区间不同数的做法。代码较丑
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <cctype>
#include <ctime>
#include <map>
using namespace std;
const int maxn=1e5+5;
int a[maxn],b[maxn],c[maxn],d[maxn];
bool vis[maxn];
int en,m,n;
int getid(int x){
    return lower_bound(b+1,b+en+1,x)-b;
}
bool check(int len){
    int cnt=0;
    for(int i=1;i<=en;i++) d[i]=0;
    for(int i=1;i<=len;i++){
        if(c[a[i]]==1){
            if(d[a[i]]==0) cnt++;            
            d[a[i]]++;
        } 
    }
    if(cnt==m) return true;
    for(int i=len+1;i<=n;i++){
        int p=i-len;
        if(c[a[p]]){
            if(d[a[p]]==1) cnt--;
            d[a[p]]--;
        }
        if(c[a[i]]==1){
            if(d[a[i]]==0) cnt++;            
            d[a[i]]++;
        } 
        if(cnt==m) return true;
    }
    return false;
}

int main(){
    int T;
    ios::sync_with_stdio(false);
    cin>>T;
    while(T--){
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i],c[i]=0;
        sort(b+1,b+n+1);
        en=unique(b+1,b+n+1)-b-1;
        for(int i=1;i<=n;i++) a[i]=getid(a[i]);
        cin>>m;
        bool flag=true;
        for(int i=1;i<=m;i++) {
            int k;
            cin>>k;
            int x=getid(k);
            if(b[x]!=k) flag=false;
            else c[x]=1;
        }
        if(flag){
            int l=m,r=n;
            while(l<=r){
                int mid=(l+r)/2;
                if(check(mid)) r=mid-1;
                else l=mid+1;
            }
            printf("%d\n",l);
        }else{
            puts("0"); 
        }


    }

    return 0;
}

发表于 2020-05-26 17:21:35 回复(0)
import java.util.Scanner;

import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int count=in.nextInt();
            for(int i=0;i<count;i++){
                int src=in.nextInt();
                int[] nums=new int[src];
                for(int j=0;j<src;j++){
                    nums[j]=in.nextInt();
                }
                int target=in.nextInt();
                Map<Integer,List<Integer>> record=new HashMap<>();
                Set<Integer> set=new HashSet<>();
                PriorityQueue<int[]> queue=new PriorityQueue<>((a,b)->a[1]-b[1]);//数组位置升序
                for(int j=0;j<target;j++){
                    record.put(in.nextInt(),new ArrayList<Integer>());
                }
                int right=0;
                //记录目标值的坐标,从小到大
                for(int j=0;j<src;j++){
                    if(record.containsKey(nums[j])){
                        record.get(nums[j]).add(j);
                        int position=record.get(nums[j]).size()-1;
                        if(set.add(nums[j])){
                            right=Math.max(right,j);
                            queue.add(new int[]{nums[j],j,position});

                        }
                    }
                }
                if(queue.size()!=target){
                    System.out.println(0);
                }else{
                    int ans=right-queue.peek()[1]+1;//记录最小子数组长度

                    while(true){
                        int[] cur=queue.peek();
                        List<Integer> curList=record.get(cur[0]);
                        if(cur[2]<curList.size()-1){
                            //说明还存在变化的可能性
                            int future=curList.get(cur[2]+1);
                            if(future>right){
                                right=future;
                            }
                            queue.poll();
                            queue.add(new int[]{cur[0],future,cur[2]+1});
                            ans=Math.min(ans,right-queue.peek()[1]+1);
                        }else{
                            break;
                        }
                    }
                    System.out.println(ans);
                }
            }
        }
    }
}
发表于 2024-08-23 16:27:07 回复(0)
#维护滑动窗,超时,只能60%
from collections import defaultdict
class Solution:
    #判断窗内是否包含所有要求的数字
    def window_contain_target(self, w1, w2):
        res = True
        for k,v in w2.items():
            if k not in w1.keys()&nbs***bsp;w1[k] < v:
                res = False
                break
        return res
    #维护滑动窗
    def minWindow(self, s: str, t: str) -> str:
        left = 0
        target = defaultdict(int)
        window = defaultdict(int)
        window_len = float("inf")
        res = ""
        for char in t:
            target[char] += 1
        #右扩滑动窗
        for right in range(0, len(s)):
            window[s[right]] += 1
            #左缩滑动窗
            while self.window_contain_target(window, target):
                if window_len > (right-left+1):
                    window_len = right-left+1
                    res = s[left:right+1]
                window[s[left]] -= 1
                if window[s[left]] == 0:
                    del window[s[left]]
                left += 1
        return res

T = int(input())
for i in range(T):
    n = int(input())
    nums = [int(i) for i in input().split()]
    m = int(input())
    query = [int(i) for i in input().split()]
    solution = Solution()
    res = solution.minWindow(nums, query)
    print(len(res))

发表于 2022-08-31 15:10:06 回复(0)
#include<bits/stdc++.h>
using namespace std;
int main(){
    int T; cin >> T;
    while(T--) {
        int N; cin >> N; 
        int a[N]; for(int i=0; i<N; ++i) cin >> a[i];
        
        set<int> s;
        int M; cin >> M;
        for(int i=0, val; i<M; ++i) cin >> val, s.insert(val);
        
        priority_queue<int, vector<int>, greater<int>> pq;
        map<int, int> m;
        int res = INT_MAX;
        for(int i=0, cnt = 0; i<N; ++i) {
            if(s.count( a[i] )) {
                pq.push(i);
                m[a[i]]++;
                cnt += m[a[i]]==1;
            }
            if(cnt >= M) {
                while(m[a[pq.top()]] > 1) m[a[pq.top()]]--, pq.pop();
                res = min(res, i - pq.top() + 1);
            }
        }
        cout << (res == INT_MAX ? 0 : res) << endl;
    }
    return 0;
}

发表于 2021-08-31 13:39:19 回复(0)
import sys

def min_len(nums, subnums, lengths):

    sub_dict = {}
    for i in range(len(subnums)):
        sub_dict[subnums[i]] = -1

    min_length = len(nums) + 1
    find_num = 0
    pos = 0
    while pos < len(nums):
        if sub_dict.get(nums[pos], -2) != -2:
            if sub_dict[nums[pos]] == -1:
                find_num += 1
            sub_dict[nums[pos]] = pos

        if find_num == len(subnums):
            max_pos = max(sub_dict.values())
            min_pos = min(sub_dict.values())
            length = max_pos - min_pos + 1
            if length < min_length:
                min_length = length
            sub_dict[nums[min_pos]] = -1
            find_num -= 1
        pos += 1

    min_length = min_length if min_length != len(nums) + 1 else 0

    lengths.append(min_length)


total = int(input())
lengths = []
for i in range(total):

    sys.stdin.readline()
    nums = sys.stdin.readline().strip().split()
    nums = list(map(int, nums))
    sys.stdin.readline()
    subnums = sys.stdin.readline().strip().split()
    subnums = list(map(int, subnums))
    min_len(nums, subnums, lengths)
for length in lengths:
    print(length)

编辑于 2020-08-25 15:16:36 回复(0)
## 60%,超时了
def solve(nums, child_nums):
    num_dict = {}
    window = {}
    length = float("inf")
    left, right = 0, 0
    for num in child_nums:
        num_dict[num] = num_dict.get(num, 0) + 1
    target = len(num_dict.keys())
    while right < len(nums):

        if nums[right] in child_nums:
            window[nums[right]] = window.get(nums[right], 0) + 1
            if window[nums[right]] == num_dict[nums[right]]:
                target -= 1
        
        while target == 0:
            length = min(length, right - left + 1)
            if nums[left] in child_nums:
                window[nums[left]] -= 1
            if window[nums[left]] < num_dict[nums[right]]:
                target += 1
            left += 1
        right += 1
    return length if length != float("inf") else 0


N = int(input())
for _ in range(N):
    ## get data
    lens = int(input())
    nums = list(map(int, input().strip().split()))
    short_lens = int(input())
    child_nums = list(map(int, input().strip().split()))
    print(solve(nums, child_nums))

发表于 2020-08-04 00:03:44 回复(0)

思路

  • 使用滑动窗口来匹配子串,分别用startend两个变量记录位置,然后通过字典记录需要的字符串的个数,key为需要的字符,val为个数,额外使用一个计数变量satisfied记录满足匹配条件的字符个数,当其值等于要查找的子串长度时说明找到了满足条件的子串。
    • 开始时一直移动end并检查是否是需要的字符并计数,当数量满足时开始移动左边界start从而缩小范围,直到end遍历到数组的最后。
def find_min_length(M,N,m_nums,need_dict):
    min_length = 100000
    satisfied = 0
    #存储目标字符串中需要的字符的个数
    src_dict = {}
    start,end = 0,0
    #滑动窗口最后的位置,end到达数组尾
    while end < M:
        # end向右滑动知道找到包含need所有字符的窗口
        if m_nums[end] in need_dict.keys():
            src_dict[m_nums[end]] = src_dict.get(m_nums[end],0)+1
        if src_dict[m_nums[end]] == need_dict[m_nums[end]]:
            satisfied += 1
        # 找到满足条件的子串后求其长度,并移动左边的窗口坐标
        while satisfied == N:
            min_length = min(min_length,end-start+1)
            if m_nums[start] in need_dict.keys():
                src_dict[m_nums[start]] -= 1
                # 重新判断新的窗口是否符合条件,不符合条件的话需要移动右窗口
                if src_dict[m_nums[start]] < need_dict[m_nums[start]]:
                    satisfied -= 1
            start += 1
        end += 1
    return min_length if min_length != 100000 else 0

def main():
    T = int(input())
    for i in range(T):
        M = int(input())
        m_nums = list(map(int,input().split()))
        N = int(input())
        n_nums = list(map(int,input().split()))

        need_dict = {}
        for i in range(N):
            need_dict[n_nums[i]] = need_dict.get(n_nums[i],0)+1
        min_length = find_min_length(M,N,m_nums, need_dict)
        print(min_length)
main()
发表于 2020-07-08 18:46:39 回复(0)

给个python滑动窗口的代码

from collections import defaultdict
class Solution():
    def f(self,n,m,src_arr,tgt_arr):
        needed=defaultdict(int)
        window=defaultdict(int)

        for item in tgt_arr:
            needed[item]+=1
        needed_len=len(needed)
        cur_len=0
        min_len=100000
        left,right=0,0
        while right<n:
            if src_arr[right] in needed:
                pre=window[src_arr[right]]
                window[src_arr[right]]+=1
                if pre<needed[src_arr[right]] and window[src_arr[right]]>=needed[src_arr[right]]:
                    cur_len+=1 # 已经满足一个了
            while cur_len==needed_len:# 所有的key满足了
                min_len=min(min_len,right-left+1)
                left_item=src_arr[left]
                if left_item in needed:
                    window[src_arr[left]]-=1
                    if window[src_arr[left]]<needed[src_arr[left]]:
                        cur_len-=1
                left+=1
            right+=1 # 向右移动一格
        return min_len if min_len!=100000 else 0
sol=Solution()
n=int(input().strip())
for i in range(n):
    n=int(input().strip())
    src=[int(item) for item in input().strip().split()]
    m=int(input().strip())
    tgt=[int(item) for item in input().strip().split()]
    # print(n,src,m,tgt)
    res=sol.f(n,m,src,tgt)
    print(res)
发表于 2020-06-28 21:28:19 回复(0)
我觉得测试用例有问题,我觉得我没错哈哈哈哈哈,错也是超时🤣
def subArraySerach():
    T = int(input())
    ret_res = []
    for i in range(T):
        ret_res.append(0)
    for tt in range(T):
        N = int(input())
        array_o = list(map(int, input().split()))
        M = int(input())
        array_s = list(map(int, input().split()))
        length = []
        if M > N&nbs***bsp;N == 0&nbs***bsp;M == 0:
            print(0)
            break
        for i in range(N - M + 1):
            length.append(0)
        for i in range(N - M + 1):
            j = 0
            head_flag = -1
            tail_flag = -1
            length_flag = i
            while i < N and j < M:
                if array_o[i] == array_s[j]:
                    if j == 0:
                        head_flag = i
                    if j == M - 1:
                        tail_flag = i
                    i = i + 1
                    j = j + 1
                else:
                    i = i + 1
                if head_flag != -1 and tail_flag != -1:
                    length[length_flag] = tail_flag - head_flag + 1
        tmp = sorted(list(set(length)))
        if 0 in length:
            ret_res[tt] = tmp[1]
        else:
            ret_res[tt] = tmp[0]
    for ret in ret_res:
        print(ret)


if __name__ == "__main__":
    subArraySerach()
    pass


发表于 2020-06-18 18:56:31 回复(0)
C++

滑动窗口

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
 
int func(vector<int>& v, vector<int>& u)
{
    unordered_map<int, int> need, window;
    for(int i: u)
        need[i]++;
    int l = 0, r = 0;
    int valid = 0;
    int ans = v.size() + 1;
    while(r < v.size())
    {
        int t = v[r];
        r++;
        if(need.count(t))
        {
             
            if(need.count(t))
            {
                window[t]++;
                if(need[t] == window[t])
                    valid++;
            }
        }
        while(valid == need.size())
        {
            ans = std::min(ans, r - l);
            int t = v[l];
            l++;
            if(need.count(t))
            {
                if(need[t] == window[t])
                    valid--;
                window[t]--;
            }
        }
    }
    return ans == v.size() + 1? 0: ans;
}
 
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        int M, N, t;
        cin>>N;
        vector<int> v;
        while(N--)
        {
            cin>>t;
            v.push_back(t);
        }
        cin>>M;
        vector<int> u;
        while(M--)
        {
            cin>>t;
            u.push_back(t);
        }
         
        cout<<func(v, u)<<endl;
    }
}


发表于 2020-06-18 15:34:09 回复(1)
//题目测试用例有问题,没有参数T,我的代码是遍历暴力查找最短子数组,子数组元素有先后顺序
#include <string>
#include <iostream>
#include <vector>
using namespace std;
int main(void)
{
    int T = 0;
    while (cin >> T)
    {
        int bigArr[100000];

        int smallArr[100000];//vector<int> smallArr(smallArrLen);
        while (T--)
        {
            int bigArrLen = 0;
            int smallArrLen = 0;
            cin >> bigArrLen;

            for (int i = 0; i < bigArrLen; i++)
            {
                cin >> bigArr[i];
            }

            cin >> smallArrLen;
            for (int i = 0; i < smallArrLen; i++)
            {
                cin >> smallArr[i];
            }


            int smallestLen = 0;
            int findflag = 0;
            for (int i = smallArrLen; i <= bigArrLen; i++)
            {
                for (int j = 0; (j + i) <= bigArrLen; j++)
                {
                    int searchIndex = 0;
                    for (int k = j; k <= (j + i - 1); k++)
                    {
                        if (bigArr[k] == smallArr[searchIndex])
                        {
                            searchIndex++;
                        }
                    }
                    if (searchIndex == smallArrLen)
                    {
                        smallestLen = i;
                        findflag = 1;
                        break;
                    }
                }
                if (findflag)
                    break;
            }
            if (findflag)
                cout << smallestLen << endl;
            else
                cout << '0' << endl;


        }
    }
    return 0;

}

发表于 2020-06-15 20:06:03 回复(0)
#
发表于 2020-05-22 15:13:57 回复(0)