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;
/* ÊäÈëÊýÊÇ>0µÄ³¡ºÏ */
if(Input > 0){
/* ÊäÈëÊýÊÇ1µÄ³¡ºÏ */
if (1 == Input) {
printf("%d", Input);
return a_Count;
} else { /* ÊäÈëÊý²»ÊÇ1µÄ³¡ºÏ */
a_ITmp= Input% 2;
a_ITrun =Input/2;
if ((a_ITmp == 1) && (1 != Input/2)) { /* ÊäÈëÊýÊÇÆæÊýÇÒ²»ÊÇ3µÄ³¡ºÏ */
if (a_ITrun%2){ /* a_ITrunÊÇÆæÊýµÄ³¡ºÏ */
printf("%d->", Input);
Input += 1;
a_Count++;
printf("%d->", Input);
Input = Input/2;
a_Count++;
Trilogy(Input);/* µÝ¹éµ÷ÓÃ*/
} else{ /* a_ITrunÊÇżÊýµÄ³¡ºÏ */
printf("%d->", Input);
Input -= 1;
a_Count++;
printf("%d->", Input);
Input = Input/2;
a_Count++;
Trilogy(Input);/* µÝ¹éµ÷ÓÃ*/
}
} else if ((a_ITmp == 1) && (1 == Input/2)){/* ÊäÈëÊýÊÇ3µÄ³¡ºÏ */
printf("%d->", Input);
Input -= 1;
a_Count++;
printf("%d->", Input);
Input = Input/2;
a_Count++;
Trilogy(Input);/* µÝ¹éµ÷ÓÃ*/
} else {/* ÊäÈëÊýÊÇżÊýµÄ³¡ºÏ */
printf("%d->", Input);
Input = a_ITrun;
a_Count++;
Trilogy(Input);/* µÝ¹éµ÷Óà */
}
}
} else {/* ÊäÈëÊýÊÇ<=0µÄ³¡ºÏ */
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);
}
}