面试难题:破碎的计算器

时间:2014-10-20 06:42:51

标签: algorithm

时间限制:2秒/堆栈限制:256MB /内存限制:256MB

问题 戴夫的计算器坏了。当出现超过K个不同的数字时,它会停止。

Dave想要输入一个整数A,但如果他正确输入这个数字,计算器可能会停止。相反,他输入的最接近的整数不会停止计算器。

输出Dave输入和整数A之间的差异。

输入

输入将以标准输入的以下格式给出。

A K
  • 在第一行,您将得到整数A(1≤A≤10^ 15),整数Dave想要输入,后跟一个空格和K(1≤K≤10),数量不同他的计算器可识别的数字。

输出

在一行中输出Dave输入和整数A之间的最小差值。确保在输出的末尾插入换行符。


输入示例1

1234 2

输出示例1

12

在这种情况下,Dave最多只能使用2个不同的数字。他将输入最接近的整数1222,因此差值为12。


输入示例2

7328495 10

输出示例2

0

在这种情况下,戴夫的计算器根本没有破坏。他可以按原样输入给定的整数A.


输入示例3

800000 1

输出示例3

22223

Dave只能使用一个数字,因此777777是最接近的整数。


输入示例4

262004 2

输出示例4

218

最接近的整数是262222。


我通过强力解决方案解决了这个问题......

我尝试了所有可能的情况,其中我在1< = i< A或A<我< = 10 ^ 15。

我将得到两个由K个不同数字组成的解决方案,并找出A和解决方案之间的最小差异。

这是一个简单的解决方案。

当A较大时,执行将超过2秒的阈值。

有没有一种聪明有效的方法来解决它?

6 个答案:

答案 0 :(得分:0)

第一部分是:选择结果中使用的k个数字(10个中可能的数字) 使用位数学(二项式系数)很容易计算数字
对于给定的k,可能有不同的数字组合 有多达252种可供选择的可能性(如果k = 5,则为252,否则不会这么多)。

如果不清楚我在说什么:对于k = 2,有01,02,03,...09,12,13,...,19,23,24,...,89
不,例如。 11因为它只有1但k = 2允许两个不同的数字 没有例如。 90因为它与09,91 = 19等相同 对于k = 5,这些可能组合的计数为252,并且它不会变大。

查找当前k的所有(最多)252种可能性 并在循环中迭代它们不应该太难。

对于每种的可能性,请执行以下操作:
{

在循环中,我们有A,K和当前选择的有效数字
对于解决方案(当然,后者在循环的每次迭代中都不同)
让我们以A = 12899,K = 3,数字123为例 现在搜索一个(从左到右)的第一个数字,这是不允许的 在该示例中,这将是12 8 99,因为允许其左边1和2。

如果有一个,则将此(8)替换为下一个允许的下一个数字(2) 并用尽可能高的数字替换它的所有内容(3也是如此) 12899 =&GT; 12 <强> 3 99 =&GT 123的 33
将结果12333保存在数组中的某处等。
如果没有下一个可能的下一个数字,则什么也不做(没有任何内容去数组)

(带有进位的正确添加会引入更改后的新数字,左 即。无效。将找到具有该新数字的解决方案 当循环达到那个数字时)

然后在另一个方向再次出现同样的事情:
将找到的数字替换为下一个更高的数字 一切都是最低的。这也在阵列中,
当然只有当下一个更高的数字时才会出现 在12899的情况下,8的唯一更高的替代品是9,
但是9是不可能的(只有123)。

}

整个循环结束后(每次迭代都产生了) 最多两个新阵列条目),您有多达502种可能的解决方案。
剩下要做的唯一事情就是检查哪个溶液是最好的(即计算差异) 到每个条目的A并采取最小差异的解决方案

答案 1 :(得分:0)

在每一步,你可以使用你需要的数字,数字+ 1(模10),数字-1(模10),或你以前用过的任何数字。一旦用完数字,您只能使用之前使用过的数字。

这会创建一棵树。在每一步中,取当前状态并计算下一级中所有可能的新状态。

您可以通过删除任何后续步骤来修剪树,这些步骤的增量距离任何后续步骤的最小增量超过一定距离。

这实际上是线性编程问题。

以下是完整的算法!也许有人可以微调决定修剪哪些分支以使其更快。

using System.IO;
using System;
using System.Collections.Generic;
using System.Linq;

