IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 百度笔试(2021-9-7)子序列问题 -> 正文阅读

[数据结构与算法]百度笔试(2021-9-7)子序列问题

算法 百度笔试(2021-9-7)子序列问题

@author:Jingdai
@date:2021.09.18

前几天做百度笔试的一个题没有a出来,今天有空,搜了大佬的题解,做了出来,现在总结一下。

题目描述

给你一个长度为 n 且全部由小写字母组成的字符串 s,问你有多少个子序列恰好包含 k 个字母?

输入描述,第一行两个整数 n 和 k,第二行输入一个长度为 n 的仅含小写字母的字符串 s。

输出描述,恰好包含k个字母的子序列的个数,由于个数可能会很大,所以输出对 1000000007 取余的结果。

示例输入1:

6 5
eecbad

示例输出1:

3

解释1:

显然有两个子序列"ecbad"满足要求,同时s自己也满足要求,因此答案为3

示例输入2:

10 2

示例输出2:

aaaccebecd

思路

我想估计很多人和我一样,首先想到的方法就是直接 dfs,对于字符串 s 的每个字母,都采取选和不选两种策略。在 dfs 的时候记录子序列的包含字母的个数,在叶子节点如果包含字母为 k,就使最后的结果加1。同时,当我们在 dfs 中如果我们发现当前包含的字母数已经超过 k 了,那么这个搜索树的子树已经不可能会是答案了,进行剪枝。最后写出的代码如下。


import java.util.*;

public class Main {

    private static int count = 0;

    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        int n = s.nextInt();
        int k = s.nextInt();
        s.nextLine();
        char[] chars = s.nextLine().toCharArray();
        int[] memo = new int[26];

        dfs(chars, 0, n, 0, k, memo);

        System.out.println(count);
    }

    private static void dfs(char[] chars, int index, int n, int curSelect, int k, int[] memo) {
        if (curSelect > k) {
            // prune
            return;
        }

        if (index == n) {
            if (curSelect == k) {
                count++;
                count %= 1000000007;
            }
            return;
        }

        // dont select
        dfs(chars, index+1, n, curSelect, k, memo);

        // select
        int charIndex = chars[index] - 'a';
        memo[charIndex]++;
        if (memo[charIndex] == 1) {
            curSelect++;
        }
        dfs(chars, index+1, n, curSelect, k, memo);
        if (memo[charIndex] == 1) {
            curSelect--;
        }
        memo[charIndex]--;
    }

}

如果这么简单就拿下这个题我也不会再专门写一篇博客了。上面的方法在字符串 s 非常长时,就会出现栈递归的过深的问题,最后不能 ac,下面看一种更好的方法。思路参考牛客的一个大佬,链接在文末。

首先对于字符串 s,我们记录串 s 中每个字母出现的个数,比如 a 有 5 个,那么选择 a 有多少种方案呢?总共就有:

C51 + C52 + C53 + C54 + C55

上面C51代表组合数的意思,这里不好打上下标,大家懂就行。然后:

C51 + C52 + C53 + C54 + C55 = 2^5 - 1

所以如果k个字母中如果选了a,那么就一共有 2^5 - 1 种可能,同理,如果选b、c等等也可以算出对应的方案数,那不就可以将问题转换为在26个字母中选择 k 种,每种的方案数不同,一共的方案数有多少。这样就可以对每种字母进行 dfs,每次考虑选择这种字母和不选,在叶子节点进行判断是否选择了k种字母,这样最多也就会 dfs 26层,大大降低了递归的深度,最后的代码如下。


import java.util.*;

public class Main {

    private static int count = 0;

    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        int n = s.nextInt();
        int k = s.nextInt();
        s.nextLine();
        char[] chars = s.nextLine().toCharArray();
        int[] charCount = new int[26];

        for (char c : chars) {
            charCount[c-'a']++;
        }
        int[] charComCount = new int[26];
        for (int i = 0; i < 26; i++) {
            if (charCount[i] != 0) {
                charComCount[i] = (int) Math.pow(2, charCount[i]) - 1;
            }
        }
        dfs(charComCount, 0, 0, k, 0);
        System.out.println(count);
    }

    private static void dfs(int[] charComCount, int index, int curSelect, int k, int curCount) {
        if (curSelect > k) {
            return;
        }

        while (index < 26 && charComCount[index] == 0) {
            index++;
        }

        if (index == 26) {
            if (curSelect == k) {
                count += curCount;
                count %= 1000000007;
            }
            return;
        }

        // dont select
        dfs(charComCount, index+1, curSelect, k, curCount);

        // select
        if (curCount == 0) {
            dfs(charComCount, index+1, curSelect+1, k, charComCount[index]);
        } else {
            dfs(charComCount, index+1, curSelect+1, k, curCount * charComCount[index]);
        }
    }
}

参考

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-09-19 08:13:59  更:2021-09-19 08:14:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 14:46:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码