【问题标题】:Check if binary string can be partitioned such that each partition is a power of 5检查二进制字符串是否可以分区,使得每个分区都是 5 的幂
【发布时间】:2015-11-19 21:02:01
【问题描述】:

我最近遇到了这个问题 - 给定一个二进制字符串,检查我们是否可以将字符串分割/分割成 0..n 个部分,使得每个部分都是 5 的幂。返回最小分割数,如果可以的话。

例如:

input = "101101" - returns 1, as the string can be split once to form "101" and "101",as 101= 5^1.
input = "1111101" - returns 0, as the string itself is 5^3.
input = "100"- returns -1, as it can't be split into power(s) of 5.

我想出了这个递归算法:

  1. 检查字符串本身是否是5的幂,如果是,返回0
  2. 否则,逐个字符地遍历字符串,在每个点检查目前看到的数字是否为 5 的幂。如果是,则将 1 添加到拆分计数并递归检查字符串的其余部分是否为 5 的幂从第 1 步开始。
  3. 返回到目前为止看到的最小分割数。

我在 Java 中实现了上述算法。我相信它可以正常工作,但它是一个简单的递归解决方案。可以使用动态规划来解决这个问题以提高运行时间吗?

代码如下:

public int partition(String inp){
    if(inp==null || inp.length()==0)
        return 0;
    return partition(inp,inp.length(),0);
}
public int partition(String inp,int len,int index){
    if(len==index)
        return 0;
    if(isPowerOfFive(inp,index))
        return 0;
    long sub=0;
    int count = Integer.MAX_VALUE;
    for(int i=index;i<len;++i){
        sub = sub*2 +(inp.charAt(i)-'0');
        if(isPowerOfFive(sub))
            count = Math.min(count,1+partition(inp,len,i+1));
    }
    return count;
}

辅助函数:

public boolean isPowerOfFive(String inp,int index){
    long sub = 0;
    for(int i=index;i<inp.length();++i){
        sub = sub*2 +(inp.charAt(i)-'0');
    }
    return isPowerOfFive(sub);
}

public boolean isPowerOfFive(long val){
    if(val==0)
        return true;
    if(val==1)
        return false;
    while(val>1){
        if(val%5 != 0)
            return false;
        val = val/5;
    }
    return true;
}

【问题讨论】:

  • 输入长度有什么限制?

标签: algorithm recursion dynamic-programming


【解决方案1】:

以下是可以做的简单改进:

  • 在开始前计算 5 的所有幂,以便您更快地进行检查。
  • 如果分割数已经大于您已经完成的最佳分割数,则停止分割输入字符串。

这是我使用这些想法的解决方案:

public static List<String> powers = new ArrayList<String>();
public static int bestSplit = Integer.MAX_VALUE;

public static void main(String[] args) throws Exception {
    // input string (5^5, 5^1, 5^10)
    String inp = "110000110101101100101010000001011111001";
    // calc all powers of 5 that fits in given string
    for (int pow = 1; ; ++pow) {
        String powStr = Long.toBinaryString((long) Math.pow(5, pow));
        if (powStr.length() <= inp.length()) { // can be fit in input string
            powers.add(powStr);
        } else {
            break;
        }
    }
    Collections.reverse(powers); // simple heuristics, sort powers in decreasing order
    // do simple recursive split
    split(inp, 0, -1);
    // print result
    if (bestSplit == Integer.MAX_VALUE) {
        System.out.println(-1);
    } else {
        System.out.println(bestSplit);
    }
}

public static void split(String inp, int start, int depth) {
    if (depth >= bestSplit) {
        return; // can't do better split
    }
    if (start == inp.length()) { // perfect split
        bestSplit = depth;
        return;
    }
    for (String pow : powers) {
        if (inp.startsWith(pow, start)) {
            split(inp, start + pow.length(), depth + 1);
        }
    }
}

编辑:

我还发现了另一种看起来非常快的方法。

  1. 计算字符串表示比input字符串短的5的所有幂。将这些字符串保存在 powers 数组中。
  2. 对于来自powers 数组的每个字符串power:如果powerinput 的子字符串,则将其startend 索引保存到edges 数组(元组数组)中。李>
  3. 现在我们只需要通过edges 数组的边找到从索引0 到索引input.length() 的最短路径。每条边的权重相同,因此使用BFS 可以非常快速地找到最短路径。
  4. 找到的最短路径中的边数正是您所需要的——输入字符串的最小分割数。