public static class Converter
{
    public static int ToInt(this IEnumerable<int> values)
    {
        return values.Aggregate(0, (total, v) => total * 10 + v);
    }

    public static IEnumerable<int> ToList (this int value)
    {
        if (value == 0) return Enumerable.Empty<int>();
        else return value.ToString().Select(x => (int)x - '0').ToList();
    }
}


class Program
{
    static void Main(string[] args)
    {
        //int desired = 262004;
        //int digits = 2;

        int desired = 800000;
        int digits = 1;

        IEnumerable<State> currentStates = new[] { new State(0, 0, desired, digits) };

        foreach (var digit in desired.ToList())
        {
            var nextStates = currentStates.SelectMany(x => x.NextPossibleStates());
            foreach (var state in nextStates.OrderBy(s => s.Delta()).Take(10))
            {
                Console.WriteLine(state.ToString());
            }
            currentStates = nextStates;
            Console.WriteLine("------------");
        }

        Console.ReadKey();
    }

    public class State
    {
        int index;
        int current;
        int desired;
        int digitsRemaining;

        private int desiredToNow()
        {
            return desired.ToList().Take(index).ToInt();
        }

        public int Delta()
        {
            return Math.Abs(desiredToNow() - current);
        }

        public State(int index, int current, int desired, int digitsRemaining)
        {
            this.index = index;
            this.current = current;
            this.desired = desired;
            this.digitsRemaining = digitsRemaining;
        }

        private State Next (int nextDigit, int digitsRemaining)
        {
            return new State(this.index + 1, this.current * 10 + nextDigit, this.desired, digitsRemaining);
        }

        private IEnumerable<int> NextPossibleDigitsWithDuplicates()
        {
            if (this.digitsRemaining > 0)
            {
                int nextDesiredDigit = desired.ToList().Skip(index).First();
                yield return nextDesiredDigit;
                yield return (nextDesiredDigit + 9) % 10;
                yield return (nextDesiredDigit + 1) % 10;
            }
            // Any previously used digit is OK
            foreach (int i in this.current.ToList())
                yield return i;
        }

        public IEnumerable<State> NextPossibleStates()
        {
            var possibles = this.NextPossibleDigitsWithDuplicates().Distinct();
            var possiblesUsingExistingDigits = possibles.Where(p => this.current.ToList().Contains(p));
            var possiblesUsingNewDigits = possibles.Where(p => !this.current.ToList().Contains(p));

            var states = possiblesUsingExistingDigits.Select(p => Next(p, this.digitsRemaining))
                .Concat(possiblesUsingNewDigits.Select(p => Next(p, this.digitsRemaining - 1)))
                .ToList();

            var bestDelta = states.Min(s => s.Delta());
            // Now reject any that can never be better
            // Guessing on the '2' here ...
            var validStates = states.Where(s => s.Delta() < bestDelta + 2);
            return validStates;
        }

        public override string ToString()
        {
            return this.current + " d=" + this.Delta() + " remaining " + this.digitsRemaining;
        }
    }
}

答案 2 :(得分:0)

在考虑了一下这个问题后,我猜一个从左到右分配数字的A *算法是最好的。为此,我们需要估计部分解的误差的下限。一个简单的可能性是:

  • 如果已设置的数字与问题中的数字完全相同,则绑定为0
  • 如果已经设置的数字在问题中较低,则将缺失的数字替换为9以获得下限
  • 如果已经设置的数字在问题中更高,则将缺失的数字替换为0以获得下限

采用示例问题12899 3算法将如下工作:

开始节点

node      best      error
xxxxx     12899         0

扩展最有希望的部分解决方案xxxxx

node      best      error
0xxxx     09999      2900
1xxxx     12899         0
2xxxx     20000      7101
3xxxx     30000     17101
4xxxx     40000     27101
5xxxx     50000     37101
6xxxx     60000     47101
7xxxx     70000     57101
8xxxx     80000     67101
9xxxx     90000     77101

扩展最有希望的部分解决方案1xxxx

node      best      error
0xxxx     09999      2900

10xxx     10999      1900
11xxx     11999       900
12xxx     12899         0
13xxx     13000       101
14xxx     14000      1101
15xxx     15000      2101
16xxx     16000      3101
17xxx     17000      4101
18xxx     18000      5101
19xxx     19000      6101

