zoukankan      html  css  js  c++  java
  • Trilogy公司的笔试题

    Trilogy公司的笔试题

    如果n为偶数,则将它除以2,
    如果n为奇数,则将它加1或者减1。
    问对于一个给定的n,怎样才能用最少的步骤将它变到1。
    例如:
    n= 61
    n-- 60
    n/2 30
    n/2 15
    n++ 16
    n/2 8
    n/2 4
    n/2 2
    n/2 1


    我的想法是这样的:
    当n为偶数时,没得选择,除以2就好。所以关键是看对奇数时怎么处理。
    当n为奇数时,如果为1,就结束了,如果不为1,那肯定可以表示成下面得等式:
    n=m*2+1
    此时选择对n是加1还是减1呢?
    如果上式中的m是偶数的话,那n减1,因为m可以继续除以2。如果m是奇数的话,n加1,再除以2之后得到m+1,m+1是偶数可以继续除以2。当然n为3的时候应该减去1。

    我觉得基本上就是“贪心”的思想。

    可以这样考虑, 把N写成2进制的形式, 则有:
    100011110001110
    题目就等于说, 最后为0可以一步去掉, 最后为1则可以通过+1, 或减1, 改为0再去掉, 直到剩1为止. 问最小的步骤.
    可以把数字按0和1分组一下, 比如, 100011110001110就分成
    1 000 1111 000 111 0 六组
    对0组, 则不用选, 直接去掉, 几个0就是几步,
    对1组, 则看连续1的个数m的情况:
    m == 1时 -1 再消0 为两步; +1 进位, 消0, 再减1, 一共3步
    m == 2时 两个1都要 -1 再消0, 一共为4步, +1 进位, 消两个0, 再减1, 一共4步, 两边一样,
    m >= 3时肯定+1消0的方法剩步骤,
    简言之,  +1 的步骤为 2 + m  , 减1 的步骤为 2m. m == 2是个拐点.

    最后如果只剩下两个1, 即3的情况, 因为只要减到1为止, 所以这种情况还是+1步骤少.

    当然, 还有情况没有考虑全面, 特别是中间只有一个0的情况: 如1101,
    抛砖引玉吧:)
    上面写错了, 最后如果只剩下两个1, 即3的情况, 因为只要减到1为止, 所以这种情况还是-1步骤少.

    其实这题很简单,
    1--2--4--8--16...
    大家看上面图,n最终落在某段的区间上(如果n是2的x方那就不用说),
    无论n是+1或-1,在除2后,它总会落在下个区间,如11-->5,6 都落在4--8区间。
    好,现在要做的是尽快让n落在区间端点上,那答案出来:
    算法:
       1. 判断n是否2的x次方,是结束
       2. 计算n落在区间的左区或右区,左区 -1,右区 +1,中间随便,转1.

    对不起,大意,第一步应先除以2,除到基数为止。

    回复人: yaunx(杨) ( 二级(初级)) 信誉:100  

    #include<stdio.h>

    int main()
    {
        int a[100+1];
        int i;
        a[1]=0;
        for(i=2;i<=100;i++)
        {
               if(i%2==0)
                   a[i]=a[i/2]+1;
               else
                   if(a[(i+1)/2]>a[(i-1)/2])
                       a[i]=a[(i-1)/2]+2;
                   else
                       a[i]=a[(i+1)/2]+2;
               printf("%d %d/n",i,a[i]);
        }
        scanf("%s");
        return 0;
    }
    贴个结果

    2 1
    3 2
    4 2
    5 3
    6 3
    7 4
    8 3
    9 4
    10 4
    11 5
    12 4
    13 5
    14 5
    15 5
    16 4
    17 5
    18 5
    19 6
    20 5
    21 6
    22 6
    23 6
    24 5
    25 6
    26 6
    27 7
    28 6
    29 7
    30 6
    31 6
    32 5
    33 6
    34 6
    35 7
    36 6
    37 7
    38 7
    39 7
    40 6
    41 7
    42 7
    43 8
    44 7
    45 8
    46 7
    47 7
    48 6
    49 7
    50 7
    51 8
    52 7
    53 8
    54 8
    55 8
    56 7
    57 8
    58 8
    59 8
    60 7
    61 8
    62 7
    63 7
    64 6
    65 7
    66 7
    67 8
    68 7
    69 8
    70 8
    71 8
    72 7
    73 8
    74 8
    75 9
    76 8
    77 9
    78 8
    79 8
    80 7
    81 8
    82 8
    83 9
    84 8
    85 9
    86 9
    87 9
    88 8
    89 9
    90 9
    91 9
    92 8
    93 9
    94 8
    95 8
    96 7
    97 8
    98 8
    99 9
    100 8

    回复人: EricZhuo() ( 一级(初级)) 信誉:100

    其实最简单的还是用递归实现, 简洁优美. 但是效率不高.
    加上附助的数组保存中间结果能很大程度的提高时间效率, 但却是用一定的空间来换取的.
    下面我试着给出一种在时间上和空间上都比较有效率的算法(不敢保证最优:))


    def fast_shift_time(n):
        assert n > 0
        times = 0
        while n != 1:
            if n % 2 == 0:   #偶数情况
                n /= 2
            elif n & 7 == 7: #二进制末位达到或超过三个连1的情况
                n += 1
            else:            #末位未到三个连1,减1最优
                n -= 1
            times += 1
        return times

    if '__main__' == __name__:
        for i in range(1, 100):
            print i, fast_shift_time(i)

    1-99的结果为:
    1 0
    2 1
    3 2
    4 2
    5 3
    6 3
    7 4
    8 3
    9 4
    10 4
    11 5
    12 4
    13 5
    14 5
    15 5
    16 4
    17 5
    18 5
    19 6
    20 5
    21 6
    22 6
    23 6
    24 5
    25 6
    26 6
    27 7
    28 6
    29 7
    30 6
    31 6
    32 5
    33 6
    34 6
    35 7
    36 6
    37 7
    38 7
    39 7
    40 6
    41 7
    42 7
    43 8
    44 7
    45 8
    46 7
    47 7
    48 6
    49 7
    50 7
    51 8
    52 7
    53 8
    54 8
    55 8
    56 7
    57 8
    58 8
    59 9
    60 7
    61 8
    62 7
    63 7
    64 6
    65 7
    66 7
    67 8
    68 7
    69 8
    70 8
    71 8
    72 7
    73 8
    74 8
    75 9
    76 8
    77 9
    78 8
    79 8
    80 7
    81 8
    82 8
    83 9
    84 8
    85 9
    86 9
    87 9
    88 8
    89 9
    90 9
    91 10
    92 8
    93 9
    94 8
    95 8
    96 7
    97 8
    98 8
    99 9

    没仔细看,应该和递归的结果一样. 原理就是我在楼上分析的, 只有末位超过两个1, +1的选择才优.
    当然, 算法本身还有改进余地. 比如末位超过三个1后, 我们就可以直接得出去掉这么多1的步骤为1+n(n为1的个数), 对减1和/2也可通过判断0和1的位数来直接出结果, 不用每次进入循环.

    啊, 错了. 偷懒的结果 :P
    没有考虑中间有一个0的情况,
    实际上对1011B来说, +1和-1的操作是一样的.
    但对/d+11011B来说+1比-1要少一
    一般的, /d+11(01)*011来说 +1要比-1少一(可以归纳证明之)
    原算法只要加上对1011的判断就可以了:

    def fast_shift_time2(n):
        assert n > 0
        times = 0

        while n != 1:
            if n % 2 == 0:   #偶数情况
                n /= 2
            elif n & 11 == 11: #1011的情况
                n += 1
            elif n & 7 == 7: #二进制末位超过两个连1的情况
                n += 1
            else:            #末位未到三个连1,减1最优
                n -= 1

            times += 1

        return times

    这次小心了, 测到10000都没问题, 后面也不会有问题的:)


    还可以再优化一下. 不过用python作小的优化没什么意义拉, 下面给个C++/C的优化版:

    unsigned int fast_shift_time(unsigned int n)
    {
        assert( n > 0 );
        unsigned int times = 0;

        while ( 1 != n ) {
            if ( n % 2 == 0 ) {
                n /= 2;
                times += 1;
            }
            else if ( (11 & n) == n ) {//末位1011.
                n /= 4; //变1011为11, 要+1, /2, /2共三次操作.
                n += 1;
                times += 3;
            }
            else if ( (7 & n) == 7 ) {//末位超过二个1.
                n += 1;
                n /= 8; //变111为1, 要+1, /2,/2,/2共四个操作.
                times += 4;
            }
            else {//末位两个或两个以下的1, 减1最优.
                n -= 1;
                times += 1;
            }
        }
        return times;
    }

    可以试试算个很大的数. 这个算法算大数应该比递归要快很多.它只跟位数有关, 就是O(log n)
    递归的应该是n!吧?

    动态设计的策略。假设将数字n转化为1的最少步骤为函数f(n),则:

    初始条件:f(2)=1,f(3)=2.

    递推公式:如果n为偶数,则f(n)=f(n/2)+1;
              如果n为奇数,则f(n)=min{f(n-1),f(n+1)}=min{f((n-1)/2),f((n+1)/2)}

    回复人: caozf(草房子) ( 一级(初级)) 信誉:100

    是不是可以这样想呀,假设这个数为m,它必定在2的n次方到2的(n-1)次方之间,计算m与2的n次方之差x和m与2的(n-1)次方之差y。若y>x,则奇数永远加1,否则奇数永远-1。

    打错了,将n-1改为n+1

    回复人: steelfrog(叶落无心)

    int i = src;
    int count = 0;
    while( i > 1)
    {
    if( i%2==0)
    i/=2;
    else if( i ==3 )
    i--;
    else
    {
    i += (i%4-2);
    }
    count++;
    }
    Console.WriteLine( "{0} use times {1}",src,count);

    结果

    1 use times 0
    2 use times 1
    3 use times 2
    4 use times 2
    5 use times 3
    6 use times 3
    7 use times 4
    8 use times 3
    9 use times 4
    10 use times 4
    11 use times 5
    12 use times 4
    13 use times 5
    14 use times 5
    15 use times 5
    16 use times 4
    17 use times 5
    18 use times 5
    19 use times 6
    20 use times 5
    21 use times 6
    22 use times 6
    23 use times 6
    24 use times 5
    25 use times 6
    26 use times 6
    27 use times 7
    28 use times 6
    29 use times 7
    30 use times 6
    31 use times 6
    32 use times 5
    33 use times 6
    34 use times 6
    35 use times 7
    36 use times 6
    37 use times 7
    38 use times 7
    39 use times 7
    40 use times 6
    41 use times 7
    42 use times 7
    43 use times 8
    44 use times 7
    45 use times 8
    46 use times 7
    47 use times 7
    48 use times 6
    49 use times 7
    50 use times 7
    51 use times 8
    52 use times 7
    53 use times 8
    54 use times 8
    55 use times 8
    56 use times 7
    57 use times 8
    58 use times 8
    59 use times 8
    60 use times 7
    61 use times 8
    62 use times 7
    63 use times 7
    64 use times 6
    65 use times 7
    66 use times 7
    67 use times 8
    68 use times 7
    69 use times 8
    70 use times 8
    71 use times 8
    72 use times 7
    73 use times 8
    74 use times 8
    75 use times 9
    76 use times 8
    77 use times 9
    78 use times 8
    79 use times 8
    80 use times 7
    81 use times 8
    82 use times 8
    83 use times 9
    84 use times 8
    85 use times 9
    86 use times 9
    87 use times 9
    88 use times 8
    89 use times 9
    90 use times 9
    91 use times 9
    92 use times 8
    93 use times 9
    94 use times 8
    95 use times 8
    96 use times 7
    97 use times 8
    98 use times 8
    99 use times 9
    100 use times 8

    steelfrog(叶落无心) 的算法很簡潔, 效率也很好. 能講下思路嗎.
    -----------------------------------------------------------

    他的思路是:
    %4 = {1, 3}; 因为{0, 2}已经被排除了
    所以%4 - 2 = {-1, 1},达到奇数+1/-1的效果;

    %4 的调用逻辑是:
    4二进制表示为100, %4表示为011或者001
    也就是如果倒数第二位是1,就+1, 如果倒数第二位是0就-1;

    ------------------------------------------------------

    PS 我的:
    int main(int argc, char** argv)
    {
       int input = 63;

       int init = input;

       while (init >= 1) {
           int nexti = init & 0x1;
           if (nexti == 0)
           {
               printf("%d: /%2 /n", init);
               init = init >> 1;
           } else {
               if (init == 1) {
                  printf("%d:/n ", init);
                  return;
               }

               if (((init >> 1) & 0x1) == 0) {
                  printf("%d: -1, =%d/n", init, init - 1);
                  init -= 1;
               } else {
                  printf("%d: +1, =%d/n", init, init + 1);
                  init += 1;
               }
               init = init >> 1;
           }
       }
    }

     dlyme(大老爷们儿) ( 一级(初级)) 信誉:100

    动态设计的策略。假设将数字n转化为1的最少步骤为函数f(n),则:

    初始条件:f(2)=1,f(3)=2.

    递推公式:如果n为偶数,则f(n)=f(n/2)+1;
              如果n为奇数,则f(n)=min{f(n-1),f(n+1)}=min{f((n-1)/2),f((n+1)/2)}

    dlyme(大老爷们儿) 的解释很合理。
    但我觉得应该是递推法。
    原理:已知[2,n]的最优步法,并在a[n],其中a[i]是i的最优步法,则就可以推出[n+1,2n-1]的最优步法,算法如下:
    设m , n+1<= m <=2n-1 ,
    if m 为偶数  a[m]=a[m/2]+1, 其中m/2 在[2,n]中
    if m 为奇数  则 a[m]=min(a[(m+1)/2])+2,a[(m-1)/2]+2),其中(m+1)/2,(m-1)/2 在[2,n]中,“2” 是补上(+1或-1)和(/2)两步。

     jeremyyang0824() ( 一级(初级))

    二进制的算法想法很不错,即 steelfrog(叶落无心)的,连续1则+1,0和1交错则减1,学习了。yaunx(杨)的迭代算法也很好。

    yaunx(杨)算法正确,但他是由[2,3]往上推,
    我现在给出,给定n,算出n的最优步法的算法,时间空间上都比较好,也符合题的要求。
    原理可以看一下我上面说的递推法
    class test2 { 
      private int a[]=new int[100];
      private int count=0;
     
      public int min(int a,int b){
         return (a<b) ? a:b;  
      }
       
    public static void main(String args[]){
        test2 t=new test2();
        t.CalSetps(Integer.valueOf(args[0]));  
      }
     
      public void CalSetps(int arg){ 
      int n=arg;
      // step1:
      // n/2 --> n1 n除以2直到基数为止
      // 设n2, 使n1=2*n2-1,则我们只需计算[2,n2]的最优步法
      int n1,n2;
      // step1.1 计算n1,n2
      n1=n;
      while(n1%2==0){
        n1=n1/2;
        count++;
      }
      n2=(n1+1)/2;
      // step2:
      // 计算[2,n2]的最优步数
      // [2,3]的最优步数
      a[2]=1;a[3]=2;
      // 向上推最优步数到n2止
      int curN=3; 
      while(curN<n2){
        // n2在下一步计算范围
        // 直接计算
        if (n2<=(2*curN-1)){
        if (n2%2==0)
          a[n2]=a[n2/2]+1;
        else
          a[n2]=min(a[(n2+1)/2],a[(n2-1)/2])+2;
         
        if ((n2-1)%2==0)
          a[n2-1]=a[(n2-1)/2]+1;
        else
          a[n2-1]=min(a[(n2-1+1)/2],a[(n2-1-1)/2])+2; 
        curN=n2;   
        }
        else{
      for(int i=curN+1;i<=2*curN-1;i++){
        if (i%2==0)
          a[i]=a[i/2]+1;
        else
          a[i]=min(a[(i+1)/2],a[(i-1)/2])+2;
      } 
      curN=2*curN-1;
      } // end if
      } // end while
      // step3:
      // 最后计算n的最优步数=count+n1的最优步数
      if (n2!=1)
      a[n]=(count+min(a[n2],a[n2-1])+2);   
      else
        a[n]=count;
      for(int i=1;i<=n;i++){
        if (a[i]>0)
        {
          System.out.print(i);
          System.out.print(" ");
          System.out.println(a[i]);
        }
      } 
    }
    }
    测试n=61:
    2 1
    3 2
    4 2
    5 3
    6 3
    7 4
    8 3
    9 4
    10 4
    11 5
    12 4
    13 5
    14 5
    15 5
    16 4
    17 5
    30 6
    31 6
    61 8
    其中最后一项61的步数,其他是递推中用到的,有一定的优化。

      回复人: snow_kit(最近想象力枯竭) ( 二级(初级)) 信誉:100

    反过去想 结果要为1 有指有1种方法  就是2/2  为偶数的时候没有选择的必要 是除2 为奇数的时候 +1 -1 的选择是看数 除2后是否是个偶数

      回复人: hanliux(寒柳) ( 一级(初级)) 信誉:100

    #include <stdio.h>

    int calc_steps(int);

    int main()
    {
        int n = 61;
        int cnt;
        cnt = calc_steps(n);
        printf("%d/n",cnt);

        scanf("%s");
        return 0;
    }

    /*
    * 1. n == 2: 步骤为 1;
    * 2. n == 3: n-1,步骤为2;n+1,步骤为3;
    * 3. n > 3 的奇数: 如果 n-1 不能被 4 整除,则 n+1 一定能被 4 整除;因此我们只需判断
    *                  n-1 能否被 4 整除。
    */

    int calc_steps(int n){
        int cnt = 0;
           
        while(n != 1){
            if(n & 1){//如果不能被2整除 ,用位运算代替模2
                if((n > 3) && ((n - 1) & 3)){//N>3,如果n-1不能被4整除,则n+1
                    n++;
                } else {
                    n--;
                }
                
            } else {
                n /= 2;
            }
            ++cnt;
        }
        return cnt;
    }

      回复人: ghao0(干什么) ( 一级(初级)) 信誉:100

    不知这个对不?
    public static int 返回方向(数字) //1: +1, -1 -1, 0: /2
    {
    if (数字能被2整除){return 0};
    if (数字2进制尾数 = 111) {return 1}
    if (数字 = 11) {return -1}
    if (数字2进制尾数 = 11) {return 1}
    if (数字2进制尾数 = 001) {return -1}
    if (数字2进制尾数 = 101) {return 返回方向(右移2位(数字))}
    }
    101101011001001110
    17(共18位) + 10(10个1) - 1(111) - 1(1101011) = 25次

    101101011001001110
    17(共18位) + 10(10个1) - 1(111) - 2(1101011) = 24次

      回复人: sky_zm_sky(sky) ( 一级(初级)) 信誉:100

    int n;
    int a=n%4;
    int b=0;
    if(a==0)
    {
    while(n!=1)
    {
    n=n/2;
    b++;
    }
    }
    if(a==1)
    {
    n=n-1;
    while(n<=1)
    {
    n=n/2;
    b++;
    }
    }
    if(a==2)
    {
    n=n-1;
    n=n-1;
    while(n<=1)
    {
    n=n/2;
    b++;
    }
    }
    if(a==3)
    {
    n=n+1;
    while(n<=1)
    {
    n=n/2;
    b++;
    }
    }
    MessageBox.Show(n.ToString());
    MessageBox.Show(b.ToString());

    还有错!
    public static int 返回方向Main(数字) //1: +1, -1 -1, 0: /2
    {
    if (数字 > 3){return 返回方向(数字)}
    else
    {
    if(数字 = 2){return 0}
    if(数字 = 3){return -1}
    }
    }
    public static int 返回方向(数字) //1: +1, -1 -1, 0: /2
    {
    if (能被2整除(数字)){return 0};
    if (2进制尾数(数字) = 111) {return 1}
    if (2进制尾数(数字) = 011) {return 1}
    if (2进制尾数(数字) = 001) {return -1}
    if (2进制尾数(数字) = 101) {return 返回方向(右移2位(数字))}
    }

      回复人: flypanda(SEED 积攒力量) ( 一级(初级)) 信誉:100

    我也想了一种 看看行的通吗 这样 先算范围 如此 给出数 X    2的N+1次方>X>2的N次方
    1.计算n+1次方减X的值除二  2.计算X减N次方的值   3.比较大小   然后 根据小的一方(两边相等情况再考虑)  进行减法(得出值先不看正负),进行分解,以2次方的形式,分解至最小(记住分解次数),最后剩下的1不管,这样完成计算,前面的那个N次方+分解次数,就差不多了
        基本这个意思我觉得,感觉有点问题,不过看着也行,时间仓促,先发出来,

    哦  基本意思就是有一堆2次方把它拆解了  只是定义一些情况

      回复人: Cybergate() ( 三级(初级)) 信誉:104

    yaunx的算法复杂度太大。如果n的范围很大,比如2的31次方,就不凑效了。而这种算法题说白了就是考算法复杂度。

      回复人: juanae() ( 一级(初级)) 信誉:100

    没那么复杂吧.....orz/
    #include <stdio.h>
    void main()
    {
    int n;
    scanf("%d",&n);

    if(n==1){
    printf("n=1, bz=1 /n");
    }

    int i=0;
    while(n!=1)
    {
    if(n%2==0)
    {
    printf("n是偶数 /n");
    n=n/2;
    printf("%d/n",n);
    }
    else{
    printf("n是奇数/n");
    if(((n+1)/2)%2==0 && ((n-1)/2)%2!=0)
    {
    n+=1;
    printf("%d/n",n);
    }
    else{
    n-=1;
    printf("%d/n",n);
    }
    }
    i++;
    }
    printf("共进行了%d次循环",i);
    }

    回复人: ghao0(干什么) ( 一级(初级)) 信誉:100


    手头无编译器,且对c不熟;谁帮忙测试一下;能改进更好
    测试数字:110110101100100111010101101 即 114708141
    #include <stdio.h>
    void main()
    {
    int n;
    scanf("%d",&n);
    int bz;
    bz = 0;
    while(n!=1)
    {
    n=下一步(n);
    printf("%d/n",n);
    bz = bz + 1;
    }
    printf("共进行了%d次循环",bz);
    }

    public static int 下一步(int 数字)
    {
    int 方向;
    方向 = 返回方向Main(数字);
    if (方向 == 0){return 数字 / 2;}
    if (方向 == 1){return 数字 + 1;}
    if (方向 == 0){return 数字 - 1;}
    new Exception ("异常!");
    return 方向;
    }
    static int 返回方向Main(int 数字) //1: +1, -1 -1, 0: /2
    {
    if (数字 > 3){return 返回方向(数字);}
    else
    {
    if(数字 == 2) {return 0;}
    if(数字 == 3) {return -1;}
    new Exception ("异常!");
    return -9;
    }
    }
    static int 返回方向(int 数字) //1: +1, -1 -1, 0: /2
    {
    if (数字%2 ==0){return 0;};
    if (get2进制尾数(数字) == 111) {return 1;}
    if (get2进制尾数(数字) == 011) {return 1;}
    if (get2进制尾数(数字) == 001) {return -1;}
    if (get2进制尾数(数字) == 101) {return 返回方向(右移2位(数字));}
    new Exception ("异常!");
    return -9;
    }
    static int get2进制尾数(int 数字){
                                int i;
                               i = 数字 / 8;
    return 数字 -  i * 8;
    }
    static int 右移2位(int 数字)
    {
    return 数字 / 4;
    }

    61
    62
    31
    32
    16
    8
    4
    2
    1
    共进行了8次循环

    114708141
    114708142
    57354071
    57354072
    28677036
    14338518
    7169259
    7169260
    3584630
    1792315
    1792316
    896158
    448079
    448080
    224040
    112020
    56010
    28005
    28004
    14002
    7001
    7000
    3500
    1750
    875
    876
    438
    219
    220
    110
    55
    56
    28
    14
    7
    8
    4
    2
    1
    共进行了38次循环

    185934
    92967
    92968
    46484
    23242
    11621
    11620
    5810
    2905
    2904
    1452
    726
    363
    364
    182
    91
    92
    46
    23
    24
    12
    6
    3
    2
    1
    共进行了24次循环

    123456789
    123456788
    61728394
    30864197
    30864196
    15432098
    7716049
    7716048
    3858024
    1929012
    964506
    482253
    482254
    241127
    241128
    120564
    60282
    30141
    30142
    15071
    15072
    7536
    3768
    1884
    942
    471
    472
    236
    118
    59
    60
    30
    15
    16
    8
    4
    2
    1
    共进行了37次循环

    987654321
    987654320
    493827160
    246913580
    123456790
    61728395
    61728396
    30864198
    15432099
    15432100
    7716050
    3858025
    3858024
    1929012
    964506
    482253
    482254
    241127
    241128
    120564
    60282
    30141
    30142
    15071
    15072
    7536
    3768
    1884
    942
    471
    472
    236
    118
    59
    60
    30
    15
    16
    8
    4
    2
    1
    共进行了41次循环

    回复人: zengkun100(水的影子) ( 一级(初级)) 信誉:100

    哇!都已经顶了这么多帖了。回去又想了一下,yaunx的算法空间复杂度太大。
    用二进制思考的想法觉得很有新意。

    另外插播一则题外话
    Trilogy公司的考试题只考数据结构和算法,而且据说月薪在10K以上。联想到MS和Google这些公司的笔试题,可见顶级公司最看重的还是数据结构和算法!

      回复人: slayerdragon(楚狂人) ( 一级(初级)) 信誉:100

    /*
     * one number if %2 == 0, number / 2
     * else
     * number +/- 1
     * least steps to 1
     */
    public class T1 {

    int cnt = 0;

    public void proc(int i) {
    if(i <= 0) return;
    if(i == 0x0001) {
    System.out.print("1 -> ");
    System.out.println("ok");
    System.out.println(cnt);
    cnt = 0;
    } else if(i == 0x00000003) {
    System.out.print("3 (-1) -> 2 (/2) -> 1 -> ");
    System.out.println("ok");
    System.out.println(cnt + 2);
    cnt = 0;
    } else if((i & 0x00000001) == 0) {
    System.out.print((i) + " (/2) -> ");
    cnt++;
    proc(i >> 1);
    } else {
    if((i & 0x0000002) == 2) {
    System.out.print((i) + " (+1) -> ");
    cnt++;
    proc(i + 1);
    } else {
    System.out.print((i) + " (-1) -> ");
    cnt++;
    proc(i - 1);
    }
    }
    }

    public void f(int n) {
    StringBuffer sb = new StringBuffer();
    for(int i = 0; i < 32; i++) {
    sb.append(n & 00000001);
    n >>= 1;
    }
    System.out.println(sb.reverse().toString());
    }

    public static void main(String[] args) {
    T1 t = new T1();
    for(int i = 0; i < 100; i++) {
    t.proc(i);
    }
    }
    }
    结果
    1 -> ok
    0
    2 (/2) -> 1 -> ok
    1
    3 (-1) -> 2 (/2) -> 1 -> ok
    2
    4 (/2) -> 2 (/2) -> 1 -> ok
    2
    5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    3
    6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    3
    7 (+1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    4
    8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    3
    9 (-1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    4
    10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    4
    11 (+1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    5
    12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    4
    13 (-1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    5
    14 (/2) -> 7 (+1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    5
    15 (+1) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    5
    16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    4
    17 (-1) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    5
    18 (/2) -> 9 (-1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    5
    19 (+1) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    6
    20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    5
    21 (-1) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    6
    22 (/2) -> 11 (+1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    6
    23 (+1) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    6
    24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    5
    25 (-1) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    6
    26 (/2) -> 13 (-1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    6
    27 (+1) -> 28 (/2) -> 14 (/2) -> 7 (+1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    28 (/2) -> 14 (/2) -> 7 (+1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    6
    29 (-1) -> 28 (/2) -> 14 (/2) -> 7 (+1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    30 (/2) -> 15 (+1) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    6
    31 (+1) -> 32 (/2) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    6
    32 (/2) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    5
    33 (-1) -> 32 (/2) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    6
    34 (/2) -> 17 (-1) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    6
    35 (+1) -> 36 (/2) -> 18 (/2) -> 9 (-1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    36 (/2) -> 18 (/2) -> 9 (-1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    6
    37 (-1) -> 36 (/2) -> 18 (/2) -> 9 (-1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    38 (/2) -> 19 (+1) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    39 (+1) -> 40 (/2) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    40 (/2) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    6
    41 (-1) -> 40 (/2) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    42 (/2) -> 21 (-1) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    43 (+1) -> 44 (/2) -> 22 (/2) -> 11 (+1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    8
    44 (/2) -> 22 (/2) -> 11 (+1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    7
    45 (-1) -> 44 (/2) -> 22 (/2) -> 11 (+1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    8
    46 (/2) -> 23 (+1) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    7
    47 (+1) -> 48 (/2) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    7
    48 (/2) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    6
    49 (-1) -> 48 (/2) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    7
    50 (/2) -> 25 (-1) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    7
    51 (+1) -> 52 (/2) -> 26 (/2) -> 13 (-1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    8
    52 (/2) -> 26 (/2) -> 13 (-1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    7
    53 (-1) -> 52 (/2) -> 26 (/2) -> 13 (-1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    8
    54 (/2) -> 27 (+1) -> 28 (/2) -> 14 (/2) -> 7 (+1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    55 (+1) -> 56 (/2) -> 28 (/2) -> 14 (/2) -> 7 (+1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    56 (/2) -> 28 (/2) -> 14 (/2) -> 7 (+1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    57 (-1) -> 56 (/2) -> 28 (/2) -> 14 (/2) -> 7 (+1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    58 (/2) -> 29 (-1) -> 28 (/2) -> 14 (/2) -> 7 (+1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    59 (+1) -> 60 (/2) -> 30 (/2) -> 15 (+1) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    60 (/2) -> 30 (/2) -> 15 (+1) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    61 (-1) -> 60 (/2) -> 30 (/2) -> 15 (+1) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    62 (/2) -> 31 (+1) -> 32 (/2) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    63 (+1) -> 64 (/2) -> 32 (/2) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    64 (/2) -> 32 (/2) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    6
    65 (-1) -> 64 (/2) -> 32 (/2) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    66 (/2) -> 33 (-1) -> 32 (/2) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    67 (+1) -> 68 (/2) -> 34 (/2) -> 17 (-1) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    68 (/2) -> 34 (/2) -> 17 (-1) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    69 (-1) -> 68 (/2) -> 34 (/2) -> 17 (-1) -> 16 (/2) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    70 (/2) -> 35 (+1) -> 36 (/2) -> 18 (/2) -> 9 (-1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    71 (+1) -> 72 (/2) -> 36 (/2) -> 18 (/2) -> 9 (-1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    72 (/2) -> 36 (/2) -> 18 (/2) -> 9 (-1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    73 (-1) -> 72 (/2) -> 36 (/2) -> 18 (/2) -> 9 (-1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    74 (/2) -> 37 (-1) -> 36 (/2) -> 18 (/2) -> 9 (-1) -> 8 (/2) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    75 (+1) -> 76 (/2) -> 38 (/2) -> 19 (+1) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    9
    76 (/2) -> 38 (/2) -> 19 (+1) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    77 (-1) -> 76 (/2) -> 38 (/2) -> 19 (+1) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    9
    78 (/2) -> 39 (+1) -> 40 (/2) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    79 (+1) -> 80 (/2) -> 40 (/2) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    80 (/2) -> 40 (/2) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    7
    81 (-1) -> 80 (/2) -> 40 (/2) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    82 (/2) -> 41 (-1) -> 40 (/2) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    83 (+1) -> 84 (/2) -> 42 (/2) -> 21 (-1) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    9
    84 (/2) -> 42 (/2) -> 21 (-1) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    8
    85 (-1) -> 84 (/2) -> 42 (/2) -> 21 (-1) -> 20 (/2) -> 10 (/2) -> 5 (-1) -> 4 (/2) -> 2 (/2) -> 1 -> ok
    9
    86 (/2) -> 43 (+1) -> 44 (/2) -> 22 (/2) -> 11 (+1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    9
    87 (+1) -> 88 (/2) -> 44 (/2) -> 22 (/2) -> 11 (+1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    9
    88 (/2) -> 44 (/2) -> 22 (/2) -> 11 (+1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    8
    89 (-1) -> 88 (/2) -> 44 (/2) -> 22 (/2) -> 11 (+1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    9
    90 (/2) -> 45 (-1) -> 44 (/2) -> 22 (/2) -> 11 (+1) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    9
    91 (+1) -> 92 (/2) -> 46 (/2) -> 23 (+1) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    9
    92 (/2) -> 46 (/2) -> 23 (+1) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    8
    93 (-1) -> 92 (/2) -> 46 (/2) -> 23 (+1) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    9
    94 (/2) -> 47 (+1) -> 48 (/2) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    8
    95 (+1) -> 96 (/2) -> 48 (/2) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    8
    96 (/2) -> 48 (/2) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    7
    97 (-1) -> 96 (/2) -> 48 (/2) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    8
    98 (/2) -> 49 (-1) -> 48 (/2) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    8
    99 (+1) -> 100 (/2) -> 50 (/2) -> 25 (-1) -> 24 (/2) -> 12 (/2) -> 6 (/2) -> 3 (-1) -> 2 (/2) -> 1 -> ok
    9
    回复人: ljxdfbb() ( 一级(初级)) 信誉:100

    我用递归的,清晰简洁。

    test.c:
    #include <stdio.h>
    #include "test.h"

    void main()
    {  
        int FunRet = 0;
        int a_Input = 0;

        printf("Please insert a integer :/n");
        scanf("%d", &a_Input);
        /* Call Trilogy Function */
        FunRet = Trilogy(a_Input);
    printf("/n");
    printf("The times become one is : [%d]", FunRet);
       
        return;
    }

    intTrilogy(intInput)
    {
    int a_ITrun = 0;
    inta_ITmp = 0;
        static int a_Count = 0;


        /* &Ecirc;&auml;&Egrave;&euml;&Ecirc;&yacute;&Ecirc;&Ccedil;>0&micro;&Auml;&sup3;&iexcl;&ordm;&Iuml; */
        if(Input > 0){
            /* &Ecirc;&auml;&Egrave;&euml;&Ecirc;&yacute;&Ecirc;&Ccedil;1&micro;&Auml;&sup3;&iexcl;&ordm;&Iuml; */
            if (1 == Input) {
                printf("%d", Input);
                return a_Count;
            } else { /* &Ecirc;&auml;&Egrave;&euml;&Ecirc;&yacute;&sup2;&raquo;&Ecirc;&Ccedil;1&micro;&Auml;&sup3;&iexcl;&ordm;&Iuml; */
                a_ITmp= Input% 2;
                a_ITrun =Input/2;
                if ((a_ITmp == 1) && (1 != Input/2)) { /* &Ecirc;&auml;&Egrave;&euml;&Ecirc;&yacute;&Ecirc;&Ccedil;&AElig;&aelig;&Ecirc;&yacute;&Ccedil;&Ograve;&sup2;&raquo;&Ecirc;&Ccedil;3&micro;&Auml;&sup3;&iexcl;&ordm;&Iuml; */
                    if (a_ITrun%2){ /* a_ITrun&Ecirc;&Ccedil;&AElig;&aelig;&Ecirc;&yacute;&micro;&Auml;&sup3;&iexcl;&ordm;&Iuml; */
                        printf("%d->", Input);
                        Input += 1;
                        a_Count++;
                        printf("%d->", Input);
                        Input = Input/2;
                        a_Count++;
                        Trilogy(Input);/* &micro;&Yacute;&sup1;é&micro;÷&Oacute;&Atilde;*/
                    } else{ /* a_ITrun&Ecirc;&Ccedil;&Aring;&frac14;&Ecirc;&yacute;&micro;&Auml;&sup3;&iexcl;&ordm;&Iuml; */
                        printf("%d->", Input);
                        Input -= 1;
                        a_Count++;
                        printf("%d->", Input);
                        Input = Input/2;
                        a_Count++;
                        Trilogy(Input);/* &micro;&Yacute;&sup1;é&micro;÷&Oacute;&Atilde;*/
                    }
                } else if ((a_ITmp == 1) && (1 == Input/2)){/* &Ecirc;&auml;&Egrave;&euml;&Ecirc;&yacute;&Ecirc;&Ccedil;3&micro;&Auml;&sup3;&iexcl;&ordm;&Iuml; */
                  printf("%d->", Input);
                  Input -= 1;
                  a_Count++;
                  printf("%d->", Input);
                Input = Input/2;
                    a_Count++;
                    Trilogy(Input);/* &micro;&Yacute;&sup1;é&micro;÷&Oacute;&Atilde;*/
                } else {/* &Ecirc;&auml;&Egrave;&euml;&Ecirc;&yacute;&Ecirc;&Ccedil;&Aring;&frac14;&Ecirc;&yacute;&micro;&Auml;&sup3;&iexcl;&ordm;&Iuml; */
                    printf("%d->", Input);
                    Input = a_ITrun;
                    a_Count++;
                    Trilogy(Input);/* &micro;&Yacute;&sup1;é&micro;÷&Oacute;&Atilde; */
                }
            }
        } else {/* &Ecirc;&auml;&Egrave;&euml;&Ecirc;&yacute;&Ecirc;&Ccedil;<=0&micro;&Auml;&sup3;&iexcl;&ordm;&Iuml; */
            printf("Illegal Input:[%d]", Input);
            return a_Count;
        }
    }


    test.h:
    #ifndef _TEST_H

    /* Fuction Definition*/
    int Trilogy(int );

    #define _TEST_H

    #endif

    Please insert a integer :
    2
    2->1
    The times become one is : [1]
    Please insert a integer :
    3
    3->2->1
    The times become one is : [2]
    Please insert a integer :
    4
    4->2->1
    The times become one is : [2]
    ......

    7这个数比较特殊,既可以7->8->4->2->1也可以7->6->3->2->1都是4步到1。
    Please insert a integer :
    7
    7->8->4->2->1
    The times become one is : [4]

      回复人: szlhj() ( 一级(初级)) 信誉:100

    其实二进制思路是近4原则,任一奇数可表示为4n-1或4n+1,前者+1,后者-1,得到尽量多的/2,结果和我的递推一样,有谁能在数学上证明。

    回复人: zengkun100(水的影子) ( 一级(初级)) 信誉:100

    总结一下这个问题:
    首先:问题的核心是——怎样把一个数用最少的变换次数变换到1。所以,在程序设计技巧上采用什么技巧我认为不是这个问题的核心。用递归还是叠代是无所谓的。

    关键是:怎样证明你的算法的确用的是最少的步骤呢?目前看到的算法中,yaunx(杨) 得算法用了归纳的思想,肯定可以得到任何数的最少步骤,但是前面已经说过了:这个算法空间复杂度太高。其它的算法都没有给出数学上的证明,只能算是接近正确的猜测:)

    另外我想说的是:任何想往2^N次方上靠的算法都是不对的。给个例子:
    数13,2^3 < 13 < 2^4
    16-13=3
    13-8=5
    如果想往2^N次方上靠,13应该加1,变成14
    然而对于13来说,最少的变换步骤是:
    13->12->6->3->2->1  5步
    那种想往2^N次方上靠的算法得到的结果是:
    13->14->7->8->4->2->1  6步
    除了13之外,一定还有很多这样的数。


    steelfrog(叶落无心) 的算法本质上和我的想法是一样的
    来看它的这句代码:
    i += (i%4-2);
    lauxp(程序很好玩)已经分析了他的代码:
    ----------------------------------------------------
    %4 = {1, 3}; 因为{0, 2}已经被排除了
    所以%4 - 2 = {-1, 1},达到奇数+1/-1的效果;
    ----------------------------------------------------
    也就是说:当i%4=1的时候i减1,当i%4=3的时候i加1
    如果:i%4=1,那么i可以表示为:
    i=n*4+1=2n*2+1,
    可见i减1后得到的偶数至少可以连续做2次除法,和我的想法一样
    如果:i%4=3,那么i可以表示为:
    i=n*4+3=(2n+1)*2+1
    i如果加1,得到的偶数至少可以连续做2次除法,而减1的话,就只能做一次出发,也是和我的想法也是一样的。


    本人愚钝,还在研究2进制表示的算法。
    还有一种算法大家都没有提到:暴力破解!
    每当n为奇数的时候,求出加1和减1之后的所有的可能。这样得到的一棵树的叶子结点全为1。从根到叶子结点的最短路径就是最少的步骤数。这个算法肯定可以得到正确的解,而且又没有空间复杂度的问题:)

      回复人: szlhj() ( 一级(初级)) 信誉:100

    我来终结一下,递推法。
    原理:已知[2,n]的最优步法,并在a[n],其中a[i]是i的最优步法,则就可以推出[n+1,2n-1]的最优步法,算法如下:
    设m , n+1<= m <=2n-1 ,
    if m 为偶数 a[m]=a[m/2]+1, 其中m/2 在[2,n]中
    if m 为奇数 则 a[m]=min(a[(m+1)/2])+2,a[(m-1)/2]+2),其中(m+1)/2,(m-1)/2 在[2,n]中,“2” 是补上(+1或-1)和(/2)两步。算法的空间复杂度[n2/2],n2意义参见代码

    class test2 {
    private int a[]=new int[100];
    private int count=0;

    public int min(int a,int b){
    return (a<b) ? a:b;
    }

    public static void main(String args[]){
    test2 t=new test2();
    t.CalSetps(Integer.valueOf(args[0]));
    }

    public void CalSetps(int arg){
    int n=arg;
    // step1:
    // n/2 --> n1 n除以2直到基数为止
    // 设n2, 使n1=2*n2-1,则我们只需计算[2,n2]的最优步法
    int n1,n2;
    // step1.1 计算n1,n2
    n1=n;
    while(n1%2==0){
    n1=n1/2;
    count++;
    }
    n2=(n1+1)/2;
    // step2:
    // 计算[2,n2]的最优步数
    // [2,3]的最优步数
    a[2]=1;a[3]=2;
    // 向上推最优步数到n2止
    int curN=3;
    while(curN<n2){
    // n2在下一步计算范围
    // 直接计算
    if (n2<=(2*curN-1)){
    if (n2%2==0)
    a[n2]=a[n2/2]+1;
    else
    a[n2]=min(a[(n2+1)/2],a[(n2-1)/2])+2;

    if ((n2-1)%2==0)
    a[n2-1]=a[(n2-1)/2]+1;
    else
    a[n2-1]=min(a[(n2-1+1)/2],a[(n2-1-1)/2])+2;
    curN=n2;
    }
    else{
    for(int i=curN+1;i<=2*curN-1;i++){
    if (i%2==0)
    a[i]=a[i/2]+1;
    else
    a[i]=min(a[(i+1)/2],a[(i-1)/2])+2;
    }
    curN=2*curN-1;
    } // end if
    } // end while
    // step3:
    // 最后计算n的最优步数=count+n1的最优步数
    if (n2!=1)
    a[n]=(count+min(a[n2],a[n2-1])+2);
    else
    a[n]=count;
    for(int i=1;i<=n;i++){
    if (a[i]>0)
    {
    System.out.print(i);
    System.out.print(" ");
    System.out.println(a[i]);
    }
    }
    }
    }
    我再来终结一下,递推法递归版,空间复杂度与步数同级.
    class TestA {
    public int min(int a,int b){
    return (a<b) ? a:b;
    }

    public static void main(String args[]){
    TestA t=new TestA();
    t.CalSetps(0);
    }

    private int inCalSteps(int n){
      if (n==2)
        return 1;
      else
      if (n==3)
        return 2;
       
      if (n%2==0)
        return inCalSteps(n/2)+1;
      else
        return min(inCalSteps((n+1)/2),inCalSteps((n-1)/2))+2;
    }

    public void CalSetps(int arg){
      for(int i=2;i<=100;i++)
      {
      System.out.print(i);
      System.out.print(" ");
      System.out.println(inCalSteps(i));
      }
    }
    }

    不好意思,原来楼上兄弟wlp555ren()早已出来。就这么简单,看来我们把这问题弄复杂。

    回复人: bigc2000(公元2005年4月9日) ( 二级(初级)) 信誉:100

    szlhj() ( ) 信誉:100    Blog  2007-1-14 18:59:54  得分: 0 

    应该他的方法正确 吧

    只不过,如果-1 和 +1 都在  同一个2的幂次区间内,那么应该-1而不是+1

      回复人: lm_tom() ( 一级(初级)) 信誉:100

    2,算法

    输入:一个32bit无符号数in.
    输出:经过DIV/ADD/SUB变为1的步骤.
    注:
         DIV--- /2操作;
         ADD--- ++操作;
         SUB--- --操作;

    1),if (in==1)
         转3);
       else
         转2); 

    2),if(in is even) 
         in = in DIV 2;
         print div 操作;
       else
         令 res1 = in-1 中所包含的bit 1的个数;
         令 res2 = in+1 中所包含的bit 1的个数; 
         if (res1<=res2)
           in = in SUB 1;
           print SUB 操作;
         else
           in = in ADD 1;
           print ADD 操作;
       endif
       转1);   

    3),退出

    3,证明

    1),DIV/ADD/SUB三种运算,DIV是使in最快逼近1的运算;

    2),使用DIV需要满足(in&1) == 0的条件,如果不能满足该条件,
       只能使用ADD/SUB运算;

    3),in包含的bit 1越多,就需要更多的ADD/SUB步骤;  
       在选择使用ADD/SUB运算过程中,将计算结果包含bit 1的
       个数最少作为选择的标准,使得所选择的运算是当前步骤中
       最优的.
      
      

    附:算法的c源码(在linux下调试通过)

    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>

    /*
      compute the number of the bit 1 in the para bits
    */
    static unsigned int get_one_number(unsigned long bits)
    {
      unsigned int ret = 1;

      while(bits &(bits-1))
      {
            ret++;
            bits = bits & (bits-1);
      }
      return ret;
    }

    int main(int argc,const char* argv[])
    {
      unsigned long in = 0;
      unsigned int out = 0;
     
      in = strtoul(argv[1],NULL,10);
     
    //  out = get_one_number(in);
    //  printf("0x%8x have %4d bit1/n",in,out);

      while(in!=1)
      {
         if (in&1UL)
         {
           unsigned long res1,res2;       
           res1 = get_one_number(in-1);
           res2 = get_one_number(in+1);
           if (res1<=res2)
           {
             printf("%8d(%8x) %8d(%8x) step=%s/n",in,in,in-1,in-1,"SUB"); 
             in-=1;
           }
           else
           {
             printf("%8d(%8x) %8d(%8x) step=%s/n",in,in,in+1,in+1,"ADD"); 
             in+=1;
           }    
         }
         else
         {
           printf("%8d(%8x) %8d(%8x) step=%s/n",in,in,in>>1,in>>1,"DIV");
           in>>=1;
         }
      }

      return 0;
    }

      回复人: mathe() ( 两星(中级)) 信誉:120

    很简单:
     int cnt(int x){
        int m;
        if(x==1)return 0;
        m=x%4;
        if(m==0||m==2)return cnt(x/2)+1;
        if(m==1)return cnt(x-1)+1;
        return cnt(x+1)+1;//if(m==3).
     }
    也就是说,在x是偶数时采用除2。x是奇数时,如果除4余1采用减1,如果除4余3采用加1。
    i)需要证明,对于x=2k,采用加1或减1的方案都没有除2好。
      i1) 2k=>k比2k=>2k+1好,
              2k+1下一步不能再到2k+1-1=2k(走回头路了),所以这个方案比如是两次后变成
            2k=>2k+1=>...=>2k+2s=>k+s (2s+1步s>=1)
            而 2k=>k的方案到k+s只需要2k=>k=>k+1=>...=>k+s (s+1步)
            所以2k=>k的方案必然好于2k=>2k+1的方案。
       i2)2k=>k比2k=>2k-1好
              同样,对于2k=>2k-1的方案,必然如下变换:
                   2k=>2k-1=>....=>2k-2s=>k-s (2s+1步变换)
              而2k=>k的方案可以通过s+1步达到同样目的
                   2k=>k=>k-1=>...=>k-s (s+1步)
              所以2k=>k的方案必然好于2k=>2k-1方案。
    由此,我们得到第一个结论:对于偶数,必然采用除2的方案。
    ii)我们来证明对于x=4k+1的情况,采用x-1的方案不差于采用x+1的方案。
        由结论1,采用x+1的方案必然是
              4k+1=>4k+2=>2k+1
             然后必然是
               4k+1=>4k+2=>2k+1=>2k+2=>k+1或
               4k+1=>4k+2=>2k+1=>2k=>k
            所以采用这个方案必然是通过4步到达k+1或k
          但是通过x=>x-1方案我们可以
               4k+1=>4k=>2k=>k
               4k+1=>4k=>2k=>k=>k+1
          分别通过3步或4步到达k或k+1,所以我们知道,对于x=4k+1,x=>x-1的方案不差于x=>x+1的方案。所以为了达到最优结果我们总可以采用x=>x-1方案。
    iii)最后一步证明对于x=4k+3,x=>x+1方案不差于x=>x-1方案。
        对于x=>x-1方案,根据结论1,其变换必然是
           4k+3=>4k+2=>2k+1,所以下面两步变换必然导致
              4k+3=>4k+2=>2k+1=>2k+2=>k+1 或
              4k+3=>4k+2=>2k+1=>2k=>k
           所以同样是经过4步变换得到k或k+1
         而采用x=>x+1的方案,我们总可以使用
              4k+3=>4k+4=>2k+2=>k+1
              4k+3=>4k+4=>2k+2=>k+1=>k
         经过3步或4步到达k+1或k,所以对于x=4k+3,采用x=>x+1方案不差于x=>x-1方案。
    由上面结论我们可以知道,使用前面的算法必然能够找到最优结果。
    时间复杂度是O(log(n)),空间复杂度是O(log(n))(因为采用了递归),实际上,这个递规是尾递规,通过简单的优化,可以到达时间复杂度位O(log(n))但是空间复杂度为O(1)的结果。


    原来szlhj已经猜测这个方法正确了。

    lm_tom给出的计算方法很凑巧,结果也是正确的,可以通过我的结论推出。不过他的证明是完全错误的。
    to mathe()
       能给出为什么我的证明是"完全错误"的吗? 实际上,这是一个典型的greedy 算法,通过每个步骤的最优达到整体的最优.


       http://blog.csdn.net/lm_tom
    回复人: lm_tom() ( 一级(初级)) 信誉:100

    [test]$ ./num_test 114708141
    114708141( 6d64ead,16) 114708140( 6d64eac,15) step=SUB
    114708140( 6d64eac,15) 57354070( 36b2756,15) step=DIV
    57354070( 36b2756,15) 28677035( 1b593ab,15) step=DIV
    28677035( 1b593ab,15) 28677036( 1b593ac,14) step=ADD
    28677036( 1b593ac,14) 14338518(  dac9d6,14) step=DIV
    14338518(  dac9d6,14)  7169259(  6d64eb,14) step=DIV
     7169259(  6d64eb,14)  7169260(  6d64ec,13) step=ADD
     7169260(  6d64ec,13)  3584630(  36b276,13) step=DIV
     3584630(  36b276,13)  1792315(  1b593b,13) step=DIV
     1792315(  1b593b,13)  1792316(  1b593c,12) step=ADD
     1792316(  1b593c,12)   896158(   dac9e,12) step=DIV
      896158(   dac9e,12)   448079(   6d64f,12) step=DIV
      448079(   6d64f,12)   448080(   6d650, 9) step=ADD
      448080(   6d650, 9)   224040(   36b28, 9) step=DIV
      224040(   36b28, 9)   112020(   1b594, 9) step=DIV
      112020(   1b594, 9)    56010(    daca, 9) step=DIV
       56010(    daca, 9)    28005(    6d65, 9) step=DIV
       28005(    6d65, 9)    28004(    6d64, 8) step=SUB
       28004(    6d64, 8)    14002(    36b2, 8) step=DIV
       14002(    36b2, 8)     7001(    1b59, 8) step=DIV
        7001(    1b59, 8)     7000(    1b58, 7) step=SUB
        7000(    1b58, 7)     3500(     dac, 7) step=DIV
        3500(     dac, 7)     1750(     6d6, 7) step=DIV
        1750(     6d6, 7)      875(     36b, 7) step=DIV
         875(     36b, 7)      876(     36c, 6) step=ADD
         876(     36c, 6)      438(     1b6, 6) step=DIV
         438(     1b6, 6)      219(      db, 6) step=DIV
         219(      db, 6)      220(      dc, 5) step=ADD
         220(      dc, 5)      110(      6e, 5) step=DIV
         110(      6e, 5)       55(      37, 5) step=DIV
          55(      37, 5)       56(      38, 3) step=ADD
          56(      38, 3)       28(      1c, 3) step=DIV
          28(      1c, 3)       14(       e, 3) step=DIV
          14(       e, 3)        7(       7, 3) step=DIV
           7(       7, 3)        8(       8, 1) step=ADD
           8(       8, 1)        4(       4, 1) step=DIV
           4(       4, 1)        2(       2, 1) step=DIV
           2(       2, 1)        1(       1, 1) step=DIV
    cnt=38
    回复人: szlhj() ( 一级(初级)) 信誉:100

    同意 ghao0.
    lm_tom() 结论正确与否不论,推导不够力,每步最优则最优,前提是各步不影响,现在-1/+1对下一步有影响。

    回复人: yaunx(杨) ( 二级(初级)) 信誉:100

    我觉得
    既然lm_tom的结论正确,就有研究一下的必要,
    当初,普朗克简单地用插值公式将瑞利公式和维恩公式联系起来得出了著名的普朗克公式

    再说了,mathe的"在x是偶数时采用除2。x是奇数时,如果除4余1采用减1,如果除4余3采用加1"也不完全正确

    比如3
    3->2->1是最简单的

    3->4->2->1反而复杂不少

    严格地说,如果这是一个命题,那么给出一个反例子就能证明其错误
    那mathe的所谓证明过程不过是一个走形式而已

    我不知道mathe的这个结论是怎么来的,相似的结论,我自己是写满了一张A4的纸然后归纳出来的,但在我看来,即使这个猜测是正确的,也只是解决了问题的1/3而已

    请lz过几天在封贴吧

    回复人: szlhj() ( 一级(初级)) 信誉:100

    近4原则(姑且这样称呼)不是乱猜,思想来自分段的思想,如下图:
    1...4...8...16...
    其中中间的3点可表示为4n+1,4n+2,4n+3
    则任一基数都落到4n+1,4n+3,因此很容易想到让-1/+1后落到2^x得到两次/2,
    若落到4n+2只有一次。-1/+1操作不会改变区段,而/2则会落到下一区,因此,n-->1的过程可以
    看作区段的跳跃,
    yaunx(杨) 提到的

    3->2->1是最简单的

    3->4->2->1反而复杂不少

    3已位于最后一个区,+1便到上一区。如果补上这点,mathe的结论便完整。

    to ls

    猜测不等于证明

    回复人: mathe() ( 两星(中级)) 信誉:120

    3是特例,没有考虑到:)
    在我的证明过程中,使用了:
    iii)最后一步证明对于x=4k+3,x=>x+1方案不差于x=>x-1方案。
        对于x=>x-1方案,根据结论1,其变换必然是
           4k+3=>4k+2=>2k+1,所以下面两步变换必然导致
              4k+3=>4k+2=>2k+1=>2k+2=>k+1 或
              4k+3=>4k+2=>2k+1=>2k=>k
           所以同样是经过4步变换得到k或k+1
         而采用x=>x+1的方案,我们总可以使用
              4k+3=>4k+4=>2k+2=>k+1
              4k+3=>4k+4=>2k+2=>k+1=>k
    也就是至少还需要变换4步以上的才适用,但是对于3,由于可以通过3步就到达最优结果,不能够适用了。所以实际上,上面推导过程只有对于n>4才成立。
    我的推导过程说明对于每个数,得出最优结果的方案可能是不唯一的(实际上会很多),但是利用近4原则可以得到其中一个方案。
    lm_tom的方案正好正确是因为用他的方法,得出方案总是正好同近4原则的方案相同。

    实际上这个问题应该来源于编译器中一个变量乘上整数常数的优化。这种乘法我们总可以优化为移位运算和加法运算。
    比如
    x*3可以优化为(x<<1)+x,这是因为3=(1<<1)+1
    同样 x*5可以优化为(x<<2)+x,这是因为 t=(1<<2)+1.
    如果一个处理器支持移位一位的操作,那么(x<<2)就要转化为两次移位运算了。
    当然这个题目已经将问题大大简化了。对于实际上的处理器,应该移位任何多位都是一样的。而且所有中间结果都可以用来相加,要解决那个问题就很不简单了。

    回复人: lm_tom() ( 一级(初级)) 信誉:100

    1,许多问题都不止一个方法;
    2,还没有人指出我证明的错误之处;
    3, 主要思想: (以32bit数为例)
      一个数字中bit 1(二进制)的数目决定了需要+1 / -1步骤的多少. 当是偶数时候,做除法;当奇数时候,判断+1/-1的标准是(in+1)和(in-1)得到结果中哪个包含的二进制bit 1更少. 当进一步(in-1)和(in+1)的bit 1数目相等时候,可以进一步通过比较(in-1)&3 和(in+1)&3的大小来选择+1还是-1.
       每一步骤都是当前最优的,并且每一步都是收敛,都导致当前数字中包含的bit 1数目在朝着下降的方向走,直到3.
       这种方法得出步骤是唯一的.

    回复人: mathe() ( 两星(中级)) 信誉:120

    >>>都导致当前数字中包含的bit 1数目在朝着下降的方向走,直到3
    这个结论只能保证收敛,不能保证最优。
    比如 二进制数1000000000中只有一个bit1,而二进制数11中有两个1,但是后面那个数到达1却要比前面快的多。
    你的方法只是一种想当然的描述。

    回复人: samwzhang(分全给我) ( 一级(初级)) 信誉:100

    近4原则应该是对的,分析一下。
    0001,0101;
    0011,0111;1111;1011;
    当多位时,0001->0000比起0001->0010,这个不必多讲,肯定减少运算步骤;
    0101->0100比起0101->0110,也是减少运算步骤;
    0011->0100比起0011->0010,看起来是增加了运算步骤;
    0111->1000比起0111->0110,减少运算步骤;
    1111->10000比起1111->1110,减少运算步骤;
    1011->1100和1011->1010,运算步骤不变;

    因此问题就出在0011这一个情况,可以看出,近4原则基本正确,只需要参考除8的余数为3时的特例就可以了。
    即:奇数除4余1是减1;除4余3时,继续观察除8余3则减1,不余3则加1;

      回复人: abc130314() ( 一级(初级)) 信誉:100

    近四原则是正确的,不过当这个数字=3时,除外

    public class sx01 {
         public static void main(String[] arge) {
    int n=114708141;
    int a=0;
    System.out.print(n);
    while ( n>1 ) {
    if ( n%2==0 ) {
    n=n/2;
    a=a+1;
    System.out.print(">>(/2)>>"+n);
    }
    else if ( n%4==1) {
    n=n-1;
    a=a+1;
    System.out.print(">>(-1)>>"+n);
    }
    else if ( n==3 ) {
    n=n-1;
    a=a+1;
    System.out.print(">>(-1)>>"+n);
    }
    else if ( n%4==3 ) {
    n=n+1;
    a=a+1;
    System.out.print(">>(+1)>>"+n);
    }
    }
    System.out.print("/n"+a);
         }
    }

    可是这么理解, 当 把 X 用近四原则 约到 n时:
    当n=1 时(二进制码 00000001 ),( +1 )之后再用近四原则会少用 -2 步;
    当n=3 时(二进制码 00000011 ),( +1 )之后再用近四原则会少用 -1 步;
    当n=7 时(二进制码 00000111 ),( +1 )之后再用近四原则会少用  0 步;
    当n=15时(二进制码 00001111 ),( +1 )之后再用近四原则会少用  1 步;
    当n=31时(二进制码 00011111 ),( +1 )之后再用近四原则会少用  2 步;
    ……
    依次类推;

    回复人: hilanderst() ( 一级(初级)) 信誉:100

    我的想法:
    1.向2^N靠:
    例子:数13,2^3 < 13 < 2^4
    D1= 16-13 =3 =  11(B)
    D2= 13-8  =5 = 101(B)

    2.计算D1,D2包含的1的个数:D1有2位,D2有两位;这是步骤中+1/-1的步骤

    3.要考虑采用+1后,除以2的次数要增加1;
    16->8->4->2->1,要4步;
        8->4->2->1,要3步;
    就是说,采用+1,要增加一步

    综合以上1,2,3,对于13:
    用减法的步骤:
        2 //(减一的步骤)
    +   3 //(右移的步骤)
    ----------------------
        5
    用加法的步骤:
        2 //(加一的步骤)
    +   4 //(右移的步骤)
    ----------------------
        6
    所以要采用减法。

    这种方法应该很好证明,时间复杂度为O(lgN),空间复杂度为O(1).
    回复人: Fortress(观察使) ( 一级(初级)) 信誉:99
    这个原则挺好的:奇数除4余1是减1;除4余3时,继续观察除8余3则减1,不余3则加1;
    没发现反例。

    回复人: star119119(叔叔阿姨大爷大妈,您的帖该结了吧!) ( 一级(初级)) 信誉:100

    #include <iostream>
    using namespace std;
    int count=0;
    int Func(int n){
      count++;
      if(n == 1) {
        return n;
      }else{
        n=(n%2?n+1:n/2);
        return Func( n);
      }
    }

    void main(){
    cout << Func(61) << endl;  // 递归调用
    cout << count << endl;     // 运算次数
    }

    如果最后一个0之后是2个或者以下的连续1就减1,如果是三个或以上连续1就加1

    学到不少东西啊 看来真的是来对了地方啊  这么多高手阿

    我想到的最快方法,不用贪心的算法.因为除2是最快的,所以你在奇数时判断是加1更接近2的n次方还是减1更接近.


    #include <iostream>
    using namespace std;

    void  guiyi(unsigned int n)
    {
    cout<<n<<" ";
    if(n==1)
    {
    cout<<endl;
    return ;//递归结束
    }
    else
    {
    if(n%2==0)
    {
    guiyi(n/2);//如果是偶尔数,除以2继续递归
    }
    else
    {
    int temp=n-1;
    if(temp%4==0)
    {
      n--;
      cout<<n<<" ";
    }
    else
    {
      n++;
      cout<<n<<" ";
    }
    guiyi(n/2);//如果是奇数,减去1除以2再递归
    }
    }
    return ;
    }

    void main()
    {
    int NUM;
    while(1)
    {
    cout<<"输入数据:";
    cin>>NUM;
    guiyi(NUM);
    }

    }

  • 相关阅读:
    [转]Spring的IOC原理[通俗解释一下]
    自我介绍
    什么是存储过程
    Linux 之 最常用的20条命令
    [转]sql语句中出现笛卡尔乘积 SQL查询入门篇
    mysql 多表连接
    正则表达式
    postman 测试API
    [转]mysql 视图
    数据库 修改统一显示时间
  • 原文地址:https://www.cnblogs.com/mtcnn/p/9410191.html
Copyright © 2011-2022 走看看