场景题

场景题

题三:进制转换

2019哈罗单车实习生笔试题

将一个10进制整数转换成36进制的数,可以用0-9A-Z表示0-35.

/** 
 *  Hellobike 2019 interview.
 */
#include <iostream>
#include <string>
 
using namespace std;
 
class Solution {
public:
    string itob36(int n) {
        int r = 0; // remainder of n mod 36
        string tmp;
        // iteratively divde by 36 to get each digit
        while (n) {
            r = n % 36;
            tmp += MAP[r];
            n /= 36;
        }
        // reverse tmp to ret
        string ret;
        cout << "tmp is " << tmp << endl;
        for (auto iter = tmp.rbegin(); iter != tmp.rend(); ++iter)
            ret.push_back(*iter);
        return ret;
    }
private:
    string MAP[36] = {"0", "1", "2", "3", "4", "5",     \
                      "6", "7", "8", "9", "A", "B",     \
                      "C", "D", "E", "F", "G", "H",     \
                      "I", "J", "K", "L", "M", "N",     \
                      "O", "P", "Q", "R", "S", "T",     \
                      "U", "V", "W", "X", "Y", "Z"};
};
 
// test
int main() {
  int number = 12345;
  cout << Solution().itob36(number);
  return 0;
}

题五:输出数组的全排列

给定一个数组,求其全排列。

Idea: pick one element as prefix, then add to the permutations of the rest n-1 elems. Reference: https://www.cnblogs.com/ranjiewen/p/8059336.html

#include <iostream>
#include <iterator>
#include <algorithm>
 
void Permute(int arr[], int beg, int end)
{
  if (beg == end)
  {
    std::copy(arr, arr + end, std::ostream_iterator<int>(std::cout));
    std::cout << std::endl;
  }
 
  for (int i = beg; i != end; ++i)
  {
    std::swap(arr[i], arr[beg]);
    Permute(arr, beg + 1, end);
    std::swap(arr[i], arr[beg]);
  } 
}
 
int main()
{
  int arr[] = {0,1,2,3,4,5,6,7,8,9};
  Permute(arr, 0, 5);
  return 0;
}

题六:斜线填充

给定n x m的矩阵,按照从右上往左下的斜线填充 1 到 n*m 的值。 例如,对于一个3x3的矩阵,

1 2 4
3 5 7
6 8 9 (3x3)

1  2  4  7
3  5  8 10
6  9 11 12  (3x4)

思路:干就完了。先填充左上角的三角区域,再填充右下角的三角区域。注意边界条件,无论行或列到达边界,记得跳转。

#include <iostream>
#include <vector>
 
using namespace std;
 
class Solution {
public:
    Solution(int n, int m)
    {
        // fill with 0
        for (int i = 0; i != n; ++i)
            mat.push_back(vector<int>(m, 0));
    }
 
    void skewFill(int n, int m)
    {
        int cnt = 1;
        // fill the up-left triangle
        for (auto col = 0; col != m; ++col)
        {
            for (auto i = 0, j = col; i != n && j >= 0; ++i)
                mat[i][j--] = cnt++;
        }
        // fill the bottom-right triangle
        for (auto row = 1; row != n; ++row)
        {
            for (auto j = m-1, i = row; i != n && j >= 0; ++i)
                mat[i][j--] = cnt++;
        }
    }
 
    /// print the matrix
    void printer()
    {
        for (auto &row : mat)
        {
            for (auto &col : row)
                printf("%3d ", col);
            cout << endl;
        }
    }
 
private:
    vector<vector<int>> mat;
};
 
// test
int main()
{
    int n = 0, m = 0;
    cin >> n >> m;
    Solution s(n, m);
    s.skewFill(n, m);
    s.printer();
    return 0;
}

题七:螺旋矩阵

按顺时针填充螺旋填充矩阵。例如:

9 8 7
2 1 6
3 4 5 (3x3)

12 11 10 9
 3  2  1 8
 4  5  6 7 (3x4)

Idea: Imagine there are for borders that surround the matrix. We walk through the mat, and once achieve a border, we change the direction. Each time we finished a circle, we will shrink our border, and start next circle. After we fill (row * col) elems in the matrix, we are done!

Note: this solution is the most elegant in my opinion, for it’s simple boundray case.

#include <iostream>
#include <vector>
 
using std::vector;
 
class Solution
{
public:
  Solution(int rows, int cols)
    : mat_(rows, vector<int>(cols, 0))
  {
  }
 
