华为OD面试算法总结

·  阅读 963

引言

从今年1月份跳槽至今已经半年了, 由于得知公司大多数都是在年初普调1k, 想到明年三四月份已经工作3年, 加上普调的1k只有12k, 在苏州这个城市还是有点少了, 所以在年中先面试一波, 积累一下经验, 为明年的跳槽做准备

去年6月份做过一次OD的机考, 当时第一题是递归、第二题是循环打印、第三题用的是回溯(但是超出时间了, 只通过60%的用例), 当时一面二面过了, HR面试挂了, 如果过了估计我就去OD了. 在去年12月份又面试了OD(因为机考成绩半年有效所以不用考了), 面试都通过了, 到谈薪的时候, 给了9.5k+2k, 当时也有这个非外包的自研, 给了11k, 考虑到OD后期跳槽可能有污点, 而且加班比较严重就拒绝了.

今年6月份boss上又有OD联系了, 想着就做一下, 积累一波, 然后考了机考, 运气比较好, 题目都比较简单, 没有错(。ì _ í。)

下面是机考还有一面二面的题目还有代码.

机考代码

import java.util.*;

/**
 * @program:
 * @description:
 * @create: 2021/6/27 下午7:03
 **/
public class Main {

  /**
   * 模拟复制粘贴, 输入一个 整数数组, 数组元素在1-5之间
   * 1 输入, a 在文本上输入a
   * 2 复制, ctrl + c 复制所选的元素到复制板中
   * 3 剪切: ctrl + x 复制所选的元素到复制板中, 将当前文本内容清空
   * 4 粘贴: ctrl + v 将复制板中的元素粘贴至文本中
   * 5 选择: ctrl + a 将文本内容全选中
   * 输出执行操作后文本中的内容
   */
  public static void questionOne() {
    Scanner in = new Scanner(System.in);
    List<Integer> commands = new ArrayList<>();
    while (in.hasNextInt()) {
      commands.add(in.nextInt());
    }
    char element = 'a';
    StringBuilder content = new StringBuilder();
    boolean isSelected = false;
    String copyContent = "";

    // 没有元素: content.length() == 0
    // 没有选择: selectFlag != true
    // 选择的内容: selectContent = "xefdsafd"
    // ! 拷贝不会撤销全选状态
    for (int command : commands) {
      switch (command) {
        case 1: // 输入a
          if (isSelected == true) {
            content = new StringBuilder();
            isSelected = false;
          }
          content.append(element);
          break;
        case 2: // 复制
          if (isSelected == true) {
            copyContent = content.toString();
          }
          break;
        case 3: // 剪切
          if (isSelected == true) {
            copyContent = content.toString();
            content = new StringBuilder();
          }
          break;
        case 4: // 粘贴
          if (isSelected == true) {
            content = new StringBuilder();
            isSelected = false;
          }
          content.append(copyContent);
          break;
        case 5: // 选择全部
          if (content.length() != 0) {
            isSelected = true;
          } else {
            isSelected = false;
          }
        default:
          break;
      }
    }
    System.out.println(content.length());
  }

  /**
   * 输入两个数, 第一个是指定的和sum, 第二个是元素的个数n
   * 输出是否存在n个连续的正整数(如1, 2, 3, 4), 其和等于sum
   * 
   * 要求: 给定sum和n, 输出符合条件的起始num集合
   */
  public static void questionTwo() {
    Scanner in = new Scanner(System.in);
    int sum, n;
    while (in.hasNextInt()) {
      sum = in.nextInt();
      n = in.nextInt();
      int afterDiffSum = 0;
      for (int i = 0; i < n; i++) {
        afterDiffSum += i;
      }
      int beginNum = (sum - afterDiffSum) / n;
      for (int i = 0; i < n - 1; i++) {
        System.out.print(beginNum + i);
        System.out.println(" ");
      }
      System.out.println(beginNum + n - 1);
    }
  }

