回溯题目:串联字符串的最大长度

文章目录

题目

标题和出处

标题:串联字符串的最大长度

出处:1239. 串联字符串的最大长度

难度

5 级

题目描述

要求

给定一个字符串数组 arr \texttt{arr} arr,字符串 s \texttt{s} s 是将 arr \texttt{arr} arr 的一个子序列 中的字符串连接 所得的字符各不相同的字符串。

返回 s \texttt{s} s 的最大可能长度。

子序列是通过从原数组中删除某些元素或不删除元素且不改变其余元素的顺序得到的数组。

示例

示例 1:

输入: arr = "un","iq","ue" \texttt{arr = "un","iq","ue"} arr = "un","iq","ue"

输出: 4 \texttt{4} 4

解释:所有的有效串联组合是:

  • "" \texttt{""} ""
  • "un" \texttt{"un"} "un"
  • "iq" \texttt{"iq"} "iq"
  • "ue" \texttt{"ue"} "ue"
  • "uniq" \texttt{"uniq"} "uniq"( "un" + "iq" \texttt{"un"} + \texttt{"iq"} "un"+"iq")
  • "ique" \texttt{"ique"} "ique"( "iq" + "ue" \texttt{"iq"} + \texttt{"ue"} "iq"+"ue")

最大长度为 4 \texttt{4} 4。

示例 2:

输入: arr = "cha","r","act","ers" \texttt{arr = "cha","r","act","ers"} arr = "cha","r","act","ers"

输出: 6 \texttt{6} 6

解释:可能的最长有效串联组合有 "chaers" \texttt{"chaers"} "chaers"( "cha" + "ers" \texttt{"cha"} + \texttt{"ers"} "cha"+"ers")和 "acters" \texttt{"acters"} "acters"( "act" + "ers" \texttt{"act"} + \texttt{"ers"} "act"+"ers")。

示例 3:

输入: arr = "abcdefghijklmnopqrstuvwxyz" \texttt{arr = "abcdefghijklmnopqrstuvwxyz"} arr = "abcdefghijklmnopqrstuvwxyz"

输出: 26 \texttt{26} 26

解释:唯一的字符串包含全部 26 \texttt{26} 26 个字符。

数据范围

  • 1 ≤ arr.length ≤ 16 \texttt{1} \le \texttt{arr.length} \le \texttt{16} 1≤arr.length≤16
  • 1 ≤ arri.length ≤ 26 \texttt{1} \le \texttt{arri.length} \le \texttt{26} 1≤arri.length≤26
  • arri \texttt{arri} arri 中只含有小写英语字母

解法一

思路和算法

字符串 s s s 由数组 arr \textit{arr} arr 中的若干个字符串拼接而成,满足字符串 s s s 中的字符各不相同。如果数组 arr \textit{arr} arr 中的一个字符串包含重复字符,则该字符串一定不能用于拼接成 s s s。当选择的数组 arr \textit{arr} arr 中的字符串都不包含重复字符时,任意两个字符串的字符必须各不相同,才能得到符合要求的字符串 s s s。

由于数组 arr \textit{arr} arr 中的字符串只含有小写英语字母,小写英语字母共有 26 26 26 个,因此可以使用 26 26 26 位二进制数表示字符串包含的字符,二进制数的从低到高的第 i i i 位表示第 i i i 个小写英语字母是否在字符串中,每个 1 1 1 对应一个在字符串中的小写英语字母。由于每个字符串都非空,因此每个字符串对应的二进制数一定大于 0 0 0。为了排除包含重复字符的字符串,当一个字符串包含重复字符时,该字符串对应的二进制数是 0 0 0。

得到数组 arr \textit{arr} arr 中的每个字符串的二进制数之后,使用回溯计算串联字符串的最大长度。用 n n n 表示数组 arr \textit{arr} arr 的长度,由于每个字符串都可以选或不选,因此选定的字符串组合有 2 n 2^n 2n 种。对于每种选定的字符串组合,判断是否可以连接得到字符各不相同的字符串,如果可以连接得到字符各不相同的字符串则用连接得到的字符串长度更新串联字符串的最大长度。

具体做法是,使用 totalMask \textit{totalMask} totalMask 表示选定的字符串连接得到的字符串中每个字符的出现情况,使用 totalLength \textit{totalLength} totalLength 表示选定的字符串连接得到的字符串长度,初始时 totalMask = 0 \textit{totalMask} = 0 totalMask=0, totalLength = 0 \textit{totalLength} = 0 totalLength=0。当遍历到数组 arr \textit{arr} arr 的下标 i i i 时,执行如下操作。

  • 如果 i = n i = n i=n,则得到一个串联字符串,用 totalLength \textit{totalLength} totalLength 更新串联字符串的最大长度。

  • 如果 i < n i < n i<n,则分别考虑不将 arr i \textit{arr}i arri 连接到字符串和将 arr i \textit{arr}i arri 连接到字符串的两种情况。

    1. 不将 arr i \textit{arr}i arri 连接到字符串,继续对下标 i + 1 i + 1 i+1 回溯。

    2. 如果 arr i \textit{arr}i arri 对应的二进制数不是 0 0 0( arr i \textit{arr}i arri 不包含重复字符),且 totalLength \textit{totalLength} totalLength 和 arr i \textit{arr}i arri 对应的二进制数的按位与的结果为 0 0 0( arr i \textit{arr}i arri 中的所有字符都不在已经选择的字符串中),则可以将 arr i \textit{arr}i arri 连接到字符串,用 arr i \textit{arr}i arri 更新 totalMask \textit{totalMask} totalMask 和 totalLength \textit{totalLength} totalLength,然后继续回溯。