  void BuildMat()
  {
    // 4 border
    int top = 0;
    int bot = mat_.size() - 1;
    int left = 0;
    int right = mat_[0].size() - 1;
 
    int N = (bot + 1) * (right + 1);
    for (int i = 0, j = 0; N != 0; --N)
    {
      // make sure fill exactly one element at each loop
      mat_[i][j] = N;
 
      if (i == top)
      {
        if (j < right) ++j;       // go right
        else if (j == right) ++i; // checkpoint
      }
      else if (j == right)
      {
        if (i < bot) ++i;         // go down
        else if (i == bot) --j;
      }
      else if (i == bot)
      {
        if (j > left) --j;        // go left
        else if (j == left) --i;
      }
      else if (j == left)
      {
        if (i > top + 1) --i;     // go up
        else if (i == top + 1)
        {
          ++j;
          // shrink borders
          ++top, --bot, ++left, --right;
        }
      }
      else
      {
        throw std::runtime_error("not behaved expectedly");
      }
    }
  }
 
  void Print()
  {
    for (auto &r : mat_)
    {
      for (auto &c : r)
        printf("%2d ", c);
      std::cout << std::endl;
    }
  }
 
private:
  vector<vector<int> > mat_;
};
 
// test
int main()
{
  Solution so(3, 4);
  so.BuildMat();
  so.Print();
  return 0;
}

模式匹配

扔骰子的期望

拼多多2019校招正式批

扔n个骰子,第i个骰子有可能掷出种等概率的不同结果,数字从1到. 比如, 就等可能的出现1点或2点。所有骰子的结果的最大值将作为最终结果。求最终结果的期望。

输入描述: 第一行一个整数n,表示n个骰子。(1 n 50) 第二行n个整数,表示每个骰子的结果数. ()

输出描述: 输出最终结果的期望,保留两位小数。

示例输入:

2
2 2

示例输出:

1.75

主要考察事件概率的计算。

假设有3个骰子,最大点数分别为2,3,4点。那么扔n个骰子,最终结果为1的概率如下

同理,最终结果为2, 也就是说三个骰子中最大的点数为2, 考虑每个骰子都点数都小于或等于2的概率,再减去每个骰子都小于或等于1的概率,即为

这就求得了最终结果的所有可能的值对应的概率,可以看出相加为1, 现在可以求期望了。

注意,当要求的点数大于当前骰子的最大点数时,那么该骰子掷出小于该点数的概率为1. 比如让一个最大点数为2的骰子掷出小于3的点数,显然概率为1.

#include <iostream>
#include <vector>
#include <algorithm>
 
using namespace std;
 
vector<double> Distribution(const vector<int>& die_ranges, int support)
{
  vector<double> probs;
  for (int i = 1; i <= support; ++i)
  {
    double pprob = 1.0; // for previous prob
    double prob = 1.0;  // for current prob
    for (auto e : die_ranges)
    {
      pprob *= min(double(i-1) / double(e), 1.0);
      prob *= min(double(i) / double(e), 1.0);
    }
    probs.push_back(prob - pprob);
  }
  return probs;
}
 
int main()
{
  int num_die;
  cin >> num_die;
 
  int maxpoint = 0;
  vector<int> die_ranges;
  for (int i = 0, tmp; i != num_die; ++i)
  {
    cin >> tmp;
    if (tmp > maxpoint)
      maxpoint = tmp;
    die_ranges.push_back(tmp);
  }
  // io done
  vector<double> probs = Distribution(die_ranges, maxpoint);
  
  double expectation = 0.0;
  for (int i = 1; i <= maxpoint; ++i)
  {
    expectation += (probs[i-1] * i);
  }
  printf("%.2f", expectation);
 
  return 0;
}

树相关

数据结构相关

大部分出自《剑指Offer》

栈和队列

反向打印链表

给定一个单链表,反向打印每个节点的值。

/*
 * 1. use stack
 * 2. use recursion
 */
 
#include <iostream>
#include <vector>
#include <stack>
 
using namespace std;
 
class ListNode {
public:
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};
 
class Solution {
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> ret;
        // boundary case
        if (head == nullptr) return ret;
 
        stack<int> s;
        for (auto p = head; p != nullptr; p = p -> next) {
            s.push(p -> val);
        }
 
        while (!s.empty()) {
            ret.push_back(s.top());
            s.pop();
        }
        return ret;
    }
 
    // recursive needs a member variable
    // use recursion stack, tricky
    vector<int> arr;
    vector<int> reversePrint(ListNode* head) {
        if (head) {
            reversePrint(head -> next);
            arr.push_back(head -> val);
        }
        return arr;
    }
    /*
     * Consider the closure, at one recursive step,
     * what I should do? Let's drop all the details,
     * just look one recursive step.
     * What had I done?
     *     Oh gee, I see if the head is not null,
     *     I must push the value to the vector,
     *     but before this, I should take a look at
     *     `head -> next`, since I have to push
     *     the tail first. So which one can help me
     *     do this? Yes, the function itself! Then
     *     after I have addressed the tail, now I'm
     *     going to push current value to the vector.
     *     That's all I need!
     * The key is you work in one recursive step, and
     * form a closure for the next, and do not forget
     * the base case (stopping rules). That how
     * recursion runs! And you are free of those
     * confusing details.
     */
};

