LeetCode每日一题(1520. Maximum Number of Non-Overlapping Substrings)

文章介绍了一种解决方法,针对给定的字符串找出满足不重叠且包含所有相同字符的子串的最大数量。通过动态规划(dp)策略,更新每个字符的覆盖范围,确保包含所有出现的相同字符。同时,文章提到了特殊情况,如可以直接分割的子串。算法会找到具有最小总长度的最优子串组合。
摘要由CSDN通过智能技术生成

Given a string s of lowercase letters, you need to find the maximum number of non-empty substrings of s that meet the following conditions:

The substrings do not overlap, that is for any two substrings s[i…j] and s[x…y], either j < x or i > y is true.
A substring that contains a certain character c must also contain all occurrences of c.
Find the maximum number of substrings that meet the above conditions. If there are multiple solutions with the same number of substrings, return the one with minimum total length. It can be shown that there exists a unique solution of minimum total length.

Notice that you can return the substrings in any order.

Example 1:

Input: s = “adefaddaccc”
Output: [“e”,“f”,“ccc”]

Explanation: The following are all the possible substrings that meet the conditions:

[
	"adefaddaccc"
	"adefadda",
	"ef",
	"e",
	"f",
	"ccc",
]

If we choose the first string, we cannot choose anything else and we’d get only 1. If we choose “adefadda”, we are left with “ccc” which is the only one that doesn’t overlap, thus obtaining 2 substrings. Notice also, that it’s not optimal to choose “ef” since it can be split into two. Therefore, the optimal way is to choose ["e","f","ccc"] which gives us 3 substrings. No other solution of the same number of substrings exist.

Example 2:

Input: s = “abbaccd”
Output: [“d”,“bb”,“cc”]

Explanation: Notice that while the set of substrings ["d","abba","cc"] also has length 3, it’s considered incorrect since it has larger total length.

Constraints:

  • 1 <= s.length <= 105
  • s contains only lowercase English letters.

做到一半其实想放弃了, 没想到最后一搏居然通过了。

我觉得会有比这更简单的解法, 但是既然过了, 那就写下来供大家参考。

一共 26 个字母, 题目里说的是要选就得把所有相同的字母选上, 这样基本可以确定一个 dp 的思路, 对于 a-z 的每个字母, 我们要么选上要么略过。

在给的字符串 s 中, 每个字母都是有其覆盖范围的, 即该字母第一次出现与最后一次出现之间的范围。当我们要选择一个字母的时候,其实最终选择的是其覆盖范围中的字母所组成的字符串。但是这里有一个问题, 就是在其覆盖范围内的那部分字母, 在覆盖范围外不一定不存在, 比如[‘a’, ‘b’, ‘a’, ‘b’], ‘a’覆盖范围是 0-2, 其中包括一个’b’, 当我们选择’a’的时候, 我们实际选择的是"aba", 但是这里有个问题, 因为题目要求要选择某个字母就要把所有出现的相同字母都选上, 在这里,因为有最后那个’b’的存在,我们无法选择"aba", 要是想选择’a’的话我们也必须把所有的’b’都选上, 所以最终我们选择的是"abab"

这样的话, 也就是说我们单依靠字母的第一次出现和最后一次出现是没法确定在选择该字母时所要拿取的字符串范围的。这时我们要做的是更新每个字母的覆盖范围, 让覆盖范围内的字母是该字母在 s 中的全部 occurrences。实际的实现就是不停的循环查找当前字母的覆盖范围内的字母的覆盖范围, 然后根据这些范围来扩大当前字母的覆盖范围, 周而复始,直到该范围不再扩大为止。

除了这种覆盖范围相互嵌套的情况, 还有一种情况就是可以直接分割的情况, 比如[‘a’, ‘a’, ‘b’, ‘b’], 我们就可以从中间将其分为[‘a’, ‘a’], [‘b’, ‘b’], 因为两边的字符集是独立的, 互相没有任何影响, 所以我们只需要两边分别求解,然后将答案合并起来即可。实际实现时我们可以用一个 suffix_sum 来记录到每个位置 i 的已出现的的字母集合, 然后我们正向遍历整个字符串, 如果当前位置之前所出现的所有字母与 suffix_sum[i+1]之间没有交集, 则证明当前位置可以进行分割。



