假币问题

原文章:https://blog.csdn.net/qq_39630587/article/details/79243348

假币问题

在八枚外观相同的硬币中,有一枚是假币,并且已知假币与真币的重量不同,但不知道假币与真币相比较轻还是较重。可以通过一架天平来任意比较两组硬币,设计一个高效的算法来检测出这枚假币。

我们先假设一个条件:已知假币比真币轻

二分查找算法实现:时间复杂度(O(log(以2为底n的对数)))

思路:把n枚硬币分成两堆,每堆有枚硬币,如果n为奇数的话,就留下一枚额外的硬币,然后把两堆硬币放在天平上。如果两堆硬币重量相同,那么放在旁边的硬币就是假币;否则我们可以用同样的方式对较轻的一堆硬币进行处理,这堆硬币中一定包含那枚假币。注意,即使我们把硬币分成了两个子集,但在每次称重之后,我们只需要解决一个规模为原来一半的问题。所以这是一个减治算法而不是一个分治算法。

public class Main {
    static int[] a = {2, 2, 2, 2, 2, 2, 1, 2};
    public static void main(String[] args) {
        int l = 0;
        int r = a.length-1;
        System.out.println(f2(l, r));
 
    }
    private static int f2(int l, int r) {
        int n = r - l + 1;
        int mid = (l+r) / 2;
        if(n == 1) {
            return l;
        }
        /**
         * n为总个数,mid为中值
         * n为偶数,则将n枚硬币分成两堆数量相等的硬币,对轻的一堆迭代称重
         * n为奇数,留下一枚硬币,对n-1枚硬币按偶数继续操作
         * */
        if (n % 2 == 0) {
            if (sum(l, mid) == sum(mid+1, r)) {
                return l - 1;
            } else if (sum(l, mid) < sum(mid+1, r)){
                return f2(l, mid);
            } else {
                return f2(mid+1, r);
            }
        } else {
            return f2(l+1, r);
        }
    }
    /**
     * 获取指定区域内硬币的重量
     * */
    private static int sum(int l, int r) {
        int sum = 0;
        for (int i = l; i <= r; i++) {
            sum += a[i];
        }
        return sum;
    }
}

三分查找算法实现:时间复杂度(O(log(以3为底n的对数)))
思路:将n枚硬币分成三组,前两组有组硬币,其余的硬币作为第三组,将前两组硬币放到天平上,如果它们的重量相同,则假币一定在第三组中,用同样的方法对第三组进行处理;如果前两组的重量不同,则假币一定在较轻的那一组中,用同样的方法对较轻的那组硬币进行处理。这也是一个减治算法。

public class Main {
    static int[] a = {2, 2, 2, 2, 2, 2, 1, 2};
    public static void main(String[] args) {
        int l = 0;
        int r = a.length-1;
        System.out.println(f3(l, r));
 
    }
    private static int f3(int l, int r) {
        int n = r - l + 1;
        int x;
        if(n == 1) {
            return l;
        }
        /**
         * n为总个数
         * 将n分成n/3, n/3, n-2(n/3)三堆硬币
         * 对前两堆称重,相等则对第三堆继续操作,不相等则对轻的一堆继续操作
         * */
        if (n % 3 == 0) {
            x = n / 3;
        } else {
            x = n / 3 + 1;
            return f3(l+1, r);
        }
        int mid1 = l + x - 1;
        int mid2 = mid1 + x;
        if (sum(l, mid1) == sum(mid1+1, mid2)) {
            return f3(mid2+1, r);
        } else if (sum(l, mid1) < sum(mid1+1, mid2)){
            return f3(l, mid1);
        } else {
            return f3(mid1+1, mid2);
        }
    }
    /**
     * 获取指定区域内硬币的重量
     * */
    private static int sum(int l, int r) {
        int sum = 0;
        for (int i = l; i <= r; i++) {
            sum += a[i];
        }
        return sum;
    }
}

二分查找算法适用于单调的一个函数,即数组序列要么升序,要么降序

而三分查找算法使用于凸函数,常用来求极值问题。