  /**
   * 给定一个服务器依赖列表字符串, 如 A-B, B-C 则表示A依赖于B, B依赖于C
   * 再给定一个服务器故障的列表, 如A,B,C 则表示A、B、C故障
   * 如果A依赖于B,服务B依赖于服务C,且C发生了故障,那么服务B和服务A也被认定为不可用
   *
   * 要求: 给定服务依赖列表、服务故障列表, 按顺序输出可用服务的列表
   */
  public static void questionThree() {
    Scanner in = new Scanner(System.in);
    String dependentString = in.nextLine();
    String breakdownString = in.nextLine();
    String[] dependentArr = dependentString.split(",");
    String[] breakdownServices = breakdownString.split(",");
    // 被关联映射, key 被依赖的服务器, value,依赖该服务的服务列表
    Map<String, Set<String>> dependenceMap = new HashMap<>();
    Set<String> orderServices = new LinkedHashSet<>();
    for (String str : dependentArr) {
      String[] dependence = str.split("-");
      String service = dependence[0], dependentService = dependence[1];
      if (!orderServices.contains(service)) {
        orderServices.add(service);
        dependenceMap.put(service, new HashSet<>());
      }
      if (!orderServices.contains(dependentService)) {
        orderServices.add(dependentService);
        dependenceMap.put(dependentService, new HashSet<>());
      }
      dependenceMap.get(dependentService).add(service);
    }
    for (String breakdownService : breakdownServices) {
      Queue<String> breakdownQueue = new LinkedList<>();
      breakdownQueue.add(breakdownService);
      while (!breakdownQueue.isEmpty()) {
        String tempService = breakdownQueue.poll();
        if (orderServices.contains(tempService)) {
          orderServices.remove(tempService);
          for (String influencedService : dependenceMap.get(tempService)) {
            breakdownQueue.offer(influencedService);
          }
        }
      }
    }
    if (orderServices.isEmpty()) {
      System.out.println(",");
      return;
    } else {
      System.out.println(String.join(",", orderServices));
    }
  }
}
复制代码

感觉机考的难度在LeetCode上算是简单中等的难度, 只要LeetCode刷的多, 问题不大, 而且只要超过150分就可以进行后续的面试了

一面手撕算法

应该也是LeetCode的原题

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program:
 * @description:
 * 题目描述
 * 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
 * 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
 * 20:00:54	  heyile h00451788 : 示例:
 * 输入:"23"
 * 输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
 *
 * 示例:
 * 输入:"23"
 * 输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
 * @create: 2021/7/5 下午7:24
 **/
public class Main {

  public static final Map<Integer, char[]> numCharacterMap = new HashMap<Integer, char[]>(){
    {
      put(2, new char[]{'a', 'b', 'c'});
      put(3, new char[]{'d', 'e', 'f'});
      put(4, new char[]{'g', 'h', 'i'});
      put(5, new char[]{'j', 'k', 'l'});
      put(6, new char[]{'m', 'n', 'o'});
      put(7, new char[]{'p', 'q', 'r', 's'});
      put(8, new char[]{'t', 'u', 'v'});
      put(9, new char[]{'w', 'x', 'y', 'z'});
    }
  };

  /**
   * 根据输入字符串, 返回所有它能表示的字母组合。
   * @param s 字符串
   * @return 字母组合
   */
  public List<String> getCombineCharacterList(String s) {
    List<String> res = new ArrayList<>();
    backtrack(res, s, new StringBuilder(), 0, s.length());
    return res;
  }

  /**
   * 回溯
   * @param res 返回的结果集
   * @param s 输入字符串
   * @param sb 当前拼接的字符串
   * @param index 当前下标
   * @param length 字符串长度
   */
  private void backtrack(List<String> res, String s, StringBuilder sb, int index, int length) {
    if (index == length) {
      res.add(sb.toString());
      return;
    }
    char[] chars = numCharacterMap.get(s.charAt(index) - '0');
    for (int i = 0; i < chars.length; i++) {
      sb.append(chars[i]);
      backtrack(res, s, sb, index + 1, length);
      sb.deleteCharAt(sb.length() - 1);
    }
  }

