PHP百题斩(一)

PHP

Posted by cuizhazha on August 11, 2017

Redis和Memcache有什么区别?

答案

共同点:

  1. 都是内存数据库
  2. 都可以做一主多从的分布式集群

区别:

  1. Redis支持hash、list、set、sorted set等多种数据,Memcache 仅支持字符串键值数据。
  2. Redis 只使用单核;Memcache可使用多核多线程。所以100K以下数据Redis性能好,以上Memcache性能好。
  3. Redis 数据可以持久化到磁盘;Memcache 不支持数据持久化,关闭后数据随之消失
  4. Redis 单个key(变量)存放的数据有1GB的限制;Memcache 单个key(变量)存放的数据有1M的限制。
  5. Redis 利用单线程模型提供了事务的功能;Memcached提供了cas命令来保证数据一致性。
  6. redis 支持master-slave复制模式做分布式;memcache可以使用magent的一致性hash做分布式。

CAS(Check and Set)是一个确保并发一致性的机制,属于“乐观锁”范畴; CAS原理很简单:拿版本号,操作,对比版本号,如果一致就操作,不一致就放弃任何操作

PHP求一个数组中出现最多的值

答案

1
2
3
4
5
6
7
8
9
function getMaxCountValue($array)
{
    $array2 = array_count_values($array);   // 统计数组中所有值出现的次数
    arsort($array2);                        // 按照键值对关联数组进行降序排序
    $first = reset($array2);
    $firstKey = key($array2);
    return [$firstKey, $first];
}

测试代码:

1
2
3
4
$array = array(1, 1, 1, 54, 3, 4, 3, 4, 3, 14, 3, 4, 3, 7, 8, 9, 12, 45, 66, 5, 7, 8, 9, 2, 45, 3);
list($key, $count) = getMaxCountValue($array);
echo "数组中数字{$key}重复次数最多,为:{$count}次";

输出结果:

数组中数字3重复次数最多,为:6次

从数组中查找最小的k个元素PHP版

答案

思路:

  1. 假设最前面的1000个数为最小的,算出这1000个数中最大的数,然后和第1001个数比较
  2. 如果这个最大的数比第1001个数小的话,跳过
  3. 如果这个最大的数比第1001个数大则将两个数交换位置,并算出新的1000个数里面的最大数
  4. 再和下一个数比较,以此类推
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
 * 从数组里找出最小的 $k 个数
 * @param $arr array 输入数组
 * @param $k int 输出元素个数
 * @return array 最小元素集合
 */
function getMinK($arr, $k)
{
    $n = count($arr);
    $minArray = array();

    for ($i = 0; $i < $n; $i++) {
        if ($i < $k) {
            $minArray[$i] = $arr[$i];
        } else {
            if ($i == $k) {
                $maxPos = getMaxPos($minArray);
                $max = $minArray[$maxPos];
            }
            if ($arr[$i] < $max) {
                $minArray[$maxPos] = $arr[$i];
                $maxPos = getMaxPos($minArray);
                $max = $minArray[$maxPos];
            }
        }
    }

    return $minArray;
}

/**
 * 从数组中查找最大值的位置
 * @param $arr array 待查找数组
 * @return int 位置
 */
function getMaxPos($arr)
{
    $pos = 0;
    for ($i = 1; $i < count($arr); $i++) {
        if ($arr[$i] > $arr[$pos]) {
            $pos = $i;
        }
    }

    return $pos;
}

测试代码:

1
2
3
4
5
$arr = [1, 100, 20, 22, 33, 44, 55, 66, 23, 79, 18, 20, 11, 9, 129, 399, 145, 2469, 58];
$res = getMinK($arr, 5);

// 输出:[1,9,20,11,18]
echo json_encode($res);

二分查找PHP版

答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function binarySearch($array, $search)
{
    $low = 0;
    $high = count($array) - 1;

    if ($search < $array[$low] || $search > $array[$high]) {
        return false;
    }

    while ($low <= $high) {
        $mid = floor(($high + $low) / 2);

        if ($array[$mid] > $search) {
            $high = $mid - 1;
        } else if ($array[$mid] < $search) {
            $low = $mid + 1;
        } else {
            return $mid;
        }
    }

    return false;
}

时间复杂度:O(logn)

约瑟夫环问题PHP版

一群猴子排成一圈,按1,2,…,n依次编号。

然后从第1只开始数,数到第m只,把它踢出圈,从它后面再开始数,再数到第m只,在把它踢出去…,如此不停的进行下去,直到最后只剩下一只猴子为止,那只猴子就叫做大王。

要求编程模拟此过程,输入m、n, 输出最后那个大王的编号。

答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * 方法一
 */
function monkeyKing($n, $m)
{
    $arr = range(1, $n);             //构造一个数组
    $i = 1;                          //从第一个开始循环

    while (count($arr) > 1) {       //如果总数大于1
        if ($i % $m != 0) {
            $arr[] = $arr[$i - 1];  //不被踢出则压入数组尾部
         }
         unset($arr[$i - 1]);       //压入数组然后删除
        $i++;                       //继续循环
    }

       return $arr[$i - 1];         //直至最后剩下一个为大王
}