use std::collections::HashMap;

impl Solution {
    fn dp(chars: &Vec<char>, ranges: &Vec<Vec<usize>>, covered: &Vec<i32>, i: usize, mask: i32, curr: Vec<String>, cache: &mut HashMap<(usize, i32), Vec<String>>) -> Vec<String> {
        if i == 26 {
            return curr;
        }
        if let Some(c) = cache.get(&(i, mask)) {
            return c.clone();
        }
        if mask & covered[i] > 0 || ranges[i][0] == 100001 {
            let ans = Solution::dp(chars, ranges, covered, i + 1, mask, curr, cache);
            cache.insert((i, mask), ans.clone());
            return ans;
        }
        let s: String = chars[ranges[i][0]..=ranges[i][1]].into_iter().map(|c| *c).collect();
        let mut next = curr.clone();
        next.push(s);
        let pick = Solution::dp(chars, ranges, covered, i + 1, mask | covered[i], next, cache);
        let pass = Solution::dp(chars, ranges, covered, i + 1, mask, curr, cache);
        if pick.len() == pass.len() {
            let pick_len: usize = pick.iter().map(|s| s.len()).sum();
            let pass_len: usize = pass.iter().map(|s| s.len()).sum();
            if pick_len < pass_len {
                cache.insert((i, mask), pick.clone());
                return pick;
            }
            cache.insert((i, mask), pass.clone());
            return pass;
        }
        if pick.len() < pass.len() {
            cache.insert((i, mask), pass.clone());
            return pass;
        }
        cache.insert((i, mask), pick.clone());
        pick
    }

    fn to_mask(c: char) -> i32 {
        1 << (c as usize - 97)
    }

    fn prepare(chars: Vec<char>) -> Vec<String> {
        let mut ranges = vec![vec![100001, 100001]; 26];
        for (i, &c) in chars.iter().enumerate() {
            let j = c as usize - 97;
            if ranges[j][0] == 100001 {
                ranges[j][0] = i;
            }
            ranges[j][1] = i;
        }
        let mut covered = vec![0; 26];
        for (i, range) in ranges.iter().enumerate() {
            if range[0] == 100001 {
                continue;
            }
            for j in range[0]..=range[1] {
                covered[i] |= Solution::to_mask(chars[j]);
            }
        }
        loop {
            let mut updated = false;
            for i in 0..26 {
                let mut start = ranges[i][0];
                if start == 100001 {
                    continue;
                }
                let mut end = ranges[i][1];
                for j in 0..26 {
                    if covered[i] & (1 << j) > 0 {
                        let s = ranges[j][0];
                        if s == 100001 {
                            continue;
                        }
                        let e = ranges[j][1];
                        start = start.min(s);
                        end = end.max(e);
                    }
                }
                ranges[i][0] = start;
                ranges[i][1] = end;
                for k in start..=end {
                    if covered[i] & Solution::to_mask(chars[k]) == 0 {
                        covered[i] |= Solution::to_mask(chars[k]);
                        updated = true;
                    }
                }
            }
            if !updated {
                break;
            }
        }
        Solution::dp(&chars, &ranges, &covered, 0, 0, Vec::new(), &mut HashMap::new())
    }

    pub fn max_num_of_substrings(s: String) -> Vec<String> {
        let chars: Vec<char> = s.chars().collect();
        let mut suffix = vec![0; chars.len() + 1];
        for i in (0..chars.len()).rev() {
            suffix[i] = suffix[i + 1] | Solution::to_mask(chars[i]);
        }
        let mut queue = Vec::new();
        let mut mask = 0;
        let mut ans = Vec::new();
        for i in 0..chars.len() {
            queue.push(chars[i]);
            mask |= Solution::to_mask(chars[i]);
            if mask & suffix[i + 1] == 0 {
                ans.append(&mut Solution::prepare(queue.clone()));
                queue.clear();
                mask = 0;
            }
        }
        ans
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值