微软笔试算法(一)

最近求职中接到的好多外包公司的电话,一般我都是拒绝的,其中告知外包到微软的就有三家,有一家说是微软小冰项目,给了我八道算法题,都不是太难,有的在LeetCode上还做过,在这里记录一下,正好也整理下LeetCode上刷过的题。

这里记录的算法是只用python实现,也是我目前能想到的最优解,一共八个问题。

  1. 完成一个函数:def Add(num1, num2):其中,两个输入都是数字,都是字符串(如:“12345678986543210123456789”),要求计算两个数字的和,返回一个字符串,不能使用内置函数,如int,long等。例如,输入两个数字是:“1000000000000000”和“-1”,返回“999999999999999”。

这个问题比较简单了,主要就是三个小问题:

  1. 字符串和数字的互转
  2. 加法的进位
  3. 减法的借位

字符串和数字的互转不让用int函数,这个简单,python只要写个字典就解决了,C的话就要写switch case来一个一个比较了。这里就解决了字符转数字的问题了。

到的数字开始计算,因为要设计借位和进位,答案会有四种情况

  1. 正数 + 正数 = 正数 2 + 3 = 5
  2. 负数 + 负数 = 负数 -2 + -3 = -5
  3. 大正数 + 负数 = 正数 -2 + 3 = 1
  4. 正数 + 大负数 = 负数 2 + -3 = -1

貌似就是这样,但是观察结果除了负号就是两种,所以程序一开始应该先分析负号,进位的情况有1,2,借位情况是3,4,正好这两个结果除了负号答案一样,所以分两类就OK

# 问题 1
def question1_add(num1, num2):
    # 创建个字典用来把字符转为int便于计算
    def strtoint(str):
        numdict = {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9}
        return numdict.get(str)
    
    # 把列表中的数字转会字符串 
    def inttostr(lt):
        ls2 = [str(i) for i in lt]
        return ''.join(ls2)

    lt, x, y = [], [], []
    
    # 两个数是否为负数的标记
    minusflag1, minusflag2 = 0, 0
    
    # 首先先提取出负号,记录负号的情况 
    if num1[0] is "-":
        x = list(map(strtoint, num1[1:]))
        minusflag1 = 1
    else:
        x = list(map(strtoint, num1))

    if num2[0] is "-":
        y = list(map(strtoint, num2[1:]))
        minusflag2 = 1
    else:
        y = list(map(strtoint, num2))

    # 把 x作为最长的那列,y是短的那列
    if len(x) < len(y):
        x, y = y, x

    maxlen, minlen = len(x), len(y)
    
    # 在短的那列前面插上0让两列一样齐以便于计算
    for i in range(maxlen-minlen):
        y.insert(0, 0)

    # 计算通常是从最低位开始,所以i从最低位开始
    # carry记录借位和进位情况
    i, carry = 0, 0

    # 两个数都是正数或负数
    if (minusflag1 == 0 and minusflag2 == 0) or (minusflag1 == 1 and minusflag2 == 1):
        # +1是为了防止最高位有进位的情况,给最高位加一个0
        for j in range(maxlen+1):
            i -= 1
            if j < maxlen:
                sum = x[i] + y[i] + carry
            else:
                sum = carry
            # 两个数相加大于十代表有进位, 把结果和10的商给新列表,余给进位标志   
            if sum >= 10:
                lt.insert(0, sum%10)
                carry = sum//10
            else:
                lt.insert(0, sum)
                carry = 0
        # 如果最高位没有进位 把0删除       
        if lt[0] == 0:
            lt.pop(0)

        # 如果都为负数 加 -
        if minusflag1 == 1 and minusflag2 == 1:
            lt.insert(0, "-")
            return inttostr(lt)
        else:
            return inttostr(lt)

    # 一正一负
    if (minusflag1 == 1 and minusflag2 == 0) or (minusflag1 == 0 and minusflag2 == 1):
        for j in range(maxlen):
            i -= 1
            sum = x[i] - y[i] + carry
            if sum < 0:
                lt.insert(0, sum%10)
                carry = sum//10
            else:
                lt.insert(0, sum)
                carry = 0

        while lt[0] == 0:
            lt.pop(0)
            if len(lt) == 1:
                break

        # 判断是否应该加 - 号
        if (num1[0] is "-" and len(num1[1:]) > len(num2)) or num2[0] is "-" and len(num2[1:]) > len(num1) :
            lt.insert(0, "-")
            return inttostr(lt)
        return inttostr(lt)
  1. 给定一个数组nums,然后对其排序,使得排序结果满足nums[0] < nums[1] > nums[2] < nums[3]…。 例如给定数组nums=[1,2,3,4,5,6,7,8,9],其中一个满足条件的结果是1<6>2<7>3<8>4<9>5.给出一个结果即可(可能无解)。最优解法是O(n)时间复杂度和O(1)空间复杂度。

原题位置:https://leetcode.com/problems/wiggle-sort-ii/

这道题就比较有趣了,LeetCode上大神给出的算法挺多,但是最优解不是O(n),用python实现的话还能比O(n)小。