【讨论】:

    【解决方案2】:

    您可以检查 5 的幂的二进制表示,而不是计算所有可能的子字符串,以寻找一个共同的模式。使用类似的东西:

    bc <<< "obase=2; for(i = 1; i < 40; i++) 5^i"
    

    你得到:

    51 = 1012
    52 = 110012
    53 = 11111012
    54 = 10011100012
    55 = 1100001101012
    56 = 111101000010012
    57 = 100110001001011012
    58 = 10111110101111000012
    59 = 1110111001101011001012
    510 = 1001010100000010111110012
    511 = 101110100100001110110111012
    512 = 11101000110101001010010100012
    513 = 10010001100001001110011100101012
    514 = 1011010111100110001000001111010012
    515 = 111000110101111110101001001100011012
    516 = 100011100001101111001001101111110000012
    517 = 10110001101000101011110000101110110001012
    518 = 1101111000001011011010110011101001110110012
    ...
    529 = 101000011000111100000111110101110011011010111001000010111110010101012

    如您所见,5 的奇数次幂总是以101 结尾,而 5 的偶数次幂以10+1 模式结尾(其中+ 表示出现一次或多次)。

    您可以将输入字符串放入 trie 中,然后对其进行迭代,识别 10+1 模式,一旦匹配,评估它以检查是否不是误报。

    【讨论】:

      【解决方案3】:

      您只需将给定字符串的值保存在地图中。例如,如果你的字符串结尾是这样的:(每个字母可能是任意大小的字符串)

      ABCD
      

      你发现A mod 5那部分没问题,所以你再试一次BCD,但发现B mod 5也可以,CD以及CD在一起也是一样。现在您应该缓存了以下结果:

      C -> 0
      D -> 0
      CD -> 0
      BCD -> 1  # split B/CD is the best
      

      但是您还没有完成 ABCD - 您发现 AB mod 5 没问题,因此您检查生成的 CD - 它已经在缓存中,您不必从头开始处理它。

      实际上,您只需要缓存来自 partition() 的答案 - 用于实际字符串或 (string, start, length) 元组。哪个更好取决于您有多少重复序列以及比较内容或仅比较索引是否更快。

      【讨论】:

      • 我相信我提供的解决方案是自上而下的,但要缓存结果,我必须采用自下而上的方法对吗?
      • 我认为这无关紧要 - 无论您以何种方式开始,问题都是完全相同的。如果你做自下而上,你可以避免深度循环调用堆栈,这有利于性能。自下而上是经典的动态编程,自上而下更多的是一种机会缓存。两者都应该在此处产生相同的缓存。
      【解决方案4】:

      下面给出的是 C++ 中的一个解决方案。使用动态编程,我正在考虑所有可能的拆分并保存最佳结果。

      #include<bits/stdc++.h>
      using namespace std;
      typedef long long ll;
      int isPowerOfFive(ll n)
      {
          if(n == 0) return 0;
          ll temp = (ll)(log(n)/log(5));
          ll t = round(pow(5,temp));
          if(t == n)
          {
              return 1;
          }
          else
          {
              return 0;
          }
      }
      ll solve(string s) 
      {
          vector<ll> dp(s.length()+1);
          for(int i = 1; i <= s.length(); i++)
          {
              dp[i] = INT_MAX;
              for(int j = 1; j <= i; j++)
              {
                  if( s[j-1] == '0')
                  {
                      continue;
                  }
                  ll num = stoll(s.substr(j-1, i-j+1), nullptr, 2);
                  if(isPowerOfFive(num))
                  {
                      dp[i] = min(dp[i], dp[j-1]+1);
                  }
              }
          }
          if(dp[s.length()] == INT_MAX)
          {
              return -1;
          }
          else
          {
              return dp[s.length()];
          }
      }
      int main()
      {
          string s;
          cin>>s;
          cout<<solve(s);
      }
      

      【讨论】:

        猜你喜欢
        • 2011-11-29
        • 2019-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-02
        • 1970-01-01
        • 2017-04-28
        • 2014-09-02
        相关资源
        最近更新 更多