永利酒店赌场小白详细讲解快速幂–杭电oj2035-A^B

别人家的面试题:一个整数是否是“4”的N次幂

2016/05/30 · 基础技术 ·
2 评论 ·
算法

本文作者: 伯乐在线 –
十年踪迹
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

这是 leetcode.com
的第二篇。与上一篇一样,我们讨论一道相对简单的问题,因为学习总讲究循序渐进。而且,就算是简单的问题,追求算法的极致的话,其中也是有大学问的。

别人家的面试题:统计“1”的个数

2016/05/27 · JavaScript
· 5 评论 ·
Javascript,
算法

本文作者: 伯乐在线 –
十年踪迹
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

小胡子哥 @Barret李靖
给我推荐了一个写算法刷题的地方
leetcode.com,没有 ACM
那么难,但题目很有趣。而且据说这些题目都来源于一些公司的面试题。好吧,解解别人公司的面试题其实很好玩,既能整理思路锻炼能力,又不用担心漏题
╮(╯▽╰)╭。

长话短说,让我们来看一道题:

算法训练 数位分离 

1、Merge Two Binary Trees

Given two binary trees and imagine that when you put one of them to
cover the other, some nodes of the two trees are overlapped while the
others are not.
You need to merge them into a new binary tree. The merge rule is that
if two nodes overlap, then sum node values up as the new value of the
merged node. Otherwise, the NOT null node will be used as the node of
new tree.

Example 1:
Input: 
    Tree 1                     Tree 2                  
          1                         2                             
         /                        /                             
        3   2                     1   3                        
       /                                                    
      5                             4   7                  
Output: 
Merged tree:
         3
        / 
       4   5
      /     
     5   4   7

Note: The merging process must start from the root nodes of both trees.

题意:将两个二叉树合并成一个,要求如果相同节点上都有值,则将其相加,如果只有一个节点有值,则以改值填充,如果都没值,该节点为空。

思路:以一个树为主树,另一个树为从树,同时遍历两个树,如果两个节点都有值,则将从树的值加到主树上,如果从树有值,主树为空,则使用从树的值,填充该节点,如果从树为空,则不用管。

代码实现,使用递归:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        if(t1 != null && t2 != null) {
            t1.left = mergeTrees(t1.left,t2.left);
            t1.right = mergeTrees(t1.right,t2.right);
            t1.val += t2.val;
            return t1;
        }
        return t1 == null ? t2 : t1;
    }
}

Problem Description

“4”的整数次幂

给定一个32位有符号整数(32 bit signed
integer),写一个函数,检查这个整数是否是“4”的N次幂,这里的N是非负整数。

例如:

  • 给定 num = 16,返回 true,因为 16 = 42
  • 给定 num = 5,返回 flase

附加条件: 你能够不用循环和递归吗?

统计“1”的个数

给定一个非负整数 num,对于任意 i,0 ≤ i ≤ num,计算 i
的值对应的二进制数中 “1” 的个数,将这些结果返回为一个数组。

例如:

当 num = 5 时,返回值为 [0,1,1,2,1,2]。

