LeetCode刷题笔记 ~ 1239. 串联字符串的最大长度

313 阅读1分钟

这是我参与更文挑战的第5天,活动详情查看:更文挑战

1239. 串联字符串的最大长度

给定一个字符串数组 arr,字符串 s 是将 arr 某一子序列字符串连接所得的字符串,如果 s 中的每一个字符都只出现过一次,那么它就是一个可行解。

请返回所有可行解 s 中最长长度。

示例:

输入:arr = ["un","iq","ue"]
输出:4
解释:所有可能的串联组合是 "","un","iq","ue","uniq""ique",最大长度为 4。

输入:arr = ["cha","r","act","ers"]
输出:6
解释:可能的解答有 "chaers""acters"。

输入:arr = ["abcdefghijklmnopqrstuvwxyz"]
输出:26

提示:

  • 1 <= arr.length <= 16
  • 1 <= arr[i].length <= 26
  • arr[i] 中只含有小写英文字母

个人思路解析

方式一:普通回溯

题目要求,要求字符串数组中的任意子串连接成一个没有重复字符的最长字符串,那么这里就需要逐个对字符串进行基本,筛选了。想到这,那基本上用回溯没跑的了,话不多说,直接开干!

  • 定义一个字符数组以及保存最大值的变量
  • 遍历每一位字符串,累计求和
  • 到达数组末尾之后,更新最大值
class Solution {
    private int max;
    private int[] ch;
    public int maxLength(List<String> arr) {
        // 初始化
        max = 0;
        ch = new int[26];
        
        // 调用回溯方法
        back(arr, 0, 0);
        
        // 返回结果
        return max;
    }

    /**
     * 回溯
     *
     * @param arr 字符串数组
     * @param index 索引指针
     * @param numCount 当前累计字符数
     */
    public void back(List<String> arr, int index, int numCount){
        // 边界判断,最大值更新
        if(index == arr.size()){
            max = Math.max(max, numCount);
        }

        for(int i = index; i < arr.size(); ++i){
            String s = arr.get(i);
            // 统计当前字符串
            int count = add(s);
            
            // 递归
            back(arr, i + 1, numCount + count);
            
            // 判断当前字符串是否有入桶,如果有,则回溯回前一状态
            if(0 != count){
                remove(s, 0, s.length());
            }
        }
    }

    /**
     * 将字符串中的字符更新到桶中
     * 
     * @param s 字符串
     */
    public int add(String s){
        int count = 0;
        char[] c = s.toCharArray();
        for(int i = 0; i < c.length; ++i){
            // 如果有重复元素,删除当前位置之前存入的元素并结束循环
            if(ch[c[i] - 'a'] != 0){
                remove(s, 0, i);
                count = 0;
                break;
            }
            ++ch[c[i] - 'a'];
            ++count;
        }
        // 返回添加的字符数量(这里测试了直接返回 s.length() 效果比用 count 累加效率低)
        return count;
    }

    /**
     * 移出已入桶的元素
     * @param s 字符串
     * @param start 开始边界
     * @param end 结束边界
     */
    public void remove(String s, int start, int end){
        for(int i = start; i < end; ++i){
            --ch[s.charAt(i) - 'a'];
        }
    }
}

提交结果

image.png

方式二:回溯 + 位运算

官方题解里面说的很清楚,这里直接借用一下,小伙伴们可以自行到官网这边查看

leetcode-cn.com/problems/ma…

class Solution {
    int ans = 0;

    public int maxLength(List<String> arr) {
        List<Integer> masks = new ArrayList<Integer>();
        // 遍历字符串,剔除单个字符串中有重复字符的
        for (String s : arr) {
            int mask = 0;
            for (int i = 0; i < s.length(); ++i) {
                int ch = s.charAt(i) - 'a';
                
                // 若 mask 已有 ch,则说明 s 含有重复字母,无法构成可行解
                if (((mask >> ch) & 1) != 0) {
                    mask = 0;
                    break;
                }
                
                // 将 ch 加入 mask 中
                mask |= 1 << ch; 
            }
            if (mask > 0) {
                masks.add(mask);
            }
        }

        // 回溯
        backtrack(masks, 0, 0);
        
        // 返回结果
        return ans;
    }

    public void backtrack(List<Integer> masks, int pos, int mask) {
        // 边界判断,最大值更新
        if (pos == masks.size()) {
            ans = Math.max(ans, Integer.bitCount(mask));
            return;
        }
        
        // mask 和 masks[pos] 无公共元素
        if ((mask & masks.get(pos)) == 0) { 
            backtrack(masks, pos + 1, mask | masks.get(pos));
        }
        backtrack(masks, pos + 1, mask);
    }
}

复杂度分析

时间复杂度:O(∣Σ∣+2 n)。
空间复杂度:O(n)。

提交结果

image.png

题目来源力扣:leetcode-cn.com/problems/ma…