栈和队列

模式匹配

求整数的二进制表示中“1”的个数

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

/*
 * `n` and `n-1` are the same from high bit position
 * to low, they differs from the last bit of 1 of n,
 * let's name it x. Have a look from x to the lowest 
 * bit position,
 * n:   010101000
 * &         x      <--- position x
 * n-1: 010100111
 * ---------------
 *      010100000   <--- n&(n-1)
 * It erases the last bit of 1 in n!!!
 *
 * This is why the following procedure will work. :)
 */
 
#include <iostream>
 
int NumOf1(int n) {
    int cnt = 0;
    while (n) {
        n = n & (n-1);
        ++cnt;
    }
    return cnt;
}
 
int main() {
    int N = 0;
    std::cin >> N;
    std::cout << NumOf1(N);
    return 0;
}

不可描述

求出113的整数中1出现的次数,并算出1001300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有 1、10、11、12、13 因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

/*
 * Credits: unknown
 *
 * Idea:
 *   1) Focus exactly one decimal position, calculate the
 *      number of ones.
 *   2) Run out from lowest to highest position, add them
 *      together, it's the answer.
 *
 *  Imaging the numbers from 1 to n sits in a line in
 *  front of you. Now you're required to calculates all
 *  the ones in the sequence. A basic way is that, each
 *  time I focus on the same digit pos, and count all
 *  ones in that pos. Next time I focus on another pos.
 *  And I sum them all together, finally got the answer.
 *
 *  See how can we count the num of ones in a specific
 *  decimal pos? Let's do that! Suppose N = 301563. 
 *
 *  Step 1.
 *  Now I focus the hundred position, and split N into `a`
 *  and `b`, where
 *
 *  a = ceil(N / 100) = 3015
 *  b = N % 100 = 63
 *
 *  1). There are (a / 10 + 1) = 302 hits of one
 *  2). Each of length 100
 *  3). Totally (a/10 + 1) * 100 hits of one
 *
 *  Let me explain a little:
 *  (000-301)5(00-99) -> (000-301)1(00-99)
 *  
 *  The digits above hundred pos have 302 variants, and
 *  the digits under hundred pos has 100 variants, thus
 *  gives a total (a/10 + 1) * 100.
 *
 *  Step 2.
 *  This time I focus on thousand pos, and now
 *
 *  a = N / 1,000 = 301
 *  b = N % 1,000 = 563
 *
 *  1). There are (a/10) = 30 hits of one
 *  2). Each of length 1000
 *  3). And a tail hits of 564
 *
 *  (00-29)1(000-999) + 301(000-563)
 *  This gives 30 * 1000 + 564 = (a/10)*1000+(b+1)
 *
 *  Step 3.
 *  Now move to ten thousand pos, with
 *
 *  a = N / 10,000 = 30
 *  b = N % 10,000 = 1563
 *
 *  1). There are total (a/10)=3 hit
 *  2). Each of length 10,000
 *
 *  (0-2)1(0000-9999)
 *  gives 3 * 10,000 = (a/10).
 *
 *  That's all 3 cases. Let's write!
 */
 
class Solution {
public:
    int NumOnes(int n) {
        int ones = 0;
        for (long long m = 1; m <= n; m *= 10) {
            ones +=
                /*
                 * this covers case 1 & 3
                 * since (a+8)/10 = a/10 + 1 if a%10 >= 2
                 * (a+8)/10 = a/10 if a%10 == 0
                 */
                (n/m + 8) / 10 * m
                +
                // case 2
                (n/m % 10 == 1) * (n%m + 1);
        }
        return ones;
    }
};

K轮换

原题:https://leetcode.com/problems/rotate-array/

Given an array, rotate the array to the right by k steps, where k is non-negative.

Example 1:

Input: [1,2,3,4,5,6,7] and k = 3
Output: [5,6,7,1,2,3,4]
Explanation:
rotate 1 steps to the right: [7,1,2,3,4,5,6]
rotate 2 steps to the right: [6,7,1,2,3,4,5]
rotate 3 steps to the right: [5,6,7,1,2,3,4]

Example 2:

Input: [-1,-100,3,99] and k = 2
Output: [3,99,-1,-100]
Explanation: 
rotate 1 steps to the right: [99,-1,-100,3]
rotate 2 steps to the right: [3,99,-1,-100]