2xxxx     20000      7101
3xxxx     30000     17101
4xxxx     40000     27101
5xxxx     50000     37101
6xxxx     60000     47101
7xxxx     70000     57101
8xxxx     80000     67101
9xxxx     90000     77101

扩展最有希望的部分解决方案12xxx

node      best      error
0xxxx     09999      2900
10xxx     10999      1900
11xxx     11999       900

120xx     12099       800
121xx     12199       700
122xx     12299       600
123xx     12399       500
124xx     12499       400
125xx     12599       300
126xx     12699       200
127xx     12799       100
128xx     12899         0
129xx     12900         1

13xxx     13000       101
14xxx     14000      1101
15xxx     15000      2101
16xxx     16000      3101
17xxx     17000      4101
18xxx     18000      5101
19xxx     19000      6101
2xxxx     20000      7101
3xxxx     30000     17101
4xxxx     40000     27101
5xxxx     50000     37101
6xxxx     60000     47101
7xxxx     70000     57101
8xxxx     80000     67101
9xxxx     90000     77101

扩展最有希望的部分解决方案128xx(现在只允许数字1,2和8)

node      best      error
0xxxx     09999      2900
10xxx     10999      1900
11xxx     11999       900
120xx     12099       800
121xx     12199       700
122xx     12299       600
123xx     12399       500
124xx     12499       400
125xx     12599       300
126xx     12699       200
127xx     12799       100

1281x     12819        80
1282x     12829        70
1288x     12889        10

129xx     12900         1
13xxx     13000       101
14xxx     14000      1101
15xxx     15000      2101
16xxx     16000      3101
17xxx     17000      4101
18xxx     18000      5101
19xxx     19000      6101
2xxxx     20000      7101
3xxxx     30000     17101
4xxxx     40000     27101
5xxxx     50000     37101
6xxxx     60000     47101
7xxxx     70000     57101
8xxxx     80000     67101
9xxxx     90000     77101

扩展最有希望的部分解决方案129xx(现在只允许数字1,2和9)

node      best      error
0xxxx     09999      2900
10xxx     10999      1900
11xxx     11999       900
120xx     12099       800
121xx     12199       700
122xx     12299       600
123xx     12399       500
124xx     12499       400
125xx     12599       300
126xx     12699       200
127xx     12799       100
1281x     12819        80
1282x     12829        70
1288x     12889        10

1291x     12910        11
1292x     12920        21
1299x     12990        91

13xxx     13000       101
14xxx     14000      1101
15xxx     15000      2101
16xxx     16000      3101
17xxx     17000      4101
18xxx     18000      5101
19xxx     19000      6101
2xxxx     20000      7101
3xxxx     30000     17101
4xxxx     40000     27101
5xxxx     50000     37101
6xxxx     60000     47101
7xxxx     70000     57101
8xxxx     80000     67101
9xxxx     90000     77101

扩展最有希望的部分解决方案1288x(现在只允许数字1,2和8)

node      best      error
0xxxx     09999      2900
10xxx     10999      1900
11xxx     11999       900
120xx     12099       800
121xx     12199       700
122xx     12299       600
123xx     12399       500
124xx     12499       400
125xx     12599       300
126xx     12699       200
127xx     12799       100
1281x     12819        80
1282x     12829        70

12881     12881        18
12882     12882        17
12888     12888        11  <-- optimal solution found

1291x     12910        11
1292x     12920        21
1299x     12990        91
13xxx     13000       101
14xxx     14000      1101
15xxx     15000      2101
16xxx     16000      3101
17xxx     17000      4101
18xxx     18000      5101
19xxx     19000      6101
2xxxx     20000      7101
3xxxx     30000     17101
4xxxx     40000     27101
5xxxx     50000     37101
6xxxx     60000     47101
7xxxx     70000     57101
8xxxx     80000     67101
9xxxx     90000     77101

这仍然不包括最佳解决方案可能比原始数字更多位数的情况(不确定这是否可能),但您明白了......

答案 3 :(得分:0)

对于K = 10,解决方案是微不足道的。差异等于0。


如果原始数字使用小于或等于K不同数字。差值等于0.例如,987777 5使用3个不同的数字,小于K(= 5)。


对于K = 1,

长度为n的数字将被999 ... 999(n位)和999 ... 999((n-1)-digit)约束。这意味着587 is bound by 999 and 99。因此,解决方案必须是n位数长度或999 ... 999((n-1)-digit)。

