【读书笔记】《编程珠玑》第二章之算法设计的重要性

这一章的前言部分很值得一看,特别是作者提到的另他印象深刻的一句话

A problem that seems difficult may have a simple, unexpected solution.

这章的原版名"Aha! Insight",aha moment 的论述在心理学的领域早就诞生了,而且这个概念是有他的逻辑的,真是非常的神奇。Google 一下能看很多这方面的文章,知乎也有挺多的,似乎能总结出一些方法论呢,对咱平凡人思考问题解决问题确实有技巧性的提高,另外也很符合《Programming Pearls》所倡导的理念。

想想这章总结咱可以放前头了,特简单直白。原文是这么说的:

Unlike the advanced methods, the aha! insights of algorithms don't come only after extensive study; they're available to any programmer willing to think seriously before, during and after coding. 

有点『曲不离口拳不离手』的意思,是不?好了,让我们看看这章主要阐述的问题吧!

问题

  1. 给定一个最多包含40亿个随机排列的32位整数的顺序文件,找出一个不在文件中的32位整数(在文件中至少缺少一个这样的数,为什么?)。在具有足够内存的情况下,如何解决该问题?如果有几个外部的“临时”文件可用,但是仅有几百字节的内存,又该如何解决该问题?

  2. 将一个n元一维向量左旋转i个位置。例如,当n=8且i=3时,向量abcdefgh旋转为defghabc。简单的代码使用一个n元的中间变量在n步内完成该工作。你能否仅使用数十个额外字节的存储空间,在正比于n的时间内完成向量的旋转?

  3. 给定一个英语词典,找出其中的所有变位词集合。例如,“pots","stop"和“tops"互为变位词,因为每个单词都可以通过改变其他单词中字母的顺序来得到。

 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~思考5分钟先~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 

好了,现在看看难吗?不是非常难吧。我相信解决方案多少都能有的,先说说我的。问题1,这里偷个懒可以直接用第一章的位图来解决,这需要一定的内存。问题2,因为左旋转数已知故按目标位替换。问题3,5分钟内没想出来,但可以摸出一种极其极其低效的方法,找出所有排列,然后比对,最后组合。当然希望你能够发现,最后一种方法在算法可行性的考虑上是不过关的:(

解决方案

对于问题1,作者给出的方案是从每个整数的32位的视角来考虑二分搜索。像对待位图那样通过每一位的0 or 1来设计算法,将数据二分。具体如下图所示:

总归是 2^32 = 4 294 967 296 > 4 000 000 000,一定是少数字的。现在就说假设2^32的全集为E,现有的40亿个数是C,少的那个集是D,我们总有:C + D = E。

那么经过0/1探测器二分后的数据集假设为 A 和 B,这里利用递归的思路,再通过服从单调递减的选集策略,筛选出至最后一个集合是空集,那这次空集的标记就是D集中的了,记录每次探测(分组)的标记就可以得到那个缺少的数了,代码如下:

package chpt2;

import com.google.common.primitives.Ints;

import java.text.DecimalFormat;
import java.util.LinkedList;
import java.util.List;

/**
 * Created by wqi on 2016/10/23.
 */
public class Question2 {
    private final char FLAG_A = '1';// 探测标志
    private final char FLAG_B = '0';// 如上

    private String D="";// 没出现的数
    private int count = 0;// 相当于探测bit位的游标
    private int digit;// 样本的二进制长度

    public static void main(String[] args) {
        int[] shu = {0, 1, 2, 3, 4, 5, 7};
        Question2 q = new Question2();
        List<Integer> integers = Ints.asList(shu);
        q.digit = 3;
        q.findAbsent(integers);
    }

    private List<Integer> group(List<Integer> E, char flag) {
        List<Integer> result = new LinkedList<>();
        for (int i : E) {
            String s = Integer.toBinaryString(i);
            DecimalFormat df = new DecimalFormat(generatePattern(this.digit));
            char c = df.format(Integer.valueOf(s)).charAt(this.count - 1);
            if (c == flag) {
                result.add(i);
            }
        }
        return result;
    }

    private String generatePattern(int count) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < count; i++) {
            sb.append(0);
        }
        return sb.toString();
    }

    public void findAbsent(List<Integer> C) {
        count = Integer.toBinaryString(C.size()).length();
        List<Integer> A = this.group(C, FLAG_A);
        List<Integer> B = this.group(C, FLAG_B);
        if (C.size() == 0) {
            String s = new StringBuilder(D).reverse().toString();
            System.out.println("the absent number is " + Integer.parseInt(s, 2));
            return;
        }
        if (A.size() > B.size()) {
            D += FLAG_B;
            this.findAbsent(B);
        } else {
            D += FLAG_A;
            findAbsent(A);
        }
    }
}

大体的逻辑实现如上,也还有待完善的地方,例如:

没有把输入样本都转为32位整数(so lazy);

没有充分考虑数集的稀疏程度和数组的长度做到弹性的匹配,比方说{242,32342432,6744566,...}就这种无规律来10个,也就是探测的次数没有和位数近似的场景。想了下,最后根据已知的输入整数位数对String D进行补0或者补1就可以了。

 

 

 

 

因为都是一边在这写一边实现的,时间长了有点累,另外说个写读书笔记的感受,就是这样读书的进度快不起来,但有助于消化书本的知识同时也加深理解。

 

本章还有两题,以及课后的习题讲解没涉及,待续。。。

转载于:https://www.cnblogs.com/benma/p/5990804.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值