Note:

  • Try to come up as many solutions as you can, there are at least 3 different ways to solve this problem.
  • Could you do it in-place with O(1) extra space?

提供三种方法:第一种,效率最低,因为vector从头插入元素开销很大;第二种,花费额外的空间,来降低时间开销;第三种,挺好的,std::reverse用的是首尾交换元素,无额外空间开销。

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
#if false   // method 1.
        if (k == 0) return;
        nums.insert(nums.begin(), nums.back());
        nums.pop_back();
        rotate(nums, k-1);
#elif false  // method 2.
        vector<int> dummy(nums);
        for (int i = 0; i != dummy.size(); ++i)
        {
            nums[(i+k) % nums.size()] = dummy[i];
        }
#elif true  // method 3.
        k %= nums.size();
        std::reverse(nums.begin(), nums.end());
        std::reverse(nums.begin(), nums.begin() + k);
        std::reverse(nums.begin() + k, nums.end());
#endif
    }
};

Move zeros

原题:https://leetcode.com/problems/move-zeroes/

Given an array nums, write a function to move all 0’s to the end of it while maintaining the relative order of the non-zero elements.

Example:

Input: [0,1,0,3,12]
Output: [1,3,12,0,0]

Note:

  • You must do this in-place without making a copy of the array.
  • Minimize the total number of operations.

思路:记录数字0出现的个数count。如果当前数字是0,给count加一;如果不是0,将这个值往前挪count位。最后将最后count个元素置0.

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        if (nums.size() < 2) return;
        
        // c.f. https://leetcode.com/explore/interview/card/top-interview-questions-easy/92/array/727/
        int numzeros = 0;
        for (int i = 0; i != nums.size(); ++i)
        {
            if (nums[i] == 0)
                ++numzeros;
            else
                nums[i-numzeros] = nums[i];
        }
        // set the tails to 0
        while (numzeros)
        {
            nums[nums.size() - numzeros] = 0;
            --numzeros;
        }
    }
};

Kth Largest Element in an Array

From: LeetCode125.

Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.

Example 1:

Input: [3,2,1,5,6,4] and k = 2
Output: 5

Example 2:

Input: [3,2,3,1,2,4,5,5,6] and k = 4
Output: 4

Note: You may assume k is always valid, 1 ≤ k ≤ array’s length.

思路:利用快排的partition思想,每次选取一个pivot,将数组分为小于pivot和大于pivot的两部分。此时pivot的index就是排好序之后的index,与k相比,如果出较小,则在后半部分(大于pivot)再次划分,如果较大,则在前半部分划分。直到划分出来的pivot的index等于k。还要注意的是,这样找出来的是第k小,用数组长度减一下,才是第k大,注意index的变换。

#include <iostream>
#include <vector>
using namespace std;
 
class Solution {
public:
  int findKthLargest(vector<int>& nums, int k) {
    // from 王小康:快排partition知道吧?
    // 就一刀一刀地劈开,劈一次你知道pivot的index,
    // 如果比k小,继续在右边劈,如果比k大,就在左边劈!
    
    if (nums.empty()) throw std::invalid_argument("empty arr");
    if (nums.size() == 1) return nums.front();
    // else @nums has >= 2 elems
    int mid = Partition(nums, 0, nums.size());
    k = nums.size() - k; // kth large = len+1-k small
    while (mid != k)
    {
        if (mid < k)
            mid = Partition(nums, mid + 1, nums.size());
        else
            mid = Partition(nums, 0, mid);
    }
    return nums[mid];
  }
  
  size_t Partition(vector<int>& arr, size_t beg, size_t end)
  {
    size_t pivot = beg;
    size_t i = pivot + 1;
    for (auto j = pivot + 1; j < end; ++j)
    {
      if (arr[j] < arr[pivot])
      {
        std::swap(arr[j], arr[i]);
        ++i;
      }
    }
    std::swap(arr[i-1], arr[pivot]);
    return i - 1;
  }
};
 
int main()
{
  vector<int> nums = {3,2,1,5,6,4};
  Solution so;
  cout << so.findKthLargest(nums, 2);
  return 0;
}

最大连续子列

From: LeetCode53.

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

设置两个变量,一个记录当前连加的值,另一个记录目前位置最大连续子序列,迭代更新。

int MaxSubArray(const vector<int>& arr)
{
  int max_so_far = INT_MIN;
  int sum = 0;
  for (int e : arr)
  {
    if (sum > 0)
      sum += e;
    else
      sum = e;
 
    max_so_far = max(max_so_far, sum);
  }
  return max_so_far;
}