让数字的前导数字为a(587中为5),表示b=a+1c=a-1。将该数字与aaa...aaabbb...bbbccc...ccc(所有n位长度)进行比较。请注意,这只是检查其中最多2个。

  • 如果original number = aaa...aaa,则差值等于0.
  • 如果original number > aaa...aaa,请继续与bbb...bbb进行比较。
  • 如果original number < aaa...aaa,请继续与ccc...ccc进行比较。

K = 1的规则,如果b=10,则忽略bbb...bbb,因为aaa...aaa = 999...999是上限。如果c=0,请将ccc...ccc更改为999...999((n-1)-digit)以进行下限检查,例如10000 1 -> 9999


对于范围[2,9]中的K,请观察first K-1 distinct digit from left to right must be used。例如,在这种情况下,必须使用9988898988981234 3 2 数字9和8。

<强>证明:

事实:

  1. 原始号码中的不同数字的数量&gt; K
  2. K-1!= 0
  3. 我们总是可以使用K个不同的数字形成两个值z000...000z999...999,其中z由K-1个不同的数字(上例中的z = 998889898898)组成并分配第K个数字为0或9(忽略K-1中可能使用0或9的事实并不重要)

    使用z000...000z999...99999888989889800009988898988989999),最佳解决方案受两个值的约束。如果z变为z + 1或z-1,则差异将增加,这是没有意义的。因此,将始终使用z部分并且first K-1 distinct digit from left to right must be used成立。

    证明结束

    在第一次遇到第K个不同数字(z部分后面的前导数字,表示为t)时,有三种可能需要考虑的案例:tt+1和{{ 1}}。

    让我们先从t-1开始。使用t将耗尽所有K数字,因此不能再分配数字。继续检查右边部分的数字,直到我们遇到一个不在那个K不同数字的数字。这次,我们最多有两个选择。 K中最接近的数字大于遇到数字,K中最接近的数字小于遭遇数字。在K中使用较大的最接近数字将导致数字大于原始数字,因此应通过选择K中的最小数字来最小化剩余数字。类似地,K情况下较小最接近数字的剩余数字将是K中的最大数字。 / p>

    对于案例t,只有在案例t+1的K中没有更大的最接近数字时才需要检查。使用t将始终大于原始数字,我们需要最小化剩余数字。请注意,t+1可能是K-1中的一个数字,我们有一个免费数字,因此请加t+1(如果0不在K-1中)。然后,我们可以选择K中的最小数字(或0时为K-1,t+1中为0)。

    对于案例K-1,只有在案例t-1的K中没有较小的最接近数字时才需要检查。使用t将始终小于原始数字,我们需要最大化剩余数字。请注意,t-1可能是K-1中的一个数字,我们有一个免费数字,因此请加t-1(如果9不在K-1中)。然后,我们可以选择K中的最大数字(如果9为{K-1,t-19),则为剩余数字。

答案 4 :(得分:0)

解决时间为O(len(A))。 找到最小| A - B |,具有不同的K位数。 首先,为了去除abs,我们应该找到具有条件的B1(min(Bi | Bi> = A,具有不同的数字编号),

  1. 设A = a1a2 ... an,B1 = b1b2 ... bn,找到ai = bi的最长序列,直到不同的数量超过K.假设我们有a1-> aj = b1-> BJ

  2. 如果j == n,则B1 = A

  3. 从{a1,a2,...,aj}的集合中,找到大于b [j + 1]的最小数字

  4. 如果找到ak(1 <= k <= j),那么令b [j + 1] = ak,其余的b [j + 1] - > bn用min {a1,a2, ...,AJ}

    如果没有找到,我们将bj从aj更改为[j] +1。对于b [j + 1]的其余部分 - > bn,

    如果[j] +1在{a1,a2,...,aj-1}的集合中,则意味着我们仍然可以按下一个数字,我们选择0作为填充。 b [j + 1] - &gt; bn全部用0填充。

    如果[j] +1不在{a1,a2,...,aj-1}的集合中,我们选择min {a1,a2,...,aj-1,a [j] +1}作为用b [j + 1] - >&gt; bn

    填充的值

    相似性,我们可以找到最接近的数字B2,它小于A,

    之后,我们只是将B1-A与A-B2进行比较,选择最小值。

    遵循C ++代码:

        #include <vector>
    #include <list>
    #include <map>
    #include <set>
    #include <queue>
    #include <deque>
    #include <stack>
    #include <bitset>
    #include <algorithm>
    #include <functional>
    #include <numeric>
    #include <utility>
    #include <iostream>
    #include <iomanip>
    #include <cstdio>
    #include <cmath>
    #include <cstdlib>
    #include <ctime>
    #include <sstream>
    using namespace std;
    
    string target_num;
    string up_num;
    string down_num;
    int K;
    
    bool hash_key[10];
    bool hash_copy[10];
    
    int find_closet_k(int c, bool up) {
     int i = c;
     while(true) {
      if (hash_key[i]) {
       return i;
      }
    
      if(up) {
       ++i;
       if (10 <= i) {
        break;
       }
      } else {
       --i;
       if (i < 0) {
        break;
       }
      }
    
     }
     return -1;
    }
    
    char min_k() {
     for (int i = 0; i < 10; ++i) {
      if (hash_key[i]) {
       return '0' + i;
      }
     }
    }
    
    char max_k() {
     for (int i = 9; 0 <= i; --i) {
      if (hash_key[i]) {
       return '0' + i;
      }
     }
    }
    
    bool is_head_zero(int v, string num,int end_pos) {
     if (0 == v) {
      for (int i = 0; i < end_pos; ++i) {
       if (num[i] != '0') {
        return false;
       }
      }
      return true;
     }
     return false;
    }
    
    int string2int(string s) {
     std::stringstream ss;
     ss << s;
     int is;
     ss >> is;
     return is;
    }
    
    void find_closest() {
     int k = 0;
     int i = 0;
     for (i = 0; i < target_num.size(); ++i) {
      int c = target_num[i] - '0';
      if (!hash_key[c]) {
       if (k == K) {
        break;
       }
       hash_key[c] = true;
       ++k;
      }
      up_num.push_back(target_num[i]);
      down_num.push_back(target_num[i]);
     }
    
     if (i == target_num.size()) {
      printf("0\n");
      return;
     }
    
     copy(hash_key, hash_key + 10, hash_copy);
     int j = i - 1;
     up_num.resize(target_num.size());
     int c = target_num[j + 1] - '0';
     int v = find_closet_k(c, true);
     if (v == -1) {
      int aj = target_num[j] - '0';
      up_num[j] = aj + 1 + '0';
      if (hash_key[aj + 1]) {
       //only used K - 1, use 0 as min_k();
       fill(up_num.begin() + j + 1, up_num.end(), '0');
      } else {
       hash_key[aj] = false;
       hash_key[aj + 1] = true;
       fill(up_num.begin() + j + 1, up_num.end(), min_k());
      }
     } else {
      up_num[j + 1] = v + '0';
      fill(up_num.begin() + j + 1, up_num.end(), min_k());
     }
    
     copy(hash_copy, hash_copy + 10, hash_key);
     j = i - 1;
     down_num.resize(target_num.size());
     v = find_closet_k(c, false);
     if (v == -1) {
      int aj = target_num[j] - '0';
      down_num[j] = aj - 1 + '0';
      if (hash_key[aj - 1] || is_head_zero(aj - 1,down_num, j)) {
       fill(down_num.begin() + j + 1, down_num.end(), '9');
      } else {
       hash_key[aj] = false;
       hash_key[aj -1] = true;
       fill(down_num.begin() + j + 1, down_num.end(), max_k());
      }
     } else {
      down_num[j + 1] = v + '0';
      fill(down_num.begin() + j + 1, down_num.end(), max_k());
     }
    
     unsigned __int64 upi;
     unsigned __int64 targeti;
     unsigned __int64 downi;
     sscanf(up_num.c_str(), "%lld", &upi);
     sscanf(target_num.c_str(),"%lld",&targeti);
     sscanf(down_num.c_str(),"%lld",&downi);
    
     unsigned __int64 delta = min(upi - targeti, targeti - downi);
     printf("%lld\n", delta);
    
    }
    
    int main(){
     while (true) {
      cin>>target_num>>K;
      fill(hash_key, hash_key + 10, false);
      find_closest();
      up_num.clear();
      down_num.clear();
      target_num.clear();
     }
    
    }
    

答案 5 :(得分:-1)

编辑N2

每一步检查是否:

a)将密集的可能数字减去然后当前,然后最大化剩余数字优于当前回答

b)将最接近的可能数字更大然后当前,然后最小化剩余数字优于当前答案

然后如果可能的话加上相同的数字,然后继续下一个数字