首页 > 试题广场 >

第K小子串(客户端开发卷)

[编程题]第K小子串(客户端开发卷)
  • 热度指数:4829 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
输入一个字符串 s,s 由小写英文字母组成,保证 s 长度小于等于 5000 并且大于等于 1。在 s 的所有不同的子串中,输出字典序第 k 小的字符串。
字符串中任意个连续的字符组成的子序列称为该字符串的子串。
字母序表示英文单词在字典中的先后顺序,即先比较第一个字母,若第一个字母相同,则比较第二个字母的字典序,依次类推,则可比较出该字符串的字典序大小。

数据范围:
进阶:空间复杂度 , 时间复杂度

输入描述:
第一行输出一个字符串 s,保证 s 长度小于等于 5000 大于等于 1。
第二行一个整数 k (1<= k <= 5),保证 s 不同子串个数大于等于 k。


输出描述:
输出一个字符串表示答案。
示例1

输入

aabb
3

输出

aab

说明

不同的子串依次为:
a aa aab aabb ab abb b bb
所以答案为aab
示例2

输入

aaa
3

输出

aaa
继续投机取巧...
s=input().strip()
k=int(input())
A=[]
for i in range(len(s)-k):
    A.append(s[i:i+k])
if k==1:
    A=sorted(A,key=lambda x:(x[0]))
elif k==2:
    A=sorted(A,key=lambda x:(x[0],x[1]))
elif k==3:
    A=sorted(A,key=lambda x:(x[0],x[1],x[2]))
elif k==4:
    A=sorted(A,key=lambda x:(x[0],x[1],x[2],x[3]))
else:
    A=sorted(A,key=lambda x:(x[0],x[1],x[2],x[3],x[4]))
print(A[0])


发表于 2021-06-02 21:08:22 回复(8)
枚举以a,b,c开头的最小字典序序列。
a, aa, .aaa... ab.........ac......
假设aa在字符串中找不到,那么就找下一个ab, 直到az,若az也不在。那么说明以a开头的都不在。
那就只能以b开头。 b, ba,......
若找不到则子串应该删除最后的元素,改为更大的一个元素,
若找到了,则应该追加一个a. 就是下一个字典序更大的子串。
import java.util.*;
import java.io.*;
public class Main{
    public static void main(String[] args) throws IOException{
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        String sss = in.readLine();
        int k = Integer.parseInt(in.readLine());
        StringBuilder par = new StringBuilder("a");
        while(k > 0){
            if(sss.indexOf(par.toString()) != -1){
                k--;
                if(k == 0) break;
                par.append("a");
            }else{
                next(par);
            }
        }
        System.out.println(par.toString());
    }
    public static void next(StringBuilder ss){
        while(ss.charAt(ss.length()-1) == 'z'){
            ss.deleteCharAt(ss.length()-1); 
        }
        char c = ss.charAt(ss.length()-1);
        ss.deleteCharAt(ss.length()-1);
        ss.append((char)(c+1));
    }
}


发表于 2021-07-15 10:19:23 回复(1)
暴力枚举!
本题K比较小,最终答案的字符串的长度一定不超过K,所以暴力枚举所有长度不超过K的子串,边枚举边维护最小的K个字符串即可
#include<bits/stdc++.h>
using namespace std;
const int nn =5100;
const int inff = 0x3fffffff;
const double eps = 1e-8;
typedef long long LL;
const double pi = acos(-1.0);
const LL mod = 1000000007;
string s;
int k;
int main()
{
    cin>>s;
    cin>>k;
    set<string>se;
    int ls=s.size();
    for(int i=0;i<ls;i++)
    {
        string tem;
        for(int j=i;j<ls;j++)
        {
            tem+=s[j];
            if(se.size()>=k)
            {
                if(tem>=*(--se.end()))
                    break;
            }
            se.insert(tem);
            if(se.size()>k)
            {
                se.erase(--se.end());
            }
        }
    }
    cout<<*(--se.end())<<endl;
    return 0;
}