/** * @param {number} num * @return {number[]} */ var countBits =
function(num) { //在此处实现代码 };

1
2
3
4
5
6
7
/**
* @param {number} num
* @return {number[]}
*/
var countBits = function(num) {
    //在此处实现代码
};

时间限制:1.0s  内存限制:512.0MB

2、leetcode Counting Bits

leetcode Counting Bits
Given a non negative integer number num. For every numbers i in the
range 0 ≤ i ≤ num calculate the number of 1’s in their binary
representation and return them as an array.
Example:
For num = 5 you should return [0,1,1,2,1,2].
Follow up:
It is very easy to come up with a solution with run time
O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly
in a single pass?
Space complexity should be O(n).
Can you do it like a boss? Do it without using any builtin function
like __builtin_popcount in c++ or in any other language.

题意:给定一个整数num,输出0~num中每个数的二进制1的个数

求A^B的最后三位数表示的整数。
说明:A^B的含义是“A的B次方”

解题思路

如果忽略“附加条件”,这题还挺简单的对吧?简直是信手拈来:

版本1

JavaScript

function isPowerOfFour(num){ while(!(num % 4)){ num /= 4; } return num
=== 1; }

1
2
3
4
5
6
function isPowerOfFour(num){
    while(!(num % 4)){
        num /= 4;
    }
    return num === 1;
}

版本1 好像很简单、很强大的样子,它的时间复杂度是
log4N。有同学说,还可以做一些微小的改动:

版本1.1

JavaScript

function isPowerOfFour(num){ while(!(num % 4)){ num >>>= 2; }
return num === 1; }

1
2
3
4
5
6
function isPowerOfFour(num){
    while(!(num % 4)){
      num >>>= 2;
    }
    return num === 1;
}

上面的代码用位移替代除法,在其他语言中更快,鉴于 JS
通常情况下非常坑的位运算操作,不一定速度能变快。

好了,最关键的是,不管是 版本1 还是 版本1.1
似乎都不满足我们前面提到的“附加条件”,即不使用循环和递归,或者说,我们需要寻找
O(1) 的解法。

按照惯例,大家先思考10秒钟,然后往下看 ——


解题思路

这道题咋一看还挺简单的,无非是:

  • 实现一个方法 countBit,对任意非负整数
    n,计算它的二进制数中“1”的个数
  • 循环 i 从 0 到 num,求 countBit(i),将值放在数组中返回。

JavaScript中,计算 countBit 可以取巧:

function countBit(n){ return n.toString(2).replace(/0/g,””).length; }

1
2
3
function countBit(n){
    return n.toString(2).replace(/0/g,"").length;
}

上面的代码里,我们直接对 n 用 toString(2)
转成二进制表示的字符串,然后去掉其中的0,剩下的就是“1”的个数。

然后,我们写一下完整的程序:

版本1

function countBit(n){ return n.toString(2).replace(/0/g,”).length; }
function countBits(nums){ var ret = []; for(var i = 0; i <= nums;
i++){ ret.push(countBit(i)); } return ret; }

1
2
3
4
5
6
7
8
9
10
11
function countBit(n){
   return n.toString(2).replace(/0/g,”).length;
}
 
function countBits(nums){
   var ret = [];
   for(var i = 0; i <= nums; i++){
       ret.push(countBit(i));
   }
   return ret;
}

上面这种写法十分讨巧,好处是 countBit 利用 JavaScript
语言特性实现得十分简洁,坏处是如果将来要将它改写成其他语言的版本,就有可能懵B了,它不是很通用,而且它的性能还取决于
Number.prototype.toString(2) 和 String.prototype.replace 的实现。

所以为了追求更好的写法,我们有必要考虑一下 countBit 的通用实现法。

我们说,求一个整数的二进制表示中 “1” 的个数,最普通的当然是一个 O(logN)
的方法:

function countBit(n){ var ret = 0; while(n > 0){ ret += n & 1; n
>>= 1; } return ret; }

1
2
3
4
5
6
7
8
function countBit(n){
    var ret = 0;
    while(n > 0){
        ret += n & 1;
        n >>= 1;
    }
    return ret;
}

所以我们有了版本2

这么实现也很简洁不是吗?但是这么实现是否最优?建议此处思考10秒钟再往下看。


提交此题   

法1:仔细分析一下可发现当一个数是2的整数幂的时候,二进制1的个数为1。之后就是前一个序列+1,如:

1、2(0010) = 1
2、3(0011) = 2(0010) + 1(0001) = 2
3、6(0110) = 4(0100) + 2(0010) = 1 + 1 = 2
4、7(0111) = 4(0100) + 3(0011) = 1 + 2 = 3
5、9 (1001) = 8(1000) + 1(0001) = 1 + 1 = 2
就是把一个数分解为小于它的最大2的整数幂 + x
代码实现:

public static int[] countBits(int num) {
        int[] res = new int[num + 1];
        int pow2 = 1,before = 1;
        for(int i = 1;i<=num;i++){
            if(pow2 == i) {
                res[i] = before = 1;
                pow2 <<= 1;
            } else {
                res[i] = res[before] + 1;
                before++;
            }
        }
        return res;
    }

 Input

不用循环和递归

其实这道题真心有好多种思路,计算指数之类的对数学系学霸们完全不是问题嘛:

版本2

JavaScript

const log4 = Math.log(4); function isPowerOfFour(num){ var n =
Math.log(num) / log4; return n === (0|n); }

1
2
3
4
5
const log4 = Math.log(4);
function isPowerOfFour(num){
    var n = Math.log(num) / log4;
    return n === (0|n);
}

嗯,通过对数公式 logm(n) = log(n) / log(m)
求出指数,然后判断指数是不是一个整数,这样就可以不用循环和递归解决问题。而且,还要注意细节,可以将
log4 当做常量抽取出来,这样不用每次都重复计算,果然是学霸范儿。

不过呢,利用 Math.log
方法也算是某种意义上的犯规吧,有没有不用数学函数,用原生方法来解决呢?

当然有了!而且还不止一种,大家可以继续想30秒,要至少想出一种哦 ——


更快的 countBit

上一个版本的 countBit 的时间复杂度已经是 O(logN)
了,难道还可以更快吗?当然是可以的,我们不需要去判断每一位是不是“1”,也能知道
n 的二进制中有几个“1”。

有一个诀窍,是基于以下一个定律:

  • 对于任意 n, n ≥ 1,有如下等式成立:

countBit(n & (n – 1)) === countBit(n) – 1

1
countBit(n & (n – 1)) === countBit(n) – 1

这个很容易理解,大家只要想一下,对于任意 n,n – 1 的二进制数表示正好是 n
的二进制数的最末一个“1”退位,因此 n & n – 1 正好将 n
的最末一位“1”消去,例如:

  • 6 的二进制数是 110, 5 = 6 – 1 的二进制数是 101,6 & 5
    的二进制数是 110 & 101 == 100
  • 88 的二进制数是 1011000,87 = 88 – 1 的二进制数是
    1010111,88 & 87 的二进制数是 1011000 & 1010111 == 1010000

于是,我们有了一个更快的算法:

版本3

function countBit(n){ var ret = 0; while(n > 0){ ret++; n &= n – 1; }
return ret; } function countBits(nums){ var ret = []; for(var i = 0; i
<= nums; i++){ ret.push(countBit(i)); } return ret; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function countBit(n){
    var ret = 0;
    while(n > 0){
        ret++;
        n &= n – 1;
    }
    return ret;
}
 
function countBits(nums){
   var ret = [];
   for(var i = 0; i <= nums; i++){
       ret.push(countBit(i));
   }
   return ret;
}

上面的 countBit(88) 只循环 3 次,而“版本2”的 countBit(88) 却需要循环
7 次。

优化到了这个程度,是不是一切都结束了呢?从算法上来说似乎已经是极致了?真的吗?再给大家
30 秒时间思考一下,然后再往下看。


问题描述

法2:一个数*2就是把它的二进制全部左移1位,也就是说1的个数是相等的,那我我们如果将一个数右移以为呢?如果这个数是偶数,右移1位正好是num/2,如果是奇数,则是num/2 + 1。我们可以得到以下公式:

res[i] = res[i >> 1] + (i & 1);(如果是奇数,i &
1得到1,如果是偶数i & 1得到0)

我们的代码可以这样改写:

public static int[] countBits2(int num) {
        int[] res = new int[num + 1];
        for (int i = 1; i <= num; i++) {
            res[i] = res[i >> 1] + (i & 1);
        }
        return  res;
    }

输入数据包含多个测试实例,每个实例占一行,由两个正整数A和B组成(1<=A,B<=10000),如果A=0,
B=0,则表示输入数据的结束,不做处理。

不用内置函数

这个问题的关键思路和上一道题类似,先考虑“4”的幂的二进制表示:

  • 40 = 1B
  • 41 = 100B
  • 42 = 10000B
  • 43 = 1000000B
  • ……

也就是每个数比上一个数的二进制后面多两个零嘛。最重要的是,“4”的幂的二进制数只有
1 个“1”。如果仔细阅读过上一篇,你就会知道,判断一个二进制数只有 1
个“1”,只需要:

JavaScript

(num & num – 1) === 0

1
(num & num – 1) === 0

但是,二进制数只有 1
个“1”只是“4”的幂的必要非充分条件,因为“2”的奇数次幂也只有 1
个“1”。所以,我们还需要附加的判断:

JavaScript

(num & num – 1) === 0 && (num & 0xAAAAAAAA) === 0

1
(num & num – 1) === 0 && (num & 0xAAAAAAAA) === 0

为什么是 num & 0xAAAAAAAA === 0? 因为这个确保 num 的二进制的那个 “1”
出现在“奇数位”上,也就确保了这个数确实是“4”的幂,而不仅仅只是“2”的幂。

最后,我们得到完整的版本:

版本3

JavaScript

function isPowerOfFour(num) { return num > 0 && (num & (num-1)) === 0
&& (num & 0xAAAAAAAA) === 0; };

1
2
3
4
function isPowerOfFour(num) {
    return num > 0 && (num & (num-1)) === 0
                   && (num & 0xAAAAAAAA) === 0;
};

上面的代码需要加上 num > 0,是因为 0 要排除在外,否则 (0 & -1) === 0
也是 true


countBits 的时间复杂度

考虑 countBits, 上面的算法:

  • “版本1” 的时间复杂度是 O(N*M),M 取决于 Number.prototype.toString
    和 String.prototype.replace 的复杂度。
  • “版本2” 的时间复杂度是 O(N*logN)
  • “版本3” 的时间复杂度是 O(N*M),M 是 N 的二进制数中的“1”的个数,介于
    1 ~ logN 之间。

上面三个版本的 countBits 的时间复杂度都大于 O(N)。那么有没有时间复杂度
O(N) 的算法呢?

实际上,“版本3”已经为我们提示了答案,答案就在上面的那个定律里,我把那个等式再写一遍:

countBit(n & (n – 1)) === countBit(n) – 1

1
countBit(n & (n – 1)) === countBit(n) – 1

也就是说,如果我们知道了 countBit(n & (n - 1)),那么我们也就知道了
countBit(n)

而我们知道 countBit(0) 的值是 0,于是,我们可以很简单的递推:

版本4

function countBits(nums){ var ret = [0]; for(var i = 1; i <= nums;
i++){ ret.push(ret[i & i – 1] + 1); } return ret; }

1
2
3
4
5
6
7
function countBits(nums){
   var ret = [0];
   for(var i = 1; i <= nums; i++){
       ret.push(ret[i & i – 1] + 1);
   }
   return ret;
}

原来就这么简单,你想到了吗 ╮(╯▽╰)╭

以上就是所有的内容,简单的题目思考起来很有意思吧?程序员就应该追求完美的算法,不是吗?

这是 leetcode
算法面试题系列的第一期,下一期我们讨论另外一道题,这道题也很有趣:判断一个非负整数是否是
4 的整数次方
,别告诉我你用循环,想想更巧妙的办法吧~

打赏支持我写出更多好文章,谢谢!

打赏作者

  编写一个程序,输入一个1000
以内的正整数,然后把这个整数的每一位数字都分离出来,并逐一地显示。

 Output

其他版本

上面的版本已经符合了我们的需求,时间复杂度是 O(1),不用循环和递归。

此外,我们还可以有其他的版本,它们严格来说有的还是“犯规”,但是我们还是可以学习一下这些思路:

版本4:用 Math.sqrt

JavaScript

function isPowerOfFour(num) { num = Math.sqrt(num); return num > 0 &&
num === (0|num) && (num & (num-1)) === 0; };

1
2
3
4
function isPowerOfFour(num) {
    num = Math.sqrt(num);
    return num > 0 && num === (0|num) && (num & (num-1)) === 0;
};

版本5:用正则表达式

JavaScript

function isPowerOfFour(num) { return /^1(00)*$/g.test(num.toString(2));
};

1
2
3
function isPowerOfFour(num) {
    return /^1(00)*$/g.test(num.toString(2));
};

以上就是所有的内容,这道题有非常多种思路,相当有趣,也比较考验基本功。如果你有自己的思路,可以留言参与讨论。

下一期我们讨论另外一道题,这道题比这两道题要难一些,但也更有趣:给定一个正整数
n,将它拆成至少两个正整数之和,对拆出的正整数求乘积,返回能够得到的乘积最大的结果

想一想你的解法是什么?你能够尽可能减少算法的时间复杂度吗?期待你的答案~~

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

永利酒店赌场 1
永利酒店赌场 2

3 赞 8 收藏 5
评论

  输入格式:输入只有一行,即一个1000以内的正整数。

对于每个测试实例,请输出A^B的最后三位表示的整数,每个输出占一行。

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

永利酒店赌场 3
永利酒店赌场 4

1 赞 7 收藏 2
评论

关于作者:十年踪迹

永利酒店赌场 5

月影,奇舞团团长,热爱前端开发,JavaScript
程序猿一枚,能写代码也能打杂卖萌说段子。
个人主页 ·
我的文章 ·
14 ·
    

永利酒店赌场 6

  输出格式:输出只有一行,即该整数的每一位数字,之间用空格隔开。

  简单的说这题就是要求高次幂,有两种方法可以实现。

关于作者:十年踪迹

永利酒店赌场 7

月影,奇舞团团长,热爱前端开发,JavaScript
程序猿一枚,能写代码也能打杂卖萌说段子。
个人主页 ·
我的文章 ·
14 ·
    

永利酒店赌场 6

  输入输出样例

  第一总比较土鳖,每次乘完对1000取余也可以过。

样例输入

  我要讲的是第二种听起来很高大上的方法——快速幂。为什么叫快速幂呢?因为用它求幂非常快,对于x^n,复杂度为O(logn),是不是很吊!快速幂的原理是把幂分解,把一个很大的幂分解成较小的几部分。例如:

769

11的二进制是1011

样例输出

11 = 2³×1 + 2²×0 + 2¹×1 + 2º×1

7 6 9

因此,我们将a¹¹转化为 永利酒店赌场 9

#include <stdio.h>

即把n化为2进制数,每个为1的位都是较小的一部分。这样可以用一个循环来解决。下面是快速幂的非递归代码,暂时忽略max

void getResult(int num)

int cal(int x, int n, int max){

{

  int sum = 1;    //最后输出的结果
  while (n > 0){   //当幂降为0是结束
  if (n &
1)      //位运算,&是按位与,当两边都为1,表达式为1,这个是用来判断二进制数最后一位是否为1,这里n是以二进制的形式比较的

    //出口

    sum =
sum*x%max;//如果为1,sum就要乘x^i,i为该位在二进制数中的位置
  n >>=
1;      //>>为位运算符,右移一位,即去掉已经计算过的部分
  x =
x*x%max;    //用来标记记录x^2^i,循环i次即去掉了i位,当第i+1位为1时,sum就要乘x^2^i;
  }
  return sum;//循环结束返回结果。
}

    if(num<10)

  现在来讲max的作用,用来把数变小的,我们可以想象如果是很大的数的很高次方,乘几次后数据非常大无法用任何一个基本数据类型表示,而且这也是不必要的,通常我们只需要知道最后若干位的值,这就可以用到取余了,余数的幂和原数的幂在余数的位数上是相同的,所以每次进行乘法运算后都要取余,当然如果数据很小也可以不用取余。

    {

  好了,感觉我已经讲的很详细了!!真的是尽力了。。。

        printf(“%d “,num);

下面贴上上面那题的代码

        return ;

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int cal(int x, int n, int max){
 5     int sum = 1;
 6     while (n > 0){
 7         if (n & 1)
 8             sum = sum*x%max;
 9         n >>= 1;
10         x = x*x%max;
11     }
12     return sum;
13 }
14 int main(){
15     int x, n;
16     while ((cin >> x >> n) && (x || n)){
17         cout << cal(x, n, 1000) << endl;
18     }
19     return 0;
20 }

    }

 

    //递归

      getResult(num/10);

 

  printf(“%d “,num%10);

}

int main()

{

  int n; 

  scanf(“%d”,&n);

  getResult(n);

  printf(“n”);

  return 0;

}

思路分析:

①定义变量:一个小于1000的整数;

②输入整数;

③调用函数将每个数字分离:如果该数小于10,则输出;如果该数大于10,则用递归将其分离并输出。

算法训练 6-2递归求二进制表示位数 

时间限制:10.0s  内存限制:256.0MB

提交此题   

问题描述

  给定一个十进制整数,返回其对应的二进制数的位数。例如,输入十进制数9,其对应的二进制数是1001,因此位数是4。

样例输入

一个满足题目要求的输入范例。

9

样例输出

9

4

与上面的样例输入对应的输出。

数据规模和约定

  输入数据中每一个数的范围。

  例:输入在int表示范围内。

#include”stdio.h”

int main()

{

  long int n;

    int s=0;

    scanf(“%d”,&n);

    while(n!=0)

{

      s++;

      n=n/2;

}

printf(“%dn”,s);

return 0;

思路分析:

①定义变量:一个十进制整数,位数(初始化为0);

②输入十进制整数;

③循环直至该数为0跳出,位数加1,转变为二进制是该数被2整除;

④输出位数。

算法训练 友好数 

时间限制:1.0s  内存限制:256.0MB

提交此题   

问题描述

  有两个整数,如果每个整数的约数和(除了它本身以外)等于对方,我们就称这对数是友好的。例如:

  9的约数和有:1+3=4

  4的约数和有:1+2=3

  所以9和4不是友好的。

  220的约数和有:1 2 4 5 10 11 20 22 44 55 110=284

  284的约数和有:1 2 4 71 142=220

  所以220和284是友好的。

  编写程序,判断两个数是否是友好数。

输入格式

  一行,两个整数,由空格分隔

输出格式

  如果是友好数,输出”yes”,否则输出”no”,注意不包含引号。

样例输入

220 284

样例输出

yes

数据规模和约定

  两个整数都小于10000

#include “stdio.h”

int fun(int n){

    int count=0,i;

    for(i=1;i < n;i++){

        if(n % i==0){

            count+=i;

        }

    }

    return count;

}

int main(){

    int num1,num2;

    int a,b;

    scanf(“%d%d”,&num1,&num2);

    b=fun(num1);

    a=fun(num2);

    if(a==num1 && b==num2){

        printf(“yesn”);

    }

    else{

        printf(“non”);

    }

    return 0;

}

思路分析:

①定义变量:两个整数;

②输入两个整数;

③调用函数:

(1) 定义变量:约数,约数和(初始化为0);

(2)for语句循环(从1开始,循环到该数),用if语句判断该数是否被约数整除余数为0,如果为0,则累加;

(3)返回值为约数和;

④if语句判断是否为友好数,如果是则输出yes,否则则为no。

网站地图xml地图