/**
 * 方法二
 */
function getMonkeyKing2($n, $m)
{
    $r = 0;
    for ($i = 2; $i <= $n; $i++) {
        $r = ($r + $m) % $i;
    }

    return $r + 1;
}

答案解析

下面是方法1的模拟过程,对于不剔除的猴子,不断的加入数组尾部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 $n = 5
 $m = 3
 $arr = [1, 2, 3, 4, 5]

 $i   $arr
 ---+------------------------
 1    x 2 3 4 5 1
 2    x x 3 4 5 1 2
>3    x x x 4 5 1 2
 4    x x x x 5 1 2 4
 5    x x x x x 1 2 4 5
>6    x x x x x x 2 4 5
 7    x x x x x x x 4 5 2
 8    x x x x x x x x 5 2 4
>9    x x x x x x x x x 2 4
 10   x x x x x x x x x x 4 2
 11   x x x x x x x x x x x 2 4
>12   x x x x x x x x x x x x 4

约瑟夫问题来历:

这个问题是以弗拉维奥·约瑟夫命名的,它是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。

如何设计/优化一个访问量比较大的网站?

答案

  1. 减少HTTP 请求(比如使用雪碧图)
  2. 优化数据库(范式、SQL语句、索引、配置、读写分离)
  3. 缓存使用(Memcache、Redis)
  4. 负载均衡
  5. 动态内容静态化+CDN
  6. 禁止外部盗链(refer、图片添加水印)
  7. 控制大文件下载
  8. 使用集群
  9. PHP接口和抽象类有什么区别?

答案

相同点:

  1. 都是抽象类,都不能实例化
  2. interface实现类及abstract class的子类都必须要实现已经声明的抽象方法。

区别:

  1. 定义接口用interface关键词;定义抽象类用abstract class
  2. 接口需要实现,要用implements;抽象类需要继承,要用extends
  3. 接口中的每一个方法都是抽象方法,都只是声明的,没有方法体,实现类必须要实现。而抽象类的子类可以有选择地实现
  4. 接口没有数据成员;抽象类可以有数据成员,实现数据的封装。
  5. 接口没有构造函数;抽象类可以有构造函数。
  6. 接口的方法都是public类型,而抽象类中的抽象方法可以是protectedpublic,但不能为private
  7. 一个类可以同时实现多个接口,但是只能继承一个抽象类

答案解析

抽象类中的子类可以有选择地实现抽象类,有两点含义:

  1. 抽象类中并非所有的方法都是抽象的,只有那些冠有abstract的方法才是抽象的,子类必须实现。那些没有abstract的方法,在abstract class必须定义方法体
  2. 抽象类的子类在继承它时,对非抽象方法既可以直接继承,也可以覆盖;而对抽象方法,可以选择实现,也可以留给其子类来实现,但此类必须也声明为抽象类。既是抽象类,当然也不能实例化

抽象类是interfaceclass的中介,抽象类在接口和class中起到了承上启下的作用。

  • 一方面,abstract class是抽象的,可以声明抽象方法,以规范子类必须实现的功能;
  • 另一方面,它又可以定义缺省的方法体,供子类直接使用或覆盖;
  • 另外,它还可以定义自己的实例变量,以供子类通过继承来使用。

接口中的抽象方法前不用也不能加abstract关键字,默认隐式就是抽象方法,也不能加final关键字来防止抽象方法的继承。而抽象类中抽象方法前则必须加上abstract表示显示声明为抽象方法。

MySQL如何高效率随机获取N条数据?

答案

假设表叫做mm_account

ID连续的情况下(注意不能带where,否则结果不好):

1
2
3
4
5
SELECT *
FROM `mm_account` AS t1 JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM `mm_account`)) AS id) AS t2
WHERE t1.id >= t2.id
ORDER BY t1.id ASC LIMIT 4;

ID不连续的情况下:

1
2
3
4
SELECT * FROM `mm_account` 
WHERE id >= (SELECT floor(RAND() * (SELECT MAX(id) FROM `mm_account`)))  and city="city_91" and showSex=1
ORDER BY id LIMIT 4;

如果有一个字段叫id,最快的方法如下(随机获取5条):

1
2
3
4
SELECT * FROM mm_account 
WHERE id >= ((SELECT MAX(id) FROM mm_account)-(SELECT MIN(id) FROM mm_account)) * RAND() + (SELECT MIN(id) FROM mm_account)
limit 5;

如果带where语句,上面就不适合了,带where语句请看下面:

1
2
3
4
5
SELECT *
FROM `mm_account` AS t1 JOIN (SELECT ROUND(RAND() * (
(SELECT MAX(id) FROM `mm_account` where id<1000 )-(SELECT MIN(id) FROM `mm_account` where id<1000 ))+(SELECT MIN(id) FROM `mm_account` where id<1000 )) AS id) AS t2
WHERE t1.id >= t2.id
ORDER BY t1.id LIMIT 5;