从二进制字符串中查找可能的对

brgchamk  于 2021-08-25  发布在  Java
关注(0)|答案(5)|浏览(381)

我有一个字符串数组,值只有0和1。
示例:[“100”、“110”、“010”、“011”、“100”]
现在,我想将每个字符串与所有其他字符串进行比较,看看其中有多少字符串相差1。例如:
“100”与“110”相比,检查每个索引处的元素,如果它们最多相差1个位置,则认为它们相似。因此,对于上面数组中的字符串“100”,类似的元素是“110”和“100”。
我的程序返回一个整数数组,其中每个值表示列表中该索引处类似字符串的数量。
例子:
输入:
["100" , "110", "010", "011", "100"]
输出:[2,3,2,1,2]
说明:在索引1处,输入为“110”,类似的字符串为3,分别为“100”、“010”、“100”。
这是我的密码:

static List<Integer> process(String[] input) {
    List<Integer> list = new ArrayList<>();

    for (int i = 0; i < input.length; i++) {
        int count = 0;
        for (int j = 0; j < input.length; j++) {
            if (i != j) {
                int differ = 0;
                for (int k = 0; k < input[i].length(); k++) {
                    if (input[i].charAt(k) != input[j].charAt(k)) {
                        differ++;
                    }
                    if (differ > 1) {
                        break;
                    }
                }
                if (differ <= 1) {
                    count++;
                }
            }
        }
        list.add(count);
    }
    return list;
}

如何提高此代码的时间复杂度?
所有字符串的长度相同,长度范围为1到20。输入数组的大小最多为10000。

ncecgwcz

ncecgwcz1#

我们可以利用所有输入字符串长度相同的事实,在字符0和1上构建一个简单的二进制基数树,并将匹配字符串的计数存储在叶子中。
然后,您可以使用递归函数为每个输入字符串导航树,最多考虑一个发散点,即访问具有相反字符的子级。您可以对以这种方式到达的所有叶节点求和。
首先,我们构建树:

static class Node 
{
    Node[] child = new Node[2];
    int count;
}

static Node buildTree(String[] s)
{
    Node root = new Node();
    for(String bs : s)
    {
        Node node = root;
        for(char c : bs.toCharArray())
        {
            int i = c - '0';
            if(node.child[i] == null) 
                node.child[i] = new Node();
            node = node.child[i];
        }
        node.count++;
    }
    return root;
}

然后我们使用递归来计算匹配的字符串。注意,如果我们到达叶子和 diff 是0,也就是说,我们找到了原始字符串,然后我们需要将计数减少1,因为我们不计算自匹配。

static int countDiff(Node node, String s, int pos, int diff)
{
    if(pos == s.length()) 
        return diff == 0 ? node.count-1 : node.count;

    int i = s.charAt(pos) - '0';

    int sum = 0;
    if(diff == 0 && node.child[(i+1)%2] != null)
        sum += countDiff(node.child[(i+1)%2], s, pos+1, 1);

    if(node.child[i] != null)
        sum += countDiff(node.child[i], s, pos+1, diff);

    return sum;     
}

测试:

String[] s = {"100" , "110", "010", "011", "100"};

Node root = buildTree(s);

int[] count = new int[s.length];
for(int i=0; i<s.length; i++)
    count[i] = countDiff(root, s[i], 0, 0);

System.out.println(Arrays.toString(count));

输出:

[2, 3, 2, 1, 2]
6xfqseft

6xfqseft2#

你可以使用set。
把所有的字符串放在一起 s 对于每个字符串,生成因单个更改而不同的所有字符串
计数属于的已生成字符串数 s 它将具有复杂性 O(n*m*m) 而不是 O(n*n*m) 你的算法已经成功了。 n 是字符串和的数目 m 是字符串的长度。
因此,这是更好的方面 n ,但更糟糕的是 m .
还需要 O(n*m) 记忆。原始算法需要 O(max(log(n), log(m))) 记忆。
为了获得更好的性能,请将字符串转换为整数。

inb24sb2

inb24sb23#

我真的很喜欢拉弗尔的答案,它使用了根树。
但作为一种更快实现的替代方案,您可以使用 int[] 作为频率Map:索引 i 存储数字的多少份副本 iinput .
频率Map数组的大小取决于输入字符串的宽度-因此它取决于 int[220] == int[1'048'576] .

static List<Integer> process(String[] input) {
    var bitWidth = input[0].length();
    var values = new int[input.length];
    var freqMap = new int[1 << bitWidth]; // 2^bitWidth;

    // fill values and frequency map
    for (var i = 0; i < values.length; i++) {
      var value = toInt(input[i]);
      values[i] = value;
      freqMap[value]++;
    }

    var result = new ArrayList<Integer>(values.length);
    // fill results
    for (var value : values) {
      var numClose = freqMap[value] - 1;
      // close value: one bit flipped
      for (var iBit = 0; iBit < bitWidth; iBit++) {
        var closeVal = flipBit(value, iBit);
        numClose += freqMap[closeVal];
      }
      result.add(numClose);
    }
    return result;
  }

  private static int toInt(String inputValue) {
    var result = 0;
    for (int pos = inputValue.length() - 1, posWeight = 1; pos >= 0; pos--, posWeight <<= 1) {
      if (inputValue.charAt(pos) == '1') {
        result += posWeight;
      }
    }
    return result;
  }

  private static int flipBit(int value, int bitPosition) {
    return value ^ (1 << bitPosition);
  }
hmae6n7t

hmae6n7t4#

弗兰肯斯坦的答案来自:
使用 Set (实际上 HashMap 支持重复输入数字),如talex的答案中所示
使用 Integer.valueOf(String, 2) 将输入转换为 int 史莱恩斯大师的答案中的数字
使用位翻转计算闭合值,如用户16394029的答案所示
我们得到的代码是:

static List<Integer> process(String[] input) {
    var values = new ArrayList<Integer>(input.length);
    var freqMap = new HashMap<Integer, Integer>(input.length);
    var bitWidth = input[0].length();

    for (var inputValue : input) {
      var value = Integer.parseInt(inputValue, 2);
      values.add(value);
      freqMap.compute(value, (key, old) -> (old == null) ? 1 : (old + 1));
    }

    for (var i = 0; i < values.size(); i++) {
      var value = values.get(i);
      var numClose = freqMap.get(value) - 1;
      for (var iBit = 0; iBit < bitWidth; iBit++) {
        numClose += freqMap.getOrDefault(flipBit(value, iBit), 0);
      }
      values.set(i, numClose);
    }
    return values;
  }

  private static int flipBit(int value, int bitPosition) {
    return value ^ (1 << bitPosition);
  }
t40tm48m

t40tm48m5#

我可以提出一个建议 O(N^2) 方法使用 bitwise XOR . 如果两位不同,则异或的结果为1。因此,我们可以使用这个属性来找到位不同的位置的计数。

int[] num = new int[input.length];
    for (int i = 0; i < input.length; i++)
        num[i] = Integer.valueOf(input[i], 2); // converting to integral form
    for (int i = 0; i < input.length; i++)
    {
        int count = 0;
        for (int j = 0; j < input.length; j++)
        {
            if (i == j) // skip
                continue;
            if (Integer.bitCount(num[i] ^ num[j]) <= 1) // similar
            {
                ++count;
            }
        }
        list.add(count);
    }

我用过 bitCount 方法 java.lang package 它返回一个数的二元补码二进制表示中的一位数的计数,以查找结果中的设置位 XOR . 结果中的设置位数 XOR 表示两个数字之间的许多位置不同。

相关问题