2019年9月25号华为面试算法题


2019年秋招,华为面试加了两轮的手撕代码环节

一面算法题

1. 复盘第一题,可以在2019年4月10号华为春招实习机试上查看代码思路。

2. LIS问题,最长连续递增子序列问题

package huawei.interview1st;

import java.util.Arrays;

/**
* 求一个序列中,最长的一段连续子序列的长度,比如{3, 5, 2, 4, 6, 7, 1, 8}, 最长的连续子序列是{2, 4, 6, 7}长度为4。
*/
public class LongestContinuousSubsequence {

   /*
   考察nums每一个元素为结尾的最长子序列,记录其中最长的即为返回值
   令 len[i]为以 nums[i]为结尾的最长子序列长度,则len[i] 可以由前面的 len[i - 1]决定,
   若 nums[i - 1] >= nums[i],则len[i]为1,否则,len[i]为len[i - 1] + 1
   时间复杂度O(N),空间复杂度O(N)
    */
   public static int longestContinusSubsequenceDP(int[] nums) {
       if (nums == null || nums.length < 1) return 0;
       if (nums.length == 1) return 1;
       int[] len = new int[nums.length];
       Arrays.fill(len, 1);
       int maxLen = 1;
       for (int i = 1; i < len.length; i++) {
           len[i] = nums[i] > nums[i - 1] ? len[i - 1] + 1 : 1;
           maxLen = Math.max(maxLen, len[i]);
       }
       return maxLen;
   }

   /*
   优化DP,其实并不需要len数组,只需要维护一个curLen和maxLen即可
    */
   public static int longestContinusSubsequenceFinal(int[] nums) {
       if (nums == null || nums.length < 1) return 0;
       if (nums.length == 1) return 1;
       int curLen = 1, maxLen = 1;
       for (int i = 1; i < nums.length; i++) {
           if (nums[i] > nums[i - 1]) {
               curLen++;
           } else {
               curLen = 1;
           }
           if (maxLen < curLen) maxLen = curLen;
       }
       return maxLen;
   }

   public static void main(String[] args) {
       int[] nums;
       int n = 1000;
       while (n > 0) {
           nums = generateNums();
           if (longestContinusSubsequenceDP(nums) != longestContinusSubsequenceFinal(nums)) {
               System.out.println("failed");
               return;
           }
           n--;
       }
       System.out.println("succeed");
   }

   private static int[] generateNums() {
       int len = (int) (Math.random() * 50);
       int[] nums = new int[len];
       for (int i = 0; i < nums.length; i++) {
           nums[i] = (int) (Math.random() * 100);
       }
       return nums;
   }
}

3. LIS,最长 递增 连续子序列问题

改进算法参考了博客最长递增子序列LIS的O(nlogn)的求法,请支持原文作者原创内容,认真看可以看懂,且可以收获新的解题思路。

package huawei.interview1st;

import java.util.Arrays;

import static huawei.interview1st.LongestContinuousSubsequence.generateNums;

/**
* 求一个序列中,最长的一段连续子序列的长度,比如{3, 5, 2, 4, 6, 7, 1, 8}, 最长的连续子序列是{2, 4, 6, 7, 8}或{1, 4, 6, 7, 8}或{3, 5, 6, 7, 8},长度为5。
*/
public class LongestNon_ContiguousSubsequence {
   /*
   考察nums每一个元素为结尾的最长子序列,记录其中最长的即为返回值
   令 len[i]为以 nums[i]为结尾的最长子序列长度,则len[i] 可以由前面的 (len[i - 1], nums[i - 1]), ... (len[0], nums[0])决定,
   若 nums[i] > nums[j],则len[i]为len[j] + 1, 否则,为1,且需要比较i前面所有的j
   时间复杂度O(N^2),空间复杂度O(N)
    */
   public static int longestNon_ContiguousSubsequenceDP(int[] nums) {
       if (nums == null || nums.length < 1) return 0;
       if (nums.length == 1) return 1;
       int[] len = new int[nums.length];
       Arrays.fill(len, 1);
       int maxLen = 1;
       for (int i = 1; i < nums.length; i++) {
           for (int j = 0; j < i; j++) {
               if (nums[i] > nums[j]) len[i] = Math.max(len[i], len[j] + 1);
           }
           maxLen = Math.max(maxLen, len[i]);
       }
       return maxLen;
   }