发表于 2021-06-22 11:23:41 回复(1)
首先我们需要一个有序的容器来存放子串,由于需要保证子串各不相同,因此还需要一个集合来对子串进行去重。
由于子串的数量可能非常庞大,我们做一些优化,考虑到字典序第k小的子串长度一定不会超过k,因此在对长度进行遍历时,没有必要将候选的长度尝试到k以上。同时,我们还希望这个有序的容器能够以O(1)的复杂度获取到字典序第k小的子串,这里采用大根堆作为此有序容器,并将堆的大小控制为k,当堆中元素数量小于k时,直接往堆中插入元素;当堆中元素数量达到了k时,为了保证堆中是字典序最小的k个子串,我们仅在待入队子串的字典序小于堆顶子串时将堆顶子串出队,然后将待入队子串插入。
如此一来,遍历完所有的子串后,直接取出堆顶元素即为字典序第k小的子串。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.HashSet;
import java.util.PriorityQueue;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str = br.readLine();
        int k = Integer.parseInt(br.readLine());
        HashSet<String> set = new HashSet<>();
        PriorityQueue<String> queue = new PriorityQueue<>((s1, s2) -> s2.compareTo(s1));
        for(int len = 1; len <= k; len ++){
            for(int i = 0; i < str.length() - len + 1; i++){
                String substr = str.substring(i, i + len);
                if(!set.contains(substr)){
                    if(queue.size() < k){
                        queue.offer(substr);
                    }else{
                        if(substr.compareTo(queue.peek()) < 0){
                            queue.poll();
                            queue.offer(substr);
                        }
                    }
                    set.add(substr);
                }
            }
        }
        System.out.println(queue.peek());
    }
}

发表于 2021-07-22 15:41:04 回复(3)
来一个不一样的K路归并思路的解法。
其实还算比较好想,同一个起点的字串的字典序一定是递增的,所以可以看成是 多个有序的数组合并成一个有序数组,也就是 k 路归并。
例如 输入是 abcd:
那么可以得到:
a -> ab -> abc -> abcd
b -> bc -> bcd
c -> cd
d
然后用这四个有序链表去合并就好了。
用Data代表链表的节点。
#include <bits/stdc++.h>
using namespace std;

struct Data {
    string s;
    int next;
    Data(string s, int next) : s(s), next(next) {}
};
struct Cmp {
    bool operator()(Data &a, Data &b) {
        return a.s > b.s;
    }
};

int main() {
    char str[5005];
    int k;
    scanf("%s", str);
    scanf("%d", &k);
    priority_queue<Data, vector<Data>, Cmp> heap;
    for (int i = 0; i < strlen(str); i++) {
        string tmp = string("") + str[i];
        heap.push(Data(tmp, i + 1));
    }
    int cnt = 0;
    set<string> vis;
    string ans;
    while (cnt < k) {
        Data t = heap.top();
        heap.pop();
        if (!vis.count(t.s)) {
            vis.insert(t.s);
            ans = t.s;
            cnt++;
        }
        if (t.next < strlen(str)) {
            string tmp = t.s + str[t.next];
            heap.push(Data(tmp, t.next + 1));
        }
    }
    printf("%s\n", ans.c_str());

    return 0;
}