在假币问题中,三分查找在n较大的情况下,效率是优于二分查找的。

最复杂的情况

下面来回到最开始的问题,在不知道假币轻重的情况下,我们通过下面的算法来实现,其时间复杂度为O(log(以2为底n的对数))

相比来说,这种情况思考起来比较复杂,但是好在逻辑清楚,只要理解了思路,算法还是好实现的,下面我们来举一个例子,假设有八枚硬币,其中有一枚硬币是假币。但是我们不知道假币是比真币重还是轻。先把八枚硬币编号,分别表示为a,b,c,d,e,f,g,h,从八枚硬币中任取六枚a,b,c,d,e,f,在天平两端各放三枚进行比较。假设a,b,c三枚放在天平的一端,d,e,f三枚放在天平的另一端,可能出现三种比较结果:

⑴ a+b+c>d+e+f

⑵ a+b+c=d+e+f

⑶ a+b+c<d+e+f

若a+b+c>d+e+f,可以肯定这六枚硬币中必有一枚为假币,同时也说明g、h为真币。这时可将天平两端各去掉一枚硬币,假设去掉c、f,同时将天平两端的硬币各换一枚,假设硬币b、e作了互换,然后进行第二次比较,比较的结果同样可能有三种:

① a+e>d+b:这种情况表明天平两端去掉硬币c、f且硬币b、e互换后,天平两端的轻重关系保持不变,从而说明了假币必然是a,d中的一个,这时我们只要用一枚真币(例如h)和a进行比较,就能找出假币。若a>h,则a是较重的假币;若a=h,则d为较轻的假币;不可能出现a<h的情况。(为什么?很简单,因为我们判断了a,d中有一个假币那么e,b都是真币,则e=d。而a+e>d+b可以推出a>d,所以不管a是真是假都不可能出现a<h情况出现)

② a+e=d+b:此时天平两端由不平衡变为平衡,表明假币一定在去掉的两枚硬币c,f中,同样用一枚真币(例如h)和c进行比较,若c>h,则c是较重的假币;若c=h,则f为较轻的假币;不可能出现c<h的情况。

③ a+e<d+b:此时表明由于两枚硬币b,e的对换,引起了两端轻重关系的改变,那么可以肯定b或e中有一枚是假币,同样用一枚真币(例如h)和b进行比较,若b>h,则b是较重的假币;若b=h,则e为较轻的假币;不可能出现b<h的情况。

	public class Main {
	    static int[] a = {2, 2, 2, 2, 2, 2, 1, 2};
	    static int flag = 0;
	    public static void main(String[] args) {
	        int p;
	        if (sum(0, 2) == sum(3, 5)) {
	            p = fp(6, 7);
	        } else if (sum(0, 2) > sum(3, 5)){
	            if (a[0] + a[4] > a[3] + a[1]) {
	                p = fp(0, 3);
	            } else if (a[0] + a[4] == a[3] + a[1]) {
	                p = fp(2, 5);
	            } else {
	                p = fp(1, 4);
	            }
	        } else {
	            if (a[0] + a[4] > a[3] + a[1]) {
	                p = fp(1, 4);
	            } else if (a[0] + a[4] == a[3] + a[1]) {
	                p = fp(2, 5);
	            } else {
	                p = fp(0, 3);
	            }
	        }
	        if (flag == 1) {
	            System.out.println("假币轻,为第" + p + "枚");
	        } else {
	            System.out.println("假币重,为第" + p + "枚");
	        }
	 
	    }
    private static int fp(int l, int r) {
        int H, L;
        int x = (l+1) % 8;
        if(a[l] > a[r]) {
            H = l;
            L = r;
        } else {
            H = r;
            L = l;
        }
        if (a[H] > a[x]) {
            flag = 1;
            return H;
        } else {
            flag = -1;
            return L;
        }
    }
    /**
     * 获取指定区域内硬币的重量
     * */
    private static int sum(int l, int r) {
        int sum = 0;
        for (int i = l; i <= r; i++) {
            sum += a[i];
        }
        return sum;
    }
}
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值