   /*
   优化算法:
   辅助数组tails,tails[i]记录长度为i + 1的子序列的最小末尾数,因为在nums中遇到一个新的数,就把数据更新到tails数组中第一个比这个数大的数的位置,并不会影响后面的tails的更新
   如果,经过二分查找后,发现这个数就是最大数,则,此时,比当前长度更长的最长子序列被发现。
   返回size即为所求值
    */
   public static int longestNon_ContiguousSubsequenceFinal(int[] nums) {
       if (nums == null || nums.length < 1) return 0;
       if (nums.length == 1) return 1;
       int[] tails = new int[nums.length];
       int size = 0;
       for (int x : nums) {
           int i = 0, j = size;
           while (i != j) {
               int m = (j - i) / 2 + i;
               if (tails[m] < x) i = m + 1;
               else j = m;
           }
           tails[i] = x;
           if (i == size) ++size;
       }
       return size;
   }

   public static void main(String[] args) {
       int n = 1000;
       while (n > 0) {
           int[] nums = generateNums();
           if (longestNon_ContiguousSubsequenceDP(nums) != longestNon_ContiguousSubsequenceFinal(nums)) {
               System.out.println("failed");
               System.out.println(nums.length);
               return;
           }
           n--;
       }
       System.out.println("succeed");
   }
}

二面算法题

1. 猴子爬楼梯问题,猴子一次只能爬一级台阶或者三级台阶,问猴子爬到n级台阶总共有多少种方法?

package huawei.interview2nd;

import java.util.Arrays;

/**
* 猴子爬楼梯问题,猴子一次只能爬一级台阶或者三级台阶,问猴子爬到n级台阶总共有多少种方法?
*/
public class MonkeyClimbStairs {

   public static void main(String[] args) {
       int n = 60;
       while (n > 0) {
           if (monKeyClimbStairsDP(n) != monkeyClimbStairsFinal(n)) {
               System.out.println("failed");
               System.out.println(n);
               return;
           }
           n--;
       }
       System.out.println("succeed");
   }

   public static int monkeyClimbStairs(int n) {
       if (n < 3) return 1;
       if (n == 3) return 2;
       return monkeyClimbStairs(n - 1) + monkeyClimbStairs(n - 3);
   }

   public static int monKeyClimbStairsDP(int n) {
       if (n < 3) return 1;
       int[] dp = new int[n + 1];
       Arrays.fill(dp, 1);
       for (int i = 3; i < dp.length; i++) {
           dp[i] = dp[i - 1] + dp[i - 3];
       }
       return dp[n];
   }

   public static int monkeyClimbStairsFinal(int n) {
       if (n < 3) return 1;
       int prePrePre = 1;
       int prePre = 1;
       int pre = 1;
       int cur = 0;
       while (n >= 3) {
           cur = pre + prePrePre;
           prePrePre = prePre;
           prePre = pre;
           pre = cur;
           n--;
       }
       return cur;
   }
}

2. 顺时针旋转的矩阵,(0, 0)位置的数字是1,向右、向下分别是x轴、y轴的正方向,具体题意可以看代码中的注释,求(x, y)对应的值是多少,本题用了对数器对算法做了验证。

package huawei.interview2nd;

import java.util.Date;
/**
* 坐标(0, 0)的数字是1,规定向右由坐标轴的x轴的正方向,向下为y轴的正方向,从原点出发,按顺时针方向可以看到,矩阵的值是递增的,根据(x, y)得到矩阵中的坐标值。
* 示例:
* 21 22 23 24 25
* 20  7  8  9 10
* 19  6  1  2 11
* 18  5  4  3 12
* 17 16 15 14 13
* <p>
* (1, 1)对应的坐标是3, (2, -1)对应的坐标是10
*/

public class ClockwiseMatrixCoordinateEvaluation {
   // 暴力解法,用(m, n)移动到(x, y),记录一个递增的res,当(m, n) = (x, y)时,到达目标点
   // 示例中的 值矩阵 对应的坐标矩阵是
   /*

   (-2, -2) (-1, -2) ( 0, -2) ( 1, -2) ( 2, -2)

   (-1, -1) (-1, -1) ( 0, -1) ( 1, -1) ( 2, -1)

   (-2,  0) (-1,  0) ( 0,  0) ( 1,  0) ( 2,  0)

   (-2,  1) (-1,  1) ( 0,  1) ( 1,  1) ( 2,  1)

   (-2,  2) (-1,  2) ( 0,  2) ( 1,  2) ( 2,  2)


    */
   // 移动规律是 m++ -> 2
   //          n++ -> 3
   //          m-- -> 4
   //          m-- -> 5
   //          n-- -> 6
   //          n-- -> 7
   //          m++ m++ m++ n++ n++ n++ m-- m-- m-- m-- n-- n-- n-- n--
   public static int clockwiseMatrixCoordinateEvaluation(int x, int y) {
       int m = 0; // 移动点的横坐标
       int n = 0; // 移动点的纵坐标
       int res = 1; // 初始值,走一步自加一次,走到终点即为结果值
       boolean postiveDirectMoveFlag = true; // 正方向移动标志
       int steps = 1; // 朝特定方向一次移动步长
       int xCurStep = 0; // 点当前沿着x轴运动的步数
       int yCurStep = 0; // 点当前沿着y轴运动的步数
       while (m != x || n != y) {
           if (postiveDirectMoveFlag) { // 正向移动
               if (xCurStep < steps) {
                   m++;
                   xCurStep++;
                   res++;
               } else if (yCurStep < steps) { // x方向走完, 开始沿着y方向移动
                   n++;
                   yCurStep++;
                   res++;
               } else { // y方向走完,可以设置steps自加1,,且反转方向
                   steps++;
                   xCurStep = 0;
                   yCurStep = 0;
                   postiveDirectMoveFlag = !postiveDirectMoveFlag;
               }
           } else {
               if (xCurStep < steps) {
                   m--;
                   xCurStep++;
                   res++;
               } else if (yCurStep < steps) { // x方向走完, 开始沿着y方向移动
                   n--;
                   yCurStep++;
                   res++;
               } else { // y方向走完,可以设置steps自加1,,且反转方向
                   steps++;
                   xCurStep = 0;
                   yCurStep = 0;
                   postiveDirectMoveFlag = !postiveDirectMoveFlag;
               }
           }
       }
       return res;
   }

   /*
   暴力解法的时间复杂度是O(N),由更低时间复杂度的算法,即需要招数学规律,观察矩阵坐标和对应的矩阵值
   (-2, -2) (-1, -2) ( 0, -2) ( 1, -2) ( 2, -2)

   (-1, -1) (-1, -1) ( 0, -1) ( 1, -1) ( 2, -1)

   (-2,  0) (-1,  0) ( 0,  0) ( 1,  0) ( 2,  0)

   (-2,  1) (-1,  1) ( 0,  1) ( 1,  1) ( 2,  1)

   (-2,  2) (-1,  2) ( 0,  2) ( 1,  2) ( 2,  2)

    21 22 23 24 25
    20  7  8  9 10
    19  6  1  2 11
    18  5  4  3 12
    17 16 15 14 13

    以原点开始给矩阵分成正方形点阵集合的数组pointSet,pointSet[i]点阵的集合为{(x, y)| |x| = i or |y| = i, |x| <= i and |y| <= i}。
    根据(x, y)可以知道所求点在pointSet[Math.max(Math.abs(x), Math.abs(y))]中。

    且,

    坐标(t, -t)对应的值是v,有:
           (2 * t + 1) ^ 2, t >= 0,
    v  =
           (2 * t) ^ 2 + 1, t < 0

    规定,每一圈的终点为(t, -t),t >= 0, 设为 endVal

    pointSet[t], t > 0,且集合元素大小为8*t
    在坐标轴上的四个顶点是(t, t),         (-t, t),        (-t, -t),       (t, -t),
    对应的值分别是       endVal - 6t,    endVal - 4t,    endVal -  2t,   endVal
    对于在pointSet[t]上的坐标(x, y),
    只要根据点在pointSet[t]的一条边上,就可以得到指定的值是多少

    */
   public static int clockwiseMatrixCoordinateEvaluation2(int x, int y) {
       int t = Math.max(Math.abs(x), Math.abs(y)); // 确定(x, y)在pointSet[t];
       int endVal = 1;
       endVal = (t << 1) + 1;
       endVal *= endVal;
       if (endVal == 1 || x == t && y == -t) return endVal;
       if (Math.abs(x) == t) {
           if (x > 0) {
               return endVal - 7 * t + y;
           } else {
               return endVal - 3 * t - y;
           }
       } else {
           if (y > 0) {
               return endVal - 5 * t - x;
           } else {
               return endVal - t + x;
           }
       }
   }