  public static void main(String[] args) {
    Main main = new Main();
    main.getCombineCharacterList("33");
  }
}
复制代码

二面手撕算法

LeetCode中的原题, 第二题

/**
 * @program:
 * @description:
 * @create: 2021/7/10 下午2:58
 **/
public class Main {

  /**
   * 给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。
   * 编写函数对这两个整数求和,并用链表形式返回结果。
   * 要求:自己实现链表
   * 示例:
   * 输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295
   * 输出:2 -> 1 -> 9,即912
   */
  public ListNode getTwoListNodeSum(ListNode node1, ListNode node2) {
    int sum = getListNodeValue(node1) + getListNodeValue(node2);
    return getListNode(sum);
  }

  /**
   * 根据num获取 执行链表
   * @param num
   * @return
   */
  private ListNode getListNode(int num) {
    ListNode dummyNode = new ListNode(0);
    ListNode preNode = dummyNode;
    while (num > 0) {
      ListNode currNode = new ListNode(num % 10);
      preNode.next = currNode;
      preNode = currNode;
      num /= 10;
    }
    return dummyNode.next;
  }

  /**
   * 获取链表的值
   * @param node
   * @return
   */
  private int getListNodeValue(ListNode node) {
    int value = 0;
    int count = 0;
    ListNode dummyNode = node;
    while (dummyNode != null) {
      value += dummyNode.val * Math.pow(10, count);
      count++;
      dummyNode = dummyNode.next;
    }
    return value;
  }


  public static class ListNode {
    int val;
    ListNode next;
    public ListNode(int val) {
      this.val = val;
    }
  }

  public static void main(String[] args) {
    ListNode node1 = new ListNode(7);
    ListNode node2 = new ListNode(1);
    ListNode node3 = new ListNode(6);
    node1.next = node2;
    node2.next = node3;

    ListNode node4 = new ListNode(5);
    ListNode node5 = new ListNode(9);
    ListNode node6 = new ListNode(2);
    node4.next = node5;
    node5.next = node6;

    Main main = new Main();
    main.getTwoListNodeSum(node1, node4);


  }
}

复制代码

之前刷过, 当时忘了, 没必要转换成数字再求和, 可以一边遍历一遍求和输出的, 面试官还说了一下, 这样实现不太好, 会有溢出的问题, 下面是修改后的代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = null, tail = null;
        int carry = 0;
        while (l1 != null || l2 != null) {
            int n1 = l1 != null ? l1.val : 0;
            int n2 = l2 != null ? l2.val : 0;
            int sum = n1 + n2 + carry;
            if (head == null) {
                head = tail = new ListNode(sum % 10);
            } else {
                tail.next = new ListNode(sum % 10);
                tail = tail.next;
            }
            carry = sum / 10;
            if (l1 != null) {
                l1 = l1.next;
            }
            if (l2 != null) {
                l2 = l2.next;
            }
        }
        if (carry > 0) {
            tail.next = new ListNode(carry);
        }
        return head;
    }
}
复制代码

链表类题目的重点有以下几个方面

  • 画图, 通过图可以很直观的看到节点的指向,
  • 设置哨兵节点dummyNode, 这样在返回的时候直接返回dummyNode.next即可
  • 如果有需要对前一个节点也要进行操作的话, 可需要申请一个 prevNode...

总结

手撕算法是挺好的一个环节, Talk is cheap, Show me the code, 从代码中能看出这个人的水平, 从最基本的变量命名方法分层代码注释逻辑思维, 到对 数据结构和算法 的掌握程度.

强烈推荐 极客时间里王争的 《数据结构和算法之美》 (点击链接跳转到课程页面), 并搭配LeetCode(按标签来刷题) 使用更佳, 学习完对后面的工作、理解一些技术的原理帮助都很大. 说白了 程序就是殊绝结构加算法

慢慢来, 比较快

分类:
后端
标签:
分类:
后端
标签: