热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

编程之美2.3笔记:寻找发帖“水王”

抽象就是从问题中提取有用的,本质的特征,然后将问题用一个简洁但包含同样信息的模型表示出来。复杂的问题经抽象后,可能会变成一个简单的问题,也可能会变成一个曾经遇到的问题,当然也可能仍然是复杂的问题。不管抽象后得到的结果是哪一种,看着抽象后的问题,想出解的可能性必然比直接看原题想的可能性大。

《编程之美》2.3:

Tango是微软亚洲研究院的一个试验项目。研究院的员工和实习生们都很喜欢在Tango上面交流灌水。传说,Tango有一大“水王”,他不但喜欢发贴,还会回复其他ID发的每个帖子。坊间风闻该“水王”发帖数目超过了帖子总数的一半。如果你有一个当 前论坛上所有帖子(包括回帖)的列表,其中帖子作者的ID也在表中,你能快速找出这个传说中的Tango水王吗?

当面试的时候我们遇到这样的问题,应该怎么去思考呢?读SICP的一个很大的收获是,学会抽象。

抽象

抽象就是从问题中提取有用的,本质的特征,然后将问题用一个简洁但包含同样信息的模型表示出来。复杂的问题经抽象后,可能会变成一个简单的问题,也可能会变成一个曾经遇到的问题,当然也可能仍然是复杂的问题。不管抽象后得到的结果是哪一种,看着抽象后的问题,想出解的可能性必然比直接看原题想的可能性大。

将这题抽象下就是:给你一个数组,里面有超过一半的数字是一样的,你的任务就是找出这个数字。

关于数字数组的处理,容易联想到的方法是排序或者哈希。那么将这些方法套到问题上,就可以得到:

  1. 排序后,直接输出中位数;
  2. 建立一个大哈希表(数字的值就是它在哈希表中的下标),遍历一次数组,将数都放到哈希表中,在这个过程中,如果发现哪个数的碰撞次数超过一半,就找到了。

这就是将问题抽象后的好处——容易进行知识迁移。

分析与解答

最直接的方法就是对数组中所有的数字排序,然后再扫描一遍,统计各个数字出现的次数,如果某个数字出现的次数超过一半,则输出这个数字。显然这个算法的时间复杂度是O(N * log2N + N)。

事实上,假如现在数组已经有序,那么数组中间的数字一定是这个要求的数字,所以根本不必扫描。此时算法的时间复杂度是O(N * log2N + 1)。那还能不能再简化一些呢?

我们看到,算法主要的消耗在排序这块,那能否跳过排序这个步骤呢?我们这样想,假如每次删除两个不同的数(不管包括不包括最高频数),那么,在剩下的数字里,原最高频数出现的频率一样超过了50%,不断重复这个过程,最后剩下的将全是同样的数字,即最高频数。此算法避免的排序,时间复杂度只为O(N)。

代码如下:

#include "stdio.h"