发表于 2021-09-16 16:24:01 回复(6)
//将获取的字符串分割成数组
var arr1 = readline().split('') 
//注意不能直接将arr1赋值给arr5,因为直接赋值会导致arr5排序时arr1也会跟着排序
var arr5 =Array.from(arr1)      
var arr2 = []
//获取k
var n = parseInt(readline())
//对arr5按照字典序排序
arr5.sort()
//遍历arr1寻找跟arr5首位一样的字母,找到之后从该字母开始进行循环
//这个次数只要为n+i就可以了,因为题目输入的k的范围(1<= k <= 5)
//不然输出超出空间限制
for(let i = 0;i<arr1.length;i++){
    if(arr1[i]===arr5[0]){
        for(let j =i;j<n+i;j++){
        arr2.push(arr1.slice(i, j + 1))    
    }
   }
}
//将得到的arr2数组进行map操作,每一项都拼接成字符串返回并赋值到arr3
var arr3 =arr2.map(item => item.join(''))
//数组去重
var arr4=[...new Set(arr3)]
//arr4按照字典序排序
arr4.sort()
//输出按照字典序排序后的第k小的字符串
console.log(arr4[n-1])
 js实现的,感觉很多题目都很少用js实现的代码。。这对前端同学也太不友好了吧。。
发表于 2021-08-23 14:09:18 回复(2)
Python暴力枚举
s = input()
k = int(input())
k_str = [0 for i in range(k)]
for i in range(len(s)):
    for j in range(i,min(i+k,len(s))):
        for kk in range(k):
            if k_str[kk]==s[i:j+1]:
                break
            if k_str[kk]==0:
                k_str[kk]=s[i:j+1]
                break
            elif k_str[kk]>s[i:j+1]:
                k_str[kk+1:]=k_str[kk:-1]
                k_str[kk]=s[i:j+1]
                break
print(k_str[-1])


编辑于 2021-08-06 09:40:30 回复(0)
因为字符串长度<=5000,所以可以使用两层循环遍历出所有子串,然后将子串放入set里。利用set排序和去重的特性,若集合大小大于k,后面子串的就不会是第k小子串直接去掉,每次去掉的都是第k小子串的后面元素,最后剩下的最后一个元素就是第k小元素,最后直接输出存下集合里的最后一个元素就是第k小的子串。
这里注意:set.end()存放的不是最后一个元素。
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
//#define mod 998244353
#define mod 1000000007
#define ll long long
using namespace std;
const int N=1e6+5;
const double ex=1e-8;
int n,m,k;
void solve(){
    set<string>st;
    string s;
    cin>>s>>k;
    n=s.size();
    for(int i=0;i<n;i++){
        string ss="";
        for(int j=i;j<n;j++){
            ss+=s[j];
            st.insert(ss);
            if(st.size()>k){
                st.erase(--st.end());
            }
        }
    }
    cout<<*(--st.end())<<'\n';
}
int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    //cout<<fixed<<setprecision(10);
    //int t;
    //cin>>t;
    //while(t--){
        solve();
    //}
    return 0;
}


发表于 2022-01-07 19:42:19 回复(0)
参考了别的答案,在考试的时候使用set容器应该是效率最高的,不知道还有没有其他好的解法
#include<iostream>
#include<set>

using namespace std;

string s;
int k;

int main()
{
    cin >> s;
    cin >> k;
    set<string>min_k;  //用于存储最小的k个子串,存储在set中的元素有序,--min_k.end()是指向容器中字典序最大字符串的指针
    int lenth = s.size();
    for (int i = 0; i < lenth; i++)
    {
        string tem;
        for (int j = i; j < lenth; j++)
        {
            tem += s[j];
            if (min_k.size() == k)
            {
                if (tem >= *(--min_k.end()))
                {
                    break;
                }
            }
            min_k.insert(tem);
            if (min_k.size() > k)
            {
                min_k.erase(--min_k.end());  //因为可能会存在重复的子串,所以在插入tem后清理超出k个的元素
            }
        }
    }
    cout << *(--min_k.end()) << endl;
    return 0;
}


发表于 2021-07-07 18:07:26 回复(1)
显然,SAM可以做到O(n)的空间和O(max(n,k))时间复杂度,非常合理。
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_set>
#include <cstring>
#include <algorithm>
using namespace std;

struct SuffixAutomaton{
	static constexpr int N=1e6+5;
	int fa[N],len[N],endpos[N];
	int ch[N][26];
	vector<int>ed[N];
	int root,last,sz;
	SuffixAutomaton(){
		root=last=sz=1;
	}