   public static void main(String[] args) {
       System.out.println(clockwiseMatrixCoordinateEvaluation2(1, 1));  //  3
       System.out.println(clockwiseMatrixCoordinateEvaluation2(1, 2));  // 14
       System.out.println(clockwiseMatrixCoordinateEvaluation2(2, 1));  // 12
       System.out.println(clockwiseMatrixCoordinateEvaluation2(2, 2));  // 13
       System.out.println(clockwiseMatrixCoordinateEvaluation2(2, -1)); // 10
       int count = 100000;
       long startTime = System.currentTimeMillis();
       long endTime = 0L;
       System.out.println(new Date().toString());
       while (count > 0) {
           int x = (int) (Math.random() * 100);
           int y = (int) (Math.random() * 100);
           int res1 = clockwiseMatrixCoordinateEvaluation(x, y);
           int res2 = clockwiseMatrixCoordinateEvaluation2(x, y);
           if (res1 != res2) {
               System.out.println("failed");
               System.out.println("x = " + x + ", y = " + y);
               System.out.println("execute time: " + (endTime - startTime) + "ms");
               System.out.println(new Date().getTime());
               return;
           }
           count--;
       }
       endTime = System.currentTimeMillis();
       System.out.println(new Date().getTime());
       System.out.println("execute time: " + (endTime - startTime) + "ms");
       System.out.println("succeed");
   }
}
专业面试我的技巧和经验: 一、 巧妇难为无米之炊,事前做一些功课是必须的,把自己学习过的和应聘岗位相关的内容复习下,比如性能与算法岗位:本科电子信息工程和通信工程这些课程肯定学过,通信原理,信息论和编码,信与系统,数字信处理,复习一下掌握大概的轮廓一个星期应该差不多可以搞定. 二、 善于引导面试官,比如当面试官问到什么问不懂的时候,避免连问几个都不懂,可以尝试这么说:我***方面的知识比较匮乏,不是很了解,但是我对***的知识还是比较熟习,我觉得***的知识在我们华为性能与算法工程师必须要掌握的吧。以我面试为例,面试问我3G和4G的关键技术,已经这些关键技术的基本原理,我是做雷达信处理的,确实不懂。我就和面试官说:对不起,因为研究生期间主要做的雷达信处理的工作,我对移动通信的知识了解甚少,但是我对移动通信的基础只是比如通信原理和调制解调技术还有一定的了解(当然这都是我事先复习好的),我觉得无论什么类型的通信技术都离不开这些基本的理论。接着面试官就让我说信源编码和信道编码的作用已经他们通常采用的方法,当然我也就能对答如流了。所以,引导很重要。 三、 专业面试对自己简历上不要为了蒙骗面试官,写的项目自己捡不熟悉,对简历上的东西一问三不知,语言表达不清楚,说不半天不能告诉面试官你做的工作内容和意义,这个很不好。 群面 一般10-14个人,看当天应聘的人数而定,分2组,一个话,让排序之类的,或者辩论之类的,不同的组会抽到不同的问,不同的地方也会有不同的问,在这里把问说出来没什么意义,这一轮会有很多意想不到的情况,比如这组我本来不是选为组长,但是在做总结的时候面试官让我做总结,或者突然问你刚才某某同学说的话你同意吗,或者突然说你今天脸色好像不好看之类的,所以灵机应变才是王道。群面一般要自我介绍,自我介绍要简短,不要说太多,我建议按以下几个方面说,自己学校专业、来自哪里、然后说自己学习,主要稍微说下自己的项目,说下名字就OK了,然后说自己做项目获得成果,比如发表文章,专利和之类的。然后说自己优点和缺点,一般情况下优点缺点都要说,而且我觉得最好优点缺点自己多准备几个,免得到时候你要说的前面的人都说了,就像我们这组:我开始说缺点的时候说我性格比较急,做什么事情都想快点做完,午觉也不睡,但是经常适得其反,中午不谁觉,下午就工作效率低。后面好几个同学说的时候都这么说了,惹的面试官说,你们重复一个东西说。说缺点的时候大家要慎重,不要说和自己工作相关的缺点,比如我们那个组一个同学说:我的缺点就是比较随性,重要场合经常穿拖鞋为此挨了不少批评。 面试官:。。。。(前面省略了一些),你这种随行的行为有同学提醒过你吗?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值