回溯结束时,即可得到串联字符串的最大长度。

代码

java 复制代码
class Solution {
    int n;
    List<String> arr;
    int[] masks;
    int maximumLength = 0;

    public int maxLength(List<String> arr) {
        this.n = arr.size();
        this.arr = arr;
        this.masks = new int[n];
        for (int i = 0; i < n; i++) {
            masks[i] = getMask(arr.get(i));
        }
        backtrack(0, 0, 0);
        return maximumLength;
    }

    public int getMask(String str) {
        int mask = 0;
        int length = str.length();
        for (int i = 0; i < length; i++) {
            char c = str.charAt(i);
            int index = c - 'a';
            if ((mask & (1 << index)) != 0) {
                return 0;
            }
            mask |= 1 << index;
        }
        return mask;
    }

    public void backtrack(int index, int totalMask, int totalLength) {
        if (index == n) {
            maximumLength = Math.max(maximumLength, totalLength);
        } else {
            backtrack(index + 1, totalMask, totalLength);
            if (masks[index] != 0 && (totalMask & masks[index]) == 0) {
                backtrack(index + 1, totalMask | masks[index], totalLength + arr.get(index).length());
            }
        }
    }
}

复杂度分析

  • 时间复杂度: O ( L + 2 n ) O(L + 2^n) O(L+2n),其中 L L L 是数组 arr \textit{arr} arr 中的所有字符串的长度之和, n n n 是数组 arr \textit{arr} arr 的长度。需要 O ( L ) O(L) O(L) 的时间计算每个字符串对应的二进制数,选定的字符串组合有 2 n 2^n 2n 种,因此时间复杂度是 O ( L + 2 n ) O(L + 2^n) O(L+2n)。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 arr \textit{arr} arr 的长度。需要创建长度为 n n n 的数组记录每个字符串对应的二进制数,递归调用栈需要 O ( n ) O(n) O(n) 的空间。

解法二

思路和算法

也可以使用迭代的方式遍历选定的字符串组合,计算串联字符串的最大长度。

用 n n n 表示数组 arr \textit{arr} arr 的长度,由于每个字符串都可以选或不选,因此选定的字符串组合有 total = 2 n \textit{total} = 2^n total=2n 种,范围 0 , total − 1 0, \\textit{total} - 1 0,total−1 中的每个整数 selection \textit{selection} selection 都对应一种选定的字符串组合。计算 selection \textit{selection} selection 对应的字符串组合的方法是:将 selection \textit{selection} selection 看成 n n n 位二进制数,该二进制数的从低到高的第 i i i 位表示 arr i \textit{arr}i arri 是否选择,每个 1 1 1 对应一个选择的字符串。

对于每种选定的字符串组合,判断是否可以连接得到字符各不相同的字符串,如果可以连接得到字符各不相同的字符串则用连接得到的字符串长度更新串联字符串的最大长度。

遍历所有选定的字符串组合之后,即可得到串联字符串的最大长度。

代码

java 复制代码
class Solution {
    int n;
    List<String> arr;
    int[] masks;

    public int maxLength(List<String> arr) {
        this.n = arr.size();
        this.arr = arr;
        this.masks = new int[n];
        for (int i = 0; i < n; i++) {
            masks[i] = getMask(arr.get(i));
        }
        int maximumLength = 0;
        int total = 1 << n;
        for (int selection = 0; selection < total; selection++) {
            maximumLength = Math.max(maximumLength, getLength(selection));
        }
        return maximumLength;
    }

    public int getMask(String str) {
        int mask = 0;
        int length = str.length();
        for (int i = 0; i < length; i++) {
            char c = str.charAt(i);
            int index = c - 'a';
            if ((mask & (1 << index)) != 0) {
                return 0;
            }
            mask |= 1 << index;
        }
        return mask;
    }

    public int getLength(int selection) {
        int totalLength = 0;
        int totalMask = 0;
        for (int i = 0; i < n; i++) {
            if ((selection & (1 << i)) != 0) {
                if (masks[i] != 0 && (totalMask & masks[i]) == 0) {
                    totalLength += arr.get(i).length();
                    totalMask |= masks[i];
                } else {
                    return 0;
                }
            }
        }
        return totalLength;
    }
}

复杂度分析

  • 时间复杂度: O ( L + 2 n ) O(L + 2^n) O(L+2n),其中 L L L 是数组 arr \textit{arr} arr 中的所有字符串的长度之和, n n n 是数组 arr \textit{arr} arr 的长度。需要 O ( L ) O(L) O(L) 的时间计算每个字符串对应的二进制数,选定的字符串组合有 2 n 2^n 2n 种,因此时间复杂度是 O ( L + 2 n ) O(L + 2^n) O(L+2n)。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 arr \textit{arr} arr 的长度。需要创建长度为 n n n 的数组记录每个字符串对应的二进制数。