先说我看到题第一眼的想法和看了LeetCode上大神的方法吧,很简单就三行

    nums.sort()
    median = len2] - 1
    nums[::2], nums[1::2] = nums[median::-1], nums[:median:-1]

首先排序,找到中位数,然后把中位数左右两边的交叉相排就OK了。

好吧,详细一点吧,S代表比中位数小的,L代表比中位数大的,M代表中位数

Small half:  M . S . S . S      Small half:  M . S . S . S .
Large half:  . L . L . M .      Large half:  . L . L . L . M
--------------------------      --------------------------
Together:    M L S L S M S      Together:    M L S L S L S M

根据这个原理还可以写成其他形式

    nums.sort()
    median = len2]
    nums[1::2], nums[::2] = nums[median:],nums[:median]

这个方式简单但是不满足O(n)时间复杂度的要求,这个方法是LeetCode上大神想出的叫virtual indexing,地址https://leetcode.com/problems/wiggle-sort-ii/discuss/77677/O(n)+O(1)-after-median-Virtual-Indexing,我就是照着用python抄袭了一遍o( ̄︶ ̄)o,顺便说下我写的这个稍有区别,时间复杂度比O(n)小一点。

解释一下site()函数的作用

为了防止median的元素挨在一起,也就是说奇数位置上的值是median,同时与他相邻的某个偶数位置上的值也是median导致排序失败

为了避免这个问题,可以采用一种方法,即另j 每次移动两步,也就是说先另j直线奇数位置,再另j指向偶数位置,所以对于大小为10的序列,j的变化可能像是这样

1 -> 3 -> 5 -> 7 -> 9 -> 0 -> 2 -> 4 -> 6 -> 8

暂且先不考虑怎么实现这样的改变,先说一下这样做带来的好处

由上面j的变化可知,j的改变是每次移动两步,所以,根据算法描述。所有和median相等的元素一定是最后才固定位置,又因为当j指向的值等于median时,是不与i和k指向的任何一个元素交换的。所以,每次移动两步带来的结果是这些median永远不可能相邻。换句话说就是永远不会出现两个median挨着的情况

def question2_sort2(nums):
    def site(n):
        return (1 + 2 * n) % (len(nums) | 1)

    nums.sort()
    if len(nums) % 2 == 0:
        median = (nums[len(nums) // 2] + nums[len(nums) // 2 - 1]) / 2
    else:
        median = nums[len(nums) // 2]
    i, j, k = 0, 0, len(nums) - 1
    while j <= k:
        if nums[site(j)] > median:
            nums[site(i)], nums[site(j)] = nums[site(j)], nums[site(i)]
            i += 1
            j += 1
        elif nums[site(j)] < median:
            nums[site(j)], nums[site(k)] = nums[site(k)], nums[site(j)]
            k -= 1
            j += 1 # 没错就是这里,我认为执行过交换后j就在正确的位置上了,所以我让j+1结果就是循环次数减少了,如果进行j+1则会循环N次,两种的结果不同但都复合条件
        else:
            j += 1
    return nums
  1. 写一个函数,输入是两个int数组A和B。要求从A和B中分别取出一个数,使他们的和为20。打印出所有的组合。要求数字在数组中的位置和数字本身。比如输入为 A = [18, 2, 7, 8, 3], B = [17, 1, 19],输出为 3 (A4) + 17 (B0) = 20,表示A的第4个元素是3,B的第0个元素是17

这就是道送分题了,嵌套循环就OK了,没什么好说的。

def question3_take(a, b):
    for i in range(len(a)):
        for j in range(len(b)):
            if a[i] + b[j] == 20:
                print("%d (A%d) + %d (B%d) = 20" % (a[i], i, b[j], j))
                return
    print(" No solution!")
  1. 写一个函数,输入一个随机的01序列,打印出这个序列中最长的01交替出现的序列的起始位置和结束位置。例如:输入“000101010101101”,输出起始位置2, 结束位置10

这题让我纠结了下,因为需要记录的位置有点多

因为我们要遍历这个序列中所有的01所以要两个指针同时移动才能确保01的出现,当出现01我们计数器n +1,然后判断是否到结尾或者下一个不是01的情况,我们就结束这段,记录这段的长度和结束开始的位置,如果下一段出现的01长我们就替换掉上次的记录。

值得注意的是01可能出现在序列的奇数或偶数位上,所以要遍历两次,再比较奇偶上哪个长,返回长的那段。

def func(i, j, num):
    start, end, n, count = 0, 0, 0, 0
    while j < len(num):
        if num[i] is '0' and num[j] is '1':
            n += 1
        if j+1 >= len(num) - 1 or num[i+2] is '1' or num[j+2] is '0':
            if n > count:
                count = n
                n = 0
                end = j + 1
                start = end - count*2
        i += 2
        j += 2
    return start, end


def question4(num):
    i, j = 0, 1
    start1, end1 = func(i, j, num)
    i, j = 1, 2
    start2, end2 = func(i, j, num)
    if end1 - start1 > end2- start2:
        return start1, end1
    else:
        return start2, end2