int FindFloodKing(int num[], int n)
{
    int i;
    int candidate = 0;
    int count = 0;
    for (i = 0; i 

程序运行为:

0 candidate = 9
1 candidate 9 != num[i] 11, count自减为 0
2 candidate = 11
3 candidate 11 != num[i] 13, count自减为 0
4 candidate = 11
5 candidate 11 == num[i] 11, count自增为 2
6 candidate 11 == num[i] 11, count自增为 3
7 candidate 11 != num[i] 18, count自减为 2
8 candidate 11 != num[i] 19, count自减为 1
9 candidate 11 == num[i] 11, count自增为 2
10 candidate 11 == num[i] 11, count自增为 3
11 candidate 11 != num[i] 20, count自减为 2
12 candidate 11 == num[i] 11, count自增为 3
11
  1. 先让candidate等于num[1],candidate = 9
  2. candidate (9) != num[2] (11),所以9和11都丢弃,重新让candidate = num[3] (11)
  3. 反复这个步骤,遇到不同的就丢弃,遇到相同的就用count来记录累计数。目的就是要丢弃掉尽可能多对不同的数字,最后剩下的就是“水数”。

在这个题目中,有一个计算机科学中很普遍的思想,就是如何把一个问题转化为规模较小的若干个问题。分治、递推和贪心等都是基于这样的思路。在转化过程中,小的问题跟原问题本质上一致。这样,我们可以通过同样的方式将小问题转化为更小的问题。因此,转化过程是很重要的。像上面这个题目,我们保证了问题的解在小问题中仍然具有与原问题相同的性质:水王的ID在ID列表中的次数超过一半。转化本身计算的效率越高,转化之后问题规模缩小得越快,则整体算法的时间复杂度越低。

特性利用

特性利用,就是根据问题的特点,定制一个解法。通常来说,这个特制的解法是最优的。使用这种思想需要先找出问题的特点,然后思考是什么导致特点的出现,以及思考特点的使用方法。最后,如果能得到想法,就可以为问题定制一个解法。

在“寻找水王”,即上面提到的问题中,显著的特点是有一个数的出现次数超过一半。从出现次数超过一半的原因的角度思考得不到启发。但是从利用这个特性的角度思考就会得到这样的启发:这个数出现的次数比剩下的数的出现次数总和还要多。将出现次数做减法,剩下的必然是出现次数超过一半的那个数。例如:5,6,5,89,5,56,5这7个数中,5出现的次数比其它数出现次数总和多1。在统计时,如果出现5就将出现次数加1,不出现5就将出现次数减1,会发现出现次数是这样变化的:1,0,1,0,1,0,1。用文字将这个过程表述一下就是:如果下一个数字与 5 相同,就将出现次数加1,否则就将出现次数减1。

更一般的描述是:如果下一个数字与前一个数字相同,就将出现次数加1,不同就将出现次数减1。改成这样时,不管事先是否知道5是出现次数最多的,统计到最后时,都会发现留有次数的是5。调换7个数字的顺序来验证这个一般性的想法,会发现这个想法是对的。将这个想法变成代码就得到了这题的最优解法了。

附带书中设计的函数:

Type Find(Type* ID, int N)
{
    Type candidate;
    int nTimes, i;
    for(i = nTimes = 0; i 

扩展问题

随着Tango的发展,管理员发现,“超级水王”没有了。统计结果表明,有3个发帖很多的ID,他们的发帖数目都超过了帖子总数目N的1/4。你能从发帖ID列表中快速找出他们的ID吗?

参考上面的解法,思路如下:

如果每次删除四个不同的ID(不管是否包含发帖数目超过总数1/4的ID),那么,在剩下的ID列表中,原先发帖比例大于1/4的ID所占比例仍然大于1/4。可以通过不断重复这个过程,把ID列表中的ID总数降低(转化为更小的问题),从而得到问题的答案。

void Find(Type* ID, int N,Type candidate[3])
{
    Type ID_NULL;//定义一个不存在的ID
    int nTimes[3], i;
    nTimes[0]=nTimes[1]=nTimes[2]=0;
    candidate[0]=candidate[1]=candidate[2]=ID_NULL;
    for(i = 0; i 						

延伸阅读

此文章所在专题列表如下:

  1. 编程之美2.3笔记:寻找发帖“水王”

本文地址:http://www.nowamagic.net/librarys/veda/detail/2377,欢迎访问原出处。


推荐阅读
  • 深入解析JVM垃圾收集器
    本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版,详细探讨了JVM中不同类型的垃圾收集器及其工作原理。通过介绍各种垃圾收集器的特性和应用场景,帮助读者更好地理解和优化JVM内存管理。 ... [详细]
  • 非公版RTX 3080显卡的革新与亮点
    本文深入探讨了图形显卡的进化历程,重点介绍了非公版RTX 3080显卡的技术特点和创新设计。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 计算机网络复习:第五章 网络层控制平面
    本文探讨了网络层的控制平面,包括转发和路由选择的基本原理。转发在数据平面上实现,通过配置路由器中的转发表完成;而路由选择则在控制平面上进行,涉及路由器中路由表的配置与更新。此外,文章还介绍了ICMP协议、两种控制平面的实现方法、路由选择算法及其分类等内容。 ... [详细]
  • 本文将介绍如何使用 Go 语言编写和运行一个简单的“Hello, World!”程序。内容涵盖开发环境配置、代码结构解析及执行步骤。 ... [详细]
  • 本文介绍如何利用动态规划算法解决经典的0-1背包问题。通过具体实例和代码实现,详细解释了在给定容量的背包中选择若干物品以最大化总价值的过程。 ... [详细]
  • 深入理解OAuth认证机制
    本文介绍了OAuth认证协议的核心概念及其工作原理。OAuth是一种开放标准,旨在为第三方应用提供安全的用户资源访问授权,同时确保用户的账户信息(如用户名和密码)不会暴露给第三方。 ... [详细]
  • 本文详细探讨了KMP算法中next数组的构建及其应用,重点分析了未改良和改良后的next数组在字符串匹配中的作用。通过具体实例和代码实现,帮助读者更好地理解KMP算法的核心原理。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 程序员妻子吐槽:丈夫北漂8年终薪3万,存款情况令人意外
    一位程序员的妻子在网上分享了她丈夫在北京工作八年的经历,月薪仅3万元,存款情况却出乎意料。本文探讨了高学历人才在大城市的职场现状及生活压力。 ... [详细]
  • Søren Kierkegaard famously stated that life can only be understood in retrospect but must be lived moving forward. This perspective delves into the intricate relationship between our lived experiences and our reflections on them. ... [详细]
  • 线性Kalman滤波器在多自由度车辆悬架主动控制中的应用研究
    本文探讨了线性Kalman滤波器(LKF)在不同自由度(2、4、7)的车辆悬架系统中进行主动控制的应用。通过详细的仿真分析,展示了LKF在提升悬架性能方面的潜力,并总结了调参过程中的关键要点。 ... [详细]
  • 本文探讨了Hive中内部表和外部表的区别及其在HDFS上的路径映射,详细解释了两者的创建、加载及删除操作,并提供了查看表详细信息的方法。通过对比这两种表类型,帮助读者理解如何更好地管理和保护数据。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • C++实现经典排序算法
    本文详细介绍了七种经典的排序算法及其性能分析。每种算法的平均、最坏和最好情况的时间复杂度、辅助空间需求以及稳定性都被列出,帮助读者全面了解这些排序方法的特点。 ... [详细]
author-avatar
Phoenix-Valefor
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有