	void clear(){
		for(int i=0;i<=sz;i++)
		{
			fa[i]=len[i]=endpos[i]=0;
			memset(ch[i],0,sizeof(ch[i]));
		}
		root=last=sz=1;
	}

	void insert(int c){
	    int p=last,now=++sz;
	    endpos[now]++;
	    len[now]=len[p]+1;
	    for(;p&&!ch[p][c];p=fa[p])ch[p][c]=now;
	    if(!p)fa[now]=root;
	    else
	    {
	        int q=ch[p][c];
	        if(len[q]==len[p]+1)fa[now]=q;
	        else
	        {
	            int nq=++sz;
	            memcpy(ch[nq],ch[q],sizeof(ch[q]));
	            fa[nq]=fa[q];
	            len[nq]=len[p]+1;
	            fa[now]=fa[q]=nq;
	            for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;
	        }
	    }
	    last=now;
	}

	int find(string&s){
		int rt=1;
		for(auto i:s)
		{
			if(ch[rt][i-'a'])rt=ch[rt][i-'a'];
			else return 0;
		}
		return 1;
	}

	void build(){
		for(int i=2;i<=sz;i++)
		{
			ed[fa[i]].push_back(i);
		}
	}

    void get(int x){
        for(auto y:ed[x])
        {
            get(y);
            endpos[x]+=endpos[y];
        }
    }

    string ans;
    int res;
	bool dfs(int x){
		for(int i=0,j;i<26;i++)
        {
            if(j=ch[x][i])
            {
                if(endpos[j])res--;
                if(!res||dfs(j))return ans+='a'+i,true;
            }
        }
        return false;
	}
}sam;

int main() {
    string s; cin>>s;
    int k; cin>>k;
    for(auto i:s)
    {
        sam.insert(i-'a');
    }

    sam.build(),sam.get(1);

    sam.res=k;
    sam.dfs(1);
    reverse(sam.ans.begin(),sam.ans.end());
    cout<<sam.ans<<"\n";
}


发表于 2023-03-19 01:25:16 回复(0)

C++, topK问题

使用set维护前k大,枚举所有可能的子串。
枚举过程中进行剪枝操作,避免不必要的计算。

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s; cin >> s;
    int k; cin >> k;
    set<string> st;
    int n = s.size();
    for (int i = 0; i < n; ++i) {  // 枚举以第i个字符开始的字符串
        for (int j = i; j < n; ++j) {
            string sub = s.substr(i, j - i + 1);
            if (st.size() < k || (sub < *st.rbegin())) {
                st.insert(sub);
                if (st.size() > k) st.erase(--st.end());
            } else {  // 当前的子串已不满足条件,后面的更不满足
                break;
            }
        }
    }
    cout << (*st.rbegin()) << endl;
}
发表于 2023-02-28 10:49:27 回复(0)
treeSet既可以排序也可以去重
import java.io.BufferedInputStream;
import java.util.Comparator;
import java.util.Scanner;
import java.util.TreeSet;

public class Main {
    public static void main(String[] args){
        Scanner in = new Scanner(new BufferedInputStream(System.in));
        String s = in.nextLine();
        int k = in.nextInt();
        TreeSet<String> set = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                for (int i = 0; i < Math.min(o1.length(), o2.length()); i++) {
                    if (o1.charAt(i) != o2.charAt(i)) {
                        return o1.charAt(i) - o2.charAt(i);
                    }
                }
                return o1.length() - o2.length();
            }
        });
        for (int i = 0; i < s.length(); i++) {
            for (int j = i + 1; j <= i + k && j <= s.length(); j++) {
                set.add(s.substring(i, j));
            }
        }
        for (String str : set) {
            k--;
            if (k == 0) {
                System.out.println(str);
                return;
            }
        }
    }



}


发表于 2022-04-24 08:52:57 回复(0)
import java.util.*;


// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别

        String s = in.next();
        int k = in.nextInt();

        String list = "abcdefghijklmnopqrstuvwxyz";

        Set<String> min5 =  new HashSet<>();
        for (int i = 0; i < list.length(); i++) {
            char character = list.charAt(i);
            List<Integer> indexes = new ArrayList<>();
            int index = s.indexOf(character);
            while (index != -1) {
                indexes.add(index);
                index = s.indexOf(character, index + 1);
            }

            //  System.out.print(indexes.toString());


            if (!indexes.isEmpty()) {
                min5.add(String.valueOf(character));
                for (int get_index : indexes) {
                    for (int j = 1; j <= 5; j++) {
                        if (get_index + j <= s.length()) {
                            min5.add(s.substring(get_index, get_index + j));
                        }
                    }
                }
            }


            List<String> list_min5 = new ArrayList<>(min5);
            Collections.sort(list_min5);
            if (list_min5.size() >= k) {
                System.out.println(list_min5.get(k-1));
                return;
            }
        }
    }
}

正常写法,从a-z,开始找子串,比如abcdaef  先从a找找到两个a, a 然后是 ab 和 ae 都加到set里面,然后是abc和aef,这样,set里面大于k个后,转list排序输出
编辑于 2023-09-16 03:35:55 回复(0)
import java.util.*;
public class Main{
    public static void main(String []args){
        Scanner in = new Scanner(System.in);
        String s = in.nextLine();
        int n = Integer.parseInt(in.nextLine());
        TreeSet<String> set = new TreeSet<>();
        char min_char = 'z';//可以不要min_char
        for(int i=0;i<s.length();i++){
            for(int j=i+1;j<=s.length();j++){
                String s1 = s.substring(i,j);
                if(s1.length()<=n){
                    if(s1.charAt(0)<=min_char){//可以不要min_char
                        set.add(s1);
                        min_char = s1.charAt(0);//可以不要min_char
                    }    
                }
                else
                    break;//这步很关键,不然超时
            }
        }
        int i=1; 
        for(String ss : set){//输出第k个
            if(i==n){
                System.out.println(ss);
                return;
            }
            i++;
        }
    }
}


发表于 2023-03-30 17:53:17 回复(0)
s=input()
k=int(input())
res=[]
for i in range(len(s)-k+1):
    res.append(s[i:i+k])
res.sort()
print(res[0])

发表于 2022-09-15 20:31:23 回复(0)

用set进行去重和排序,最后输出第k个string即可

#include <iostream>
#include <string>
#include <set>

using namespace std;


int main() {
    string s;
    int k;
    cin >> s;
    cin >> k;
    set<string> S;
    int n = s.size();
    for (int i = 0; i < n; ++i) {
        string temp;
        for (int j = i; j < min(n, i + k); ++j) {
            temp += s[j];
            S.insert(temp);
        }
    }

    int cnt = 0;
    for (auto it = S.begin(); it != S.end(); ++it) {
        ++cnt;
        if (cnt == k) {
            cout << *it << endl;
            break;
        }
    }


    return 0;
}
发表于 2022-08-09 22:51:29 回复(0)
python解法,对s中的每个字母s[i]记录[s[i]的ascii码,i],先按照 s[i]的ascii码 从小到大排序,如果s[i]的ascii码一样则和按照s[i:len(s)] 从小到大排序。s[i:len(s)]的长度就是以s[i]开头所能构成的子串个数,上述的排序方式就可以把所有的子串按照字典序排出来,接下来一个一个遍历,用字典记录已经遍历过的子串防止重复,当遍历到第k个子串时就找到了答案。
import sys
from collections import defaultdict
s = sys.stdin.readline().strip()
k = int(sys.stdin.readline().strip())
ans = []
for i in range(len(s)):
    ans.append([ord(s[i]) - ord("a"), i])
ans.sort(key=lambda x: (x[0], s[x[1]:len(s)]))
 
def find():
    viewed = defaultdict(bool)
    count = 0
    for j in range(len(ans)):
        for h in range(len(s)-ans[j][1]):
            curr_s = s[ans[j][1]:ans[j][1]+h+1]
            if not viewed[curr_s]:
                viewed[curr_s] = True
                count += 1
                if count == k:
                    return curr_s
print(find())


编辑于 2022-06-15 23:11:37 回复(0)
简单脑壳的方法,我直接遍历全找出来,然后排序,去重,报错:空间超出限制。那么限制我要用的vector大小为k,放满不重复的k个值后,之后如果新值跟之前的不重复而且比当前最大的值小,用此值代替当前最大值,循环下去,就可以了。
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<numeric>
#include<cmath>
using namespace std;
int main() {
    string input_str;
    cin >> input_str;
    int k;
    cin >> k;
    vector<string> sub_str_vec;
    for (int i = 0; i < input_str.size(); i++) {
        for (int j = i; j < input_str.size(); j++) {
            string tmp = input_str.substr(i, j - i + 1);
            if (sub_str_vec.size() <= k) {
                auto it_find = find(sub_str_vec.begin(),sub_str_vec.end(),tmp);
                if(it_find==sub_str_vec.end()) 
                sub_str_vec.push_back(tmp);
            }
            else {
               auto it = max_element(sub_str_vec.begin(), sub_str_vec.end());
                if (tmp < *it){
                auto it_find = find(sub_str_vec.begin(),sub_str_vec.end(),tmp);
                if(it_find==sub_str_vec.end()) 
                    sub_str_vec[distance(sub_str_vec.begin(), it)] = tmp;
                }
           }
        }
    }

    sort(sub_str_vec.begin(), sub_str_vec.end());
    cout << sub_str_vec[k-1]<<endl;
}

发表于 2022-04-26 00:20:49 回复(0)
 
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Set set = new HashSet();
        Scanner in = new Scanner(System.in);
        String str = in.nextLine();
        int k = in.nextInt();
        for (int i = 0; i < str.length(); i++) {
            for (int j = i + 1; j <= str.length(); j++) {
                if (j - i > k){
                    break;
                }
                set.add(str.substring(i, j));
            }
        }
        List<String> list = new ArrayList<>(set);
        Collections.sort(list);
        System.out.println(list.get(k - 1));
    }
}

发表于 2022-03-25 00:01:00 回复(0)
#include <iostream>
#include <queue>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <string>
#include <set>

using namespace std;

string s;
set<string>big_heap;
int main() {
	cin >> s;
	int k;
	cin >> k;
	if (s.size() < 5) {
		for (int i = 1; i <= min(k, (int)s.size()); i++) {
			for (int j = 1; i + j <= s.size(); j++) {
				string temp_str;
				for (int k = j; k < i + j; k++)
					temp_str.push_back(s[k-1]);
				big_heap.insert(temp_str);
			}
		}
		int count = 0;
		for (auto i = big_heap.begin(); i != big_heap.end(); i++) {
			count++;
			if (count == min((int)big_heap.size(), k)) {
				cout << *i << endl;
				break;
			}
			//cout << *i << endl;
		}
	}
	else {
		for (int i = 1; i <= k; i++) {
			for (int j = 1; i + j <= s.size(); j++) {
				string temp_str;
				for (int k = j; k < i + j; k++)
					temp_str.push_back(s[k-1]);
				big_heap.insert(temp_str);
			}
		}
		int count = 0;
		for (auto i = big_heap.begin();i!=big_heap.end();i++) {
			/*count++;
			if (count == min((int)big_heap.size(),k)) {
				cout << *i << endl;
				break;
			}*/
			cout << *i << endl;
		}
	}
}
直接用的set的自动排序的特性,最近刚看完STL印象深刻。。。
发表于 2022-02-07 18:21:50 回复(0)