python面试题六: 剑指offer

面试题3 二维数组中的查找 LeetCode
题目:二维数组中,每行从左到右递增,每列从上到下递增,给出一个数,判断它是否在数组中
思路:从左下角或者右上角开始比较

def find_integer(matrix, num):
    """
    :param matrix: [[]]
    :param num: int
    :return: bool
    """
    if not matrix:
        return False
    rows, cols = len(matrix), len(matrix[0])
    row, col = rows - 1, 0
    while row >= 0 and col <= cols - 1:
        if matrix[row][col] == num:
            return True
        elif matrix[row][col] > num:
            row -= 1
        else:
            col += 1
    return False

面试题4 替换空格
题目:把字符串中的空格替换成'20%'
方法1:直接使用Python字符串的内置函数

' a  b  '.replace(' ', '20%')

面试题5 从尾到头打印单链表
方法1:使用栈,可以使用列表模拟

def print_links(links):
    stack = []
    while links:
        stack.append(links.val)
        links = links.next
    while stack:
        print stack.pop()

面试题6 重建二叉树 LeetCode
要求:用前序和中序遍历结果构建二叉树,遍历结果中不包含重复值

思路:前序的第一个元素是根结点的值,在中序中找到该值,中序中该值的左边的元素是根结点的左子树,右边是右子树,然后递归的处理左边和右边

提示:二叉树结点,以及对二叉树的各种操作,测试代码见six.py

def construct_tree(preorder=None, inorder=None):
    """
    构建二叉树
    """
    if not preorder or not inorder:
        return None
    index = inorder.index(preorder[0])
    left = inorder[0:index]
    right = inorder[index+1:]
    root = TreeNode(preorder[0])
    root.left = construct_tree(preorder[1:1+len(left)], left)
    root.right = construct_tree(preorder[-len(right):], right)
    return root


面试题7 用两个栈实现队列
要求:用两个栈实现队列,分别实现入队和出队操作 思路:一个栈负责入队,另一个负责出队,出栈为空则从入栈中导入到出栈中

class MyQueue(object):
    def __init__(self):
        self.stack = []
        self.stack2 = []

    def push(self, val):
        self.stack.append(val)

    def pop(self):
        if self.stack2:
            return self.stack2.pop()
        while self.stack:
            self.stack2.append(self.stack.pop())
        return self.stack2.pop() if self.stack2 else u'队列为空'

面试题8 旋转数组的最小数字
要求:把递增数组的前面部分数字移到队尾,求数组中的最小值,例如[3,4,5,6,1,2]

思路:使用二分法,但要考虑[1, 0, 0, 1]这种数据,只能顺序查找

def find_min(nums):
    if not nums:
        return False
    length = len(nums)
    left, right = 0, length - 1
    while nums[left] >= nums[right]:
        if right - left == 1:
            return nums[right]
        mid = (left + right) / 2
        if nums[left] == nums[mid] == nums[right]:
            return min(nums)
        if nums[left] <= nums[mid]:
            left = mid
        if nums[right] >= nums[mid]:
            right = mid
    return nums[0]

面试题9 斐波那契数列
思路:用生成器

def fib(num):
    a, b = 0, 1
    for i in xrange(num):
        yield b
        a, b = b, a + b

面试题10 二进制中1的个数
要求:求一个整数的二进制表示中,1的个数

思路:二进制表示中,最后的那个1被减去后,低位都变为0,高位不变,按位与就可以去掉这个1

def num_of_1(n):
    ret = 0
    while n:
        ret += 1
        n = n & n-1
    return ret

面试题11 数值的整数次方
要求:求一个数的整数次方

思路:需要考虑次方是正数、负数和0,基数是0

浮点数相等不能直接用==

# -*- coding:utf-8 -*-
class Solution:
    def Power(self, base, exponent):
        # write code here
        if base == 0:
            return 0
        if exponent == 0:
            return 1
        e = abs(exponent)
        res = 1
        while e>0:
            if (e & 1 == 1):
                res = res * base
            e = e>>1
            base = base * base
        return res if exponent>1 else 1/res

理解上述代码,要先了解什么是快速幂,传统的幂运算,是对底数进行连乘,时间复杂度为o(n),例如:2^13 = 2*2……*2,连乘十三次。利用指数的二进制,可以实现复杂度为o(logn)的幂运算。还是以2^13为例,13的二进制为1101,因此2的13次方可以分解成以下形式:

和13的二进制1101相对比,只要二进制为1的位,就有权重,权重为2^(i-1),i表示第几位,1101从右到左,依次为第1位,第2位,第3位,第4位。下面的工作就是如何确定二进制中的哪一位为1,这里可以利用位运算中的&和>>运算。由于1的二进制除了第一位是1,其他的全是0,因此可以利用n&1是否为0来判断n的二进制的当前最低位是否为1,如果n&1等于0,说明当前n的最低位不为1。利用右移运算来逐位读取。

 

 ---------------------------------------

在数学没有溢出的前提下,对于正数和负数,左移以为都相当于乘以2的1次方,左移n位

就相当于乘以2的n次方

右移一位相当于除以2,右移n位相当于除以2的n次方,这里取的是商,不要余数

-----------------------------------------------------------------------------------------

 

面试题12 打印1到最大的n位数
要求:输入n,打印出从1到最大的n位数

思路:Python中已经对大整数可以进行自动转换了,所以不需要考虑大整数溢出问题

 

 

def print_num(num):
    for i in range(1,10*num):
        print(i)

 

面试题13 O(1)时间删除链表结点
要求:O(1)时间删除链表结点

思路:如果有后续结点,后续结点的值前移,删除后续结点,如果没有,只能顺序查找了

def delete_node(link, node):
    if node == link:  # 只有一个结点
        del node
    if node.next is None:  # node是尾结点
        while link:
            if link.next == node:
                link.next = None
            link = link.next
    else:
        node.val = node.next.val
        n_node = node.next
        node.next = n_node.next
        del n_node

面试题14 调整数组顺序使奇数位于偶数前面
思路:使用两个指针,前后各一个,为了更好的扩展性,可以把判断奇偶部分抽取出来

def reorder(nums, func):
    left, right = 0, len(nums) - 1
    while left < right:
        while not func(nums[left]):
            left += 1
        while func(nums[right]):
            right -= 1
        if left < right:
            nums[left], nums[right] = nums[right], nums[left]


def is_even(num):
    return (num & 1) == 0

面试题15 链表中倒数第k个结点
要求:求单链表中的倒数第k个结点

思路:使用快慢指针,快的先走k-1步,需要考虑空链表以及k为0

def last_kth(link, k):
    if not link or k <= 0:
        return None
    move = link
    while move and k-1 >= 0:
        move = move.next
        k -= 1
    while move:
        move = move.next
        link = link.next
    if k == 0:
        return link.val
    return None

面试题16 反转链表
要求:反转链表

思路:需要考虑空链表,只有一个结点的链表

def reverse_link(head):
    if not head or not head.next:
        return head
    then = head.next
    head.next = None
    last = then.next
    while then:
        then.next = head
        head = then
        then = last
        if then:
            last = then.next
    return head

面试题17 合并两个排序的链表
要求:合并两个排序的链表

思路:使用递归

class Solution:
    def Merge(self, pHead1, pHead2):
        if not pHead1:
            return pHead2
        if not pHead2:
            return pHead1
        if pHead1.val <= pHead2.val:
            pHead1.next = self.Merge(pHead1.next, pHead2)
            return pHead1
        else:
            pHead2.next = self.Merge(pHead1, pHead2.next)
            return pHead2
 
    def getNewChart(self, list):
        if list:
            node = ListNode(list.pop(0))
            node.next = self.getNewChart(list)
            return node
 
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
 
if __name__ == '__main__':
    list1 = [1, 3, 5]
    list2 = [0, 1, 4]
    testList1 = Solution().getNewChart(list1)
    testList2 = Solution().getNewChart(list2)
    final = Solution().Merge(testList1, testList2)
    while final:
        print(final.val, end=" ")
        final = final.next

 

 

面试题18 树的子结构
要求:判断一棵二叉树是不是另一个的子结构

思路:使用递归

def sub_tree(tree1, tree2):
    if tree1 and tree2:
        if tree1.val == tree2.val:
            return sub_tree(tree1.left, tree2.left) and sub_tree(tree1.right, tree2.right)
        else:
            return sub_tree(tree1.left, tree2) or sub_tree(tree1.right, tree2)
    if not tree1 and tree2:
        return False
    return True

面试题19 二叉树的镜像
思路一:可以按层次遍历,每一层从右到左

思路二:使用递归

def mirror_bfs(root):
    ret = []
    queue = deque([root])
    while queue:
        node = queue.popleft()
        if node:
            ret.append(node.val)
            queue.append(node.right)
            queue.append(node.left)
    return ret


def mirror_pre(root):
    ret = []

    def traversal(root):
        if root:
            ret.append(root.val)
            traversal(root.right)
            traversal(root.left)
    traversal(root)
    return ret

面试题20 顺时针打印矩阵

def print_matrix(matrix):
    """
    :param matrix: [[]]
    """
    rows = len(matrix)
    cols = len(matrix[0]) if matrix else 0
    start = 0
    ret = []
    while start * 2 < rows and start * 2 < cols:
        print_circle(matrix, start, rows, cols, ret)
        start += 1
    print ret


def print_circle(matrix, start, rows, cols, ret):
    row = rows - start - 1  # 最后一行
    col = cols - start - 1
    # left->right
    for c in range(start, col+1):
        ret.append(matrix[start][c])
    # top->bottom
    if start < row:
        for r in range(start+1, row+1):
            ret.append(matrix[r][col])
    # right->left
    if start < row and start < col:
        for c in range(start, col)[::-1]:
            ret.append(matrix[row][c])
    # bottom->top
    if start < row and start < col:
        for r in range(start+1, row)[::-1]:
            ret.append(matrix[r][start])

面试题21 包含min函数的栈
要求:栈的push,pop,min操作的时间复杂度都是O(1)

思路:使用一个辅助栈保存最小值

class MyStack(object):

    def __init__(self):
        self.stack = []
        self.min = []

    def push(self, val):
        self.stack.append(val)
        if self.min and self.min[-1] < val:
            self.min.append(self.min[-1])
        else:
            self.min.append(val)

    def pop(self):
        if self.stack:
            self.min.pop()
            return self.stack.pop()
        return None

    def min(self):
        return self.min[-1] if self.min else None

面试题22 栈的压入弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

解题思路:

        借用一个辅助栈,遍历压栈顺序,先将第一个放入辅助栈中,这里是1,然后判断辅助栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,直到相等以后对辅助栈开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。

class Solution:
    def IsPopOrder(self, pushV, popV):
        # write code here
        stack=[]
        while popV:
            if stack and stack[-1] == popV[0]:
                stack.pop()
                popV.pop(0)
            elif pushV:
                stack.append(pushV.pop(0))
            else:
                return False
        return True

 

  

面试题23 从上往下打印二叉树
思路:广度优先搜索,按层次遍历

 def bfs(tree):
    if not tree:
        return None
    stack = [tree]
    ret = []
    while stack:
        node = stack.pop(0)
        ret.append(node.val)
        if node.left:
            stack.append(node.left)
        if node.right:
            stack.append(node.right)
    return ret

面试题24 二叉搜索树的后序遍历序列
要求:判断给定的整数数组是不是二叉搜索树的后序遍历序列

整数数组中不包含重复值

整数序列的最后一个值是根结点,然后比根结点小的值是左子树,剩下的是右子树,递归左右子树

首先要清楚,这道题不是让你去判断一个给定的数组是不是一个(原先)给定的二叉搜索树的对应后序遍历的结果,而是判断一个给定的数组是不是能够对应到一个具体的二叉搜索树的后序遍历结果

所以还是用递归的思想。

把数组分成三部分,比如[4,8,6,12,16,14,10],10就是根节点,4,8,6都是左子树,12,16,14,10都是右子树,然后针对左右子树再去判断是不是符合根节点、左右子树这一个规律(左子树都比根节点小,右子树都比根节点大)

class Solution:
    def VerifySquenceOfBST(self,sequence):
        # write code here
        if len(sequence)==0:
            return False
        index = 0
        for i in range(len(sequence)):
            if sequence[i]>sequence[-1]:
                index = i
                break
        for j in range(index,len(sequence)):
            if sequence[j]<sequence[-1]:
                return False
        left = True
        right = True
        if len(sequence[:index])>0:
            left = self.VerifySquenceOfBST(sequence[:index])
        if len(sequence[index:-1])>0:
            right = self.VerifySquenceOfBST(sequence[index:-1])
        return left and right

面试题25 二叉树中和为某一值的路径
要求:输入一棵二叉树和一个值,求从根结点到叶结点的和等于该值的路径

深度优先搜索变形

– 注意到路径必须为跟节点 –> 没有左右子树的叶子节点;定义两个数组pathArray、onePath,pathArray用于存储所有符合条件的路径,onePath用于存储当前遍历的路径;
– 类似于深度优先搜索,每遍历到一个节点,就将其加入到onePath中,并判定是否符合条件:
1. 为叶节点且和等于要求的整数,则将该数组存储至pathArray中,并换其他路径继续搜寻;
2. 和小于要求的整数,则向当前节点的左右子树依次深度优先搜索;
3. 和大于要求的整数,则直接换路搜索。

class Solution:
    def __init__(self):
        self.onePath = []
        self.PathArray = []
    def FindPath(self, root, expectNumber):
        if root is None:
            return self.PathArray
        self.onePath.append(root.val)
        expectNumber -= root.val
        if expectNumber==0 and not root.left and not root.right:
            self.PathArray.append(self.onePath[:])
        elif expectNumber>0:
            self.FindPath(root.left,expectNumber)
            self.FindPath(root.right,expectNumber)
        self.onePath.pop()
        return self.PathArray

 

面试题26 复杂链表的复制
要求:链表中除了指向后一个结点的指针之外,还有一个指针指向任意结点

分为三步完成:

一:复制每个结点,并把新结点放在老结点后面,如1->2,复制为1->1->2->2

二:为每个新结点设置other指针

三:把复制后的结点链表拆开

题目设置了复杂链表的实现,测试代码见文件twenth_six.py

class Solution(object):

    @staticmethod
    def clone_nodes(head):
        # 结点复制
        move = head
        while move:
            tmp = Node(move.val)
            tmp.next = move.next
            move.next = tmp
            move = tmp.next
        return head

    @staticmethod
    def set_nodes(head):
        # other指针设置
        move = head
        while move:
            m_next = move.next
            if move.other:
                m_next.other = move.other.next
            move = m_next.next
        return head

    @staticmethod
    def reconstruct_nodes(head):
        # 结点拆分
        ret = head.next if head else Node
        move = ret
        while head:
            head = move.next
            if head:
                move.next = head.next
                move = move.next
        return ret

    @staticmethod
    def clone_link(head):
        # 结果
        h = Solution.clone_nodes(head)
        h = Solution.set_nodes(h)
        ret = Solution.reconstruct_nodes(h)
        return ret


面试题27 二叉搜索树与双向链表
要求: 将二叉搜索树转化成一个排序的双向链表,只调整树中结点的指向

思路: 中序遍历,根结点的left指向左子树的最后一个(最大)值,right指向右子树的(最小)值

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。如图:

 

 

  1. 核心算法依旧是中序遍历
  2. 不是从根节点开始,而是从中序遍历得到的第一个节点开始
  3. 定义两个辅助节点listHead(链表头节点)、listTail(链表尾节点)。事实上,二叉树只是换了种形式的链表;listHead用于记录链表的头节点,用于最后算法的返回;listTail用于定位当前需要更改指向的节点。了解了listHead和listTail的作用,代码理解起来至少顺畅80%。
  4. 提供我画的算法的过程图,有点丑,但有助于理解(帮你们画了,你们就不用画啦),另外图中右上角步骤三应该是“2”标红,“2”和“1”中间的连接为单线黑~~~ 

 

class Solution:
    def __init__(self):
        self.listHead = None
        self.listTail = None
 
    # 将二叉树转换为有序双向链表
    def Convert(self, pRootOfTree):
        if pRootOfTree==None:
            return
        self.Convert(pRootOfTree.left)
        if self.listHead==None:
            self.listHead = pRootOfTree
            self.listTail = pRootOfTree
        else:
            self.listTail.right = pRootOfTree
            pRootOfTree.left = self.listTail
            self.listTail = pRootOfTree
        self.Convert(pRootOfTree.right)
        return self.listHead
 
    # 获得链表的正向序和反向序
    def printList(self, head):
        while head.right:
            print(head.val, end=" ")
            head = head.right
        print(head.val)
        while head:
            print(head.val, end= " ")
            head = head.left
 
 
    # 给定二叉树的前序遍历和中序遍历,获得该二叉树
    def getBSTwithPreTin(self, pre, tin):
        if len(pre)==0 | len(tin)==0:
            return None
        root = TreeNode(pre[0])
        for order,item in enumerate(tin):
            if root .val == item:
                root.left = self.getBSTwithPreTin(pre[1:order+1], tin[:order])
                root.right = self.getBSTwithPreTin(pre[order+1:], tin[order+1:])
                return root
class TreeNode:
    def __init__(self, x):
        self.left = None
        self.right = None
        self.val = x
if __name__ == '__main__':
    solution = Solution()
    preorder_seq = [4,2,1,3,6,5,7]
    middleorder_seq = [1,2,3,4,5,6,7]
    treeRoot1 = solution.getBSTwithPreTin(preorder_seq, middleorder_seq)
    head = solution.Convert(treeRoot1)
    solution.printList(head)

 

 

面试题28 字符串的排列
要求:求输入字符串的全排列

思路:递归完成,也可以直接使用库函数

以字符串 abc 为例介绍对字符串进行全排列的方法。

例如输入字符串 abc,要求输出由字母 a、b、c 所能排列出来的所有字符串 abc,acb,bac,bca,cab,cba。
(1) 首先固定第一个字符 a,然后对后面的两个字符 b、c 进行全排列;
(2) 交换第一个字符与其后面的字符,即交换 a 与 b,然后对后面的两个字符 a与c 进行全排列;
(3) 由于第二步交换了 a与b 破坏了字符串原来的顺序,所以需要再次交换 a与b 使其恢复到原来的顺序,然后交换第一个字符与第三个字符(交换a和c),接着固定第一个字符c,对后面的两个字符 a与b 求全排列。
在对字符串求全排列的时候就可以采用递归的方式求解。

 

 

 

全排列的主要思路为:从第一个字符起每个字符分别与它们后面的字符进行交换:例如对于“baa”,交换 baa 第一个与第二个字符后得到“aba”,再考虑交换 baa 第一个与第三个字符后得到“aab”,由于 baa 的第二个字符与第三个字符相等,所以会导致两种交换方式得到的 aba 与 aab 对应的全排列是重复的(在固定aba与aab的第一个字符的情况下,它们对应的全排列都为 aba,aab)。
所以可以知道,去掉重复排列的主要思路为:
从第一个字符起,每个字符分别与它后面的非重复出现的字符进行交换。在递归的基础上只需要增加一个判断字符是否重复出现的函数即可。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time  : 2020/2/3 10:37
# @Author : buu
# @Software: PyCharm
# @Blog  :https://blog.csdn.net/weixin_44321080
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time  : 2020/2/3 9:49
# @Author : buu
# @Software: PyCharm
# @Blog  :https://blog.csdn.net/weixin_44321080
def swap(str, i, j):
  # 交换字符数组下标为i和j对应的字符
  tmp = str[i]
  str[i] = str[j]
  str[j] = tmp
 
def isDuplicate(str,begin,end):
  """
  判断 [begin,end)区间中是否有字符与 *end相等
  :param begin: 指向字符的指针
  :param end: 指向字符的指针
  :return: 如果有相等的字符True,else False
  """
  i=begin
  while i<end:
    if str[i]==str[end]:
      return True
    else:
      i+=1
  return False
 
 
def permutation(str, start):
  """
  对字符串中的字符进行全排列
  :param str: 待排序的字符串,list
  :param start: 待排序的子字符串的首字符下标
  :return:
  """
  if str == None or start < 0:
    return
  if start == len(str) - 1:
    # 完成全排列后输出当前排列的字符串
    print(''.join(str),end=' ')
  else:
    i = start
    while i < len(str):
      # 若有重复字符,则终止当前循环,开始下一次循环
      if isDuplicate(str,start,i):
        i+=1
        continue
      # 交换start与i所在位置的字符
      swap(str, start, i)
      # 固定第一个字符,对剩余的字符进行全排列
      permutation(str, start + 1)
      # 还原start与i所在位置的字符
      swap(str, start, i)
      i += 1
 
 
def permutation_transe(s):
  str = list(s)
  permutation(str, 0)
 
 
if __name__ == '__main__':
  s = 'baa'
  permutation_transe(s)

#方法2
def my_permutation(s):
    str_set = []
    ret = []  # 最后的结果

    def permutation(string):
        for i in string:
            str_tem = string.replace(i, '')
            str_set.append(i)
            if len(str_tem) > 0:
                permutation(str_tem)
            else:
                ret.append(''.join(str_set))
            str_set.pop()

    permutation(s)
    return ret

 

 

面试题29 数组中出现次数超过一半的数字
思路: 使用hash,key是数字,value是出现的次数

注意: 列表的len方法的时间复杂度是O(1)

def get_more_half_num(nums):
    hashes = dict()
    length = len(nums)
    for n in nums:
        hashes[n] = hashes[n] + 1 if hashes.get(n) else 1
        if hashes[n] > length / 2:
            return n

面试题30 最小的k个数
要求:求数组中出现次数超过一半的数字

由于不要求最小的k个数按序输出,参考快排中partition函数思想:每轮排序之后枢轴左边数字都比它小,右边数字都比它大。因此如果排序之后枢轴刚好处于数组的第k个位置,那么此时数组的前k个数字也即是最小的k个数

这种思想经证明是O(n)的时间复杂度

class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        n = len(tinput)
        if k <= 0 or k > n:
            return list()
        start = 0
        end = n - 1
        mid = self.partition(tinput, start, end)
        while k - 1 != mid:
            if k - 1 > mid:
                start = mid + 1
                mid = self.partition(tinput, start, end)
            elif k - 1 < mid:
                end = mid - 1
                mid = self.partition(tinput, start, end)
        res = tinput[:mid+1]
        # res.sort()
        return res
        
    def partition(self, numbers, low, high):
        key = numbers[low]
        while low < high:
            while low < high and numbers[high] >= key:
                high -= 1
            numbers[low] = numbers[high]
            while low < high and numbers[low] <= key:
                low += 1
            numbers[high] = numbers[low]
        numbers[low] = key
        return low

 

思路二: 数组比较小的时候可以直接使用heapq的nsmallest方法

import heapq


def get_least_k_nums(nums, k):
    # 数组比较小的时候可以直接使用
    return heapq.nsmallest(k, nums)


class MaxHeap(object):
    def __init__(self, k):
        self.k = k
        self.data = []

    def push(self, elem):
        elem = -elem  # 入堆的时候取反,堆顶就是最大值的相反数了
        if len(self.data) < self.k:
            heapq.heappush(self.data, elem)
        else:
            least = self.data[0]
            if elem > least:
                heapq.heapreplace(self.data, elem)

    def get_least_k_nums(self):
        return sorted([-x for x in self.data])

面试题31 连续子数组的最大和
思路: 动态规划问题

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)

思路:
最大和连续子数组一定有如下几个特点:
1、第一个不为负数
2、如果前面数的累加值加上当前数后的值会比当前数小,说明累计值对整体和是有害的;如果前面数的累加值加上当前数后的值比当前数大或者等于,则说明累计值对整体和是有益的。
步骤:
1、定义两个变量,一个用来存储之前的累加值,一个用来存储当前的最大和。遍历数组中的每个元素,假设遍历到第i个数时:
①如果前面的累加值为负数或者等于0,那对累加值清0重新累加,把当前的第i个数的值赋给累加值。
②如果前面的累加值为整数,那么继续累加,即之前的累加值加上当前第i个数的值作为新的累加值。
2、判断累加值是否大于最大值:如果大于最大值,则最大和更新;否则,继续保留之前的最大和

def max_sum(nums):
    ret = float("-inf")  # 负无穷
    if not nums:
        return ret
    current = 0
    for i in nums:
        if current <= 0:
            current = i
        else:
            current += i
        ret = max(ret, current)
    return ret

面试题32 从1到n整数中1出现的次数

输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中包含1的数字有1、10、11和12,1一共出现了5次。

       对于书中说的不考虑时间效率的解法很好理解,可以直接完成,但是对于书中介绍的另一种方法,没有理解,于是按照自己的思路进行了分析。

      1位数,1-9中,1一共出现了1次;

      2位数,10-99中,10-19的十位上一共出现了10*1=10次,对于每个十位开头的数字10-19、20-29,每个数个位上出现的是1-9中1出现的次数,共有9个区间9*1=9次;

      3位数,100-999,100-199百位上出现了10**2=100次,对于每个百位数开头,例如100-199,200-299,低位上其实就是0-99这个区间上1出现的次数,一共9个区间 9*19=171次;

      由此推测,对于1-9,10-99,100-999,每个n位数中包含1的个数公式为:

      f(1) = 1

      f(2) = 9 * f(1) + 10 ** 1

      f(3) = 9 * f(2) + 10 ** 2

      f(n) = 9 * f(n-1) + 10 ** (n-1)

      通过以上分析,我们可以确定对于任意一个给定的数,例如23456这个5位数,10000之前的数中包含的个数是确定的了,为f(1)+f(2)+f(3)+f(4),这是一个递归的过程,对此可以求出1-4位中包含1的总数,函数如下所示:

def get_1_digits(n):
    """
    获取每个位数之间1的总数
    :param n: 位数
    """
    if n <= 0:
        return 0
    if n == 1:
        return 1
    current = 9 * get_1_digits(n-1) + 10 ** (n-1)
    return get_1_digits(n-1) + current

 

   通过上面的分析,我们知道了23456中,1-10000之间一共出现了多少个1.下一步需要分析10000-23456中包含的1.

     我们首先把最高位单独拿出来分析一下,求出最高位上1的个数,如果最高位是1,则最高位上一共会出现的1的次数是低位上数字+1,例如12345,最高位上一共出现了2346个1;如果最高位大于1,则会一共出现的次数是10000-19999一共10**4个数。

     然后,根据最高位的不同,计算出该高位前面的相同位数范围中的所有数中1的个数。例如对于34567,需要计算出10000-19999,20000-29999中一的个数,这时候计算一的个数,也就是计算0-9999中1的个数,这就可以转化成上面的f(n)来计算了,调用上面函数可以直接得到,然后用得到的值和最高位和1的差值(这里最高位是3)相乘就可以了。

    分析完上面的部分后,我们现在只剩下最高位后面的部分了,我们发现剩下的部分还是一个整数,例如23456剩下了3456,这时候直接使用递归处理剩下的3456就行了。具体代码如下:

def get_1_nums(n):
    if n < 10:
        return 1 if n >= 1 else 0
    digit = get_digits(n)  # 位数
    low_nums = get_1_digits(digit-1)  # 最高位之前的1的个数
    high = int(str(n)[0])  # 最高位
    low = n - high * 10 ** (digit-1)  # 低位

    if high == 1:
        high_nums = low + 1  # 最高位上1的个数
        all_nums = high_nums
    else:
        high_nums = 10 ** (digit - 1)
        all_nums = high_nums + low_nums * (high - 1)  # 最高位大于1的话,统计每个多位数后面包含的1
    return low_nums + all_nums + get_1_nums(low)

 

        对于上面使用的get_digits函数,是用来求给定的n是几位数的。代码如下:

def get_digits(n):
    # 求整数n的位数
    ret = 0
    while n:
        ret += 1
        n /= 10
    return ret

面试题33 把数组排成最小的数
大致思路
(1)我们可以先思考只有两个数字的情况: [3,32],可以看出来 332>323 因此需要把数组改变为 [32,3] ;
(2)对于有三个数字的情况: [3,32,321]我们两两进行比较, 332>323 于是,将 3 与 32 交换位置变成 [32,3,321] 而 3321>3213 于是将 3 与 321 继续交换位置到 [32,321,3]

          接着我们继续使用 32 进行比较,由于 32321>32132 将 32与321 进行位置交换为 [321,32,3]此时,将数组链接起来变成 321323即为最小的数。

具体思路

(1)先将数字列表转化成字符串链表,这样便于在一个字符串后面直接加上另外一个字符串。也就是 "3"+"321"="3321" 。
(2)构造一个比较函数,当 str1+str2>str2+str1 时我们认为字符串 str1>str2 。
(3)将字符串列表按照比较函数的规定进行冒泡排序(或其它方法排序),将定义为”大”的字符串放到最后。

          而”小”的字符串放在前面。最后将字符串列表链接起来,便是所求。

class Solution:
    def theMax(self, str1, str2):
        '''定义字符串比较函数'''
        return str1 if str1+str2 > str2+str1 else str2

    def PrintMinNumber(self, numbers):
        """使用冒泡进行排序(把最大的放最后)"""
        string = [str(num) for num in numbers]
        res = []
        flag = True
        count = len(string) - 1
        while flag and  count > 0:
            flag = False
            for i in range(len(string)-1):
                if self.theMax(string[i], string[i+1]) == string[i]:
                    temp = string[i]
                    del string[i]
                    string.insert(i+1, temp)
                    flag = True
            count -= 1
        string = ''.join(string)
        return string

面试题34 丑数 LeetCode
要求:只含有2、3、5因子的数是丑数,求第1500个丑数

思路: 按顺序保存已知的丑数,下一个是已知丑数中某三个数乘以2,3,5中的最小值

因为丑数只包含质因子2,3,5,假设我们已经有n-1个丑数,按照顺序排列,且第n-1的丑数为M。那么第n个丑数一定是由这n-1个丑数分别乘以2,3,5,得到的所有大于M的结果中,最小的那个数。

事实上我们不需要每次都计算前面所有丑数乘以2,3,5的结果,然后再比较大小。因为在已存在的丑数中,一定存在某个数T2,在它之前的所有数乘以2都小于已有丑数,而T2×2的结果一定大于M,同理,也存在这样的数T3,T5,我们只需要标记这三个数即可。

# -*- coding:utf-8 -*-
class Solution:
    def GetUglyNumber_Solution(self, index):
        # write code here
        if index == 0:
            return 0
        # 1作为特殊数直接保存
        baselist = [1]
        min2 = min3 = min5 = 0
        curnum = 1
        while curnum < index:
            minnum = min(baselist[min2] * 2, baselist[min3] * 3, baselist[min5] * 5)
            baselist.append(minnum)
            # 找到第一个乘以2的结果大于当前最大丑数M的数字,也就是T2
            while baselist[min2] * 2 <= minnum:
                min2 += 1
            # 找到第一个乘以3的结果大于当前最大丑数M的数字,也就是T3
            while baselist[min3] * 3 <= minnum:
                min3 += 1
            # 找到第一个乘以5的结果大于当前最大丑数M的数字,也就是T5
            while baselist[min5] * 5 <= minnum:
                min5 += 1
            curnum += 1
        return baselist[-1]

 

面试题35 第一个只出现一次的字符
要求:求字符串中第一个只出现一次的字符

思路: 使用两个hash,一个记录每个字符出现的次数,另一个记录每个字符第一次出现的位置

def first_not_repeating_char(string):
    if not string:
        return -1
    count = {}
    loc = {}
    for k, s in enumerate(string):
        count[s] = count[s] + 1 if count.get(s) else 1
        loc[s] = loc[s] if loc.get(s) else k
    ret = float('inf')
    for k in loc.keys():
        if count.get(k) == 1 and loc[k] < ret:
            ret = loc[k]
    return ret

面试题36 数组中的逆序对
要求:在一个数组中,前面的数字比后面的大,就是一个逆序对,求总数

思路: 归并排序,先把数组依次拆开,然后合并的时候统计逆序对数目,并排序

分治的思想,将数组不断一分为二,直到数组中只有两个元素,统计逆序对个数。

然后对相邻的两个子数组进行合并,由于已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组进行排序,避免在之后的统计过程中重复统计。

在合并的时候也要计算组间的逆序对个数。

逆序对的总数 = 左边数组中的逆序对的数量 + 右边数组中逆序对的数量 + 左右结合成新的顺序数组时中出现的逆序对的数量

归并排序的性能不受输入数据的影响,时间复杂度始终都是O(nlogn)。代价是需要额外的内存空间。

class Solution:
    def InversePairs(self, data):
        # write code here
        count, result = self.merge_sort(data)
        return count

    def merge_sort(self, a_list):
        n = len(a_list)
        count = 0
        if n <= 1:
            return count, a_list
        # 拆分
        count_l, left = self.merge_sort(a_list[:n // 2])
        count_r, right = self.merge_sort(a_list[n // 2:])
        # 合并排序
        count_merge, merge = self.merge(left, right)
        count = count_l + count_r + count_merge
        return count, merge

    def merge(self, left, right):
        count = 0
        l = r = 0
        result = []
        while l < len(left) and r < len(right):
            if left[l] <= right[r]:
                result.append(left[l])
                l += 1
            else:
                result.append(right[r])
                r += 1
                # 当右边的元素被插入时,证明这个元素比左边的剩下的所有元素都小
                # 可以组成len(left)-l个逆序对
                count += len(left) - l
        result += left[l:] + right[r:]
        
        return count, result

 

面试题37 两个链表的第一个公共结点
思路: 先获取到两个链表的长度,然后长的链表先走多的几步,之后一起遍历

文件thirty_seven.py中包含了设置链表公共结点的代码,可以用来测试

def get_first_common_node(link1, link2):
    if not link1 or not link2:
        return None
    length1 = length2 = 0
    move1, move2 = link1, link2
    while move1:  # 获取链表长度
        length1 += 1
        move1 = move1.next
    while move2:
        length2 += 1
        move2 = move2.next
    while length1 > length2:  # 长链表先走多的长度
        length1 -= 1
        link1 = link1.next
    while length2 > length1:
        length2 -= 1
        link2 = link2.next
    while link1:  # 链表一起走
        if link1 == link2:
            return link1
        link1, link2 = link1.next, link2.next
    return None

面试题38 数字在排序数组中出现的次数
思路: 使用二分法分别找到数组中第一个和最后一个出现的值的坐标,然后相减

利用二分查找法,二分查找法总是先拿数组中间的数和k作比较。如果中间的数字比k大,那么k只有可能出现在数组的前半段,下一轮只在数组的前半段查找即可。如果中间的数字比k小,那么k只有可能出现在数组的后半段,下一轮我们只在数组的后半段查找即可。如果中间数字和k相等,则看其前面一位的数字,如果中间的数字的前面一个数字不是k,则此时中间的数字刚好就是第一个k,如果中间数字的前面一个数字也是k,那么第一个k肯定在数组的前半段,下一轮仍需要在数组的前半段查找。
由上述过程可写出两个递归函数,分别求出所要查找的数字出现的第一位的索引和最后一位的索引
在函数get_first_k中,如果数组中不包含数字k,则返回-1。如果数组中包含至少一个k,则返回第一个k在数组中的下标。

同理,get_last_k函数也是,数组中不包含k则返回-1,数组中包含k就返回最后一个k在数组中的下标。

 

def get_k_counts(nums, k):
    first = get_first_k(nums, k)
    last = get_last_k(nums, k)
    if first < 0 and last < 0:
        return 0
    if first < 0 or last < 0:
        return 1
    return last - first + 1


def get_first_k(nums, k):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) / 2
        if nums[mid] < k:
            if mid + 1 < len(nums) and nums[mid+1] == k:
                return mid + 1
            left = mid + 1
        elif nums[mid] == k:
            if mid - 1 < 0 or (mid - 1 >= 0 and nums[mid-1] < k):
                return mid
            right = mid - 1
        else:
            right = mid - 1
    return -1


def get_last_k(nums, k):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) / 2
        if nums[mid] < k:
            left = mid + 1
        elif nums[mid] == k:
            if mid + 1 == len(nums) or (mid + 1 < len(nums) and nums[mid+1] > k):
                return mid
            left = mid + 1
        else:
            if mid - 1 >= 0 and nums[mid-1] == k:
                return mid - 1
            right = mid - 1
    return -1

面试题39 二叉树的深度
思路: 分别递归的求左右子树的深度

def get_depth(tree):
    if not tree:
        return 0
    if not tree.left and not tree.right:
        return 1
    return 1 + max(get_depth(tree.left), get_depth(tree.right))

面试题40 数组中只出现一次的数字
要求:数组中除了两个只出现一次的数字外,其他数字都出现了两遍

思路: 按位异或,在得到的值中找到二进制最后一个1,然后把数组按照该位是0还是1分为两组

def get_only_one_number(nums):
    if not nums:
        return None
    tmp_ret = 0
    for n in nums:  # 获取两个值的异或结果
        tmp_ret ^= n
    last_one = get_bin(tmp_ret)
    a_ret, b_ret = 0, 0
    for n in nums:
        if is_one(n, last_one):
            a_ret ^= n
        else:
            b_ret ^= n
    return [a_ret, b_ret]


def get_bin(num):  # 得到第一个1
    ret = 0
    while num & 1 == 0 and ret < 32:
        num = num >> 1
        ret += 1
    return ret


def is_one(num, t):  # 验证t位是不是1
    num = num >> t
    return num & 0x01
#方法2
def
FindNumsAppearOnce(self, array): if len(array) < 2: return None dict, res = {}, [] for num in array: dict[num] = 1 if num in dict else 0 for key in dict: if dict[key] == 0: res.append(key) return res

 

面试题41 和为s的两个数字VS和为s的连续正数序列
和为s的两个数字
要求:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使其和为s

思路: 设置头尾两个指针,和大于s,尾指针减小,否砸头指针增加

def sum_to_s(nums, s):
    head, end = 0, len(nums) - 1
    while head < end:
        if nums[head] + nums[end] == s:
            return [nums[head], nums[end]]
        elif nums[head] + nums[end] > s:
            end -= 1
        else:
            head += 1
    return None

和为s的连续整数序列
要求:输入一个正数s, 打印出所有和为s的正整数序列(至少两个数)

思路: 使用两个指针,和比s小,大指针后移,比s大,小指针后移

def sum_to_s(s):
    a, b = 1, 2
    ret = []
    while a < (s / 2 + 1):
        if sum(range(a, b+1)) == s:
            ret.append(range(a, b+1))
            a += 1
        elif sum(range(a, b+1)) < s:
            b += 1
        else:
            a += 1
    return ret

面试题42 翻转单词顺序与左旋转字符串
翻转单词顺序
要求:翻转一个英文句子中的单词顺序,标点和普通字符一样处理

思路: Python中字符串是不可变对象,不能用书中的方法,可以直接转化成列表然后转回去

def reverse_words(sentence):
    tmp = sentence.split()
    return ' '.join(tmp[::-1])  # 使用join效率更好,+每次都会创建新的字符串

左旋转字符串
思路: 把字符串的前面的若干位移到字符串的后面

def rotate_string(s, n):
    if not s:
        return ''
    n %= len(s)
    return s[n:] + s[:n]

面试题43 n个骰子的点数
要求:求出n个骰子朝上一面之和s所有可能值出现的概率

思路:n出现的可能是前面n-1到n-6出现可能的和,设置两个数组,分别保存每一轮

def get_probability(n):
    if n < 1:
        return []
    data1 = [0] + [1] * 6 + [0] * 6 * (n - 1)
    data2 = [0] + [0] * 6 * n   # 开头多一个0,方便按照习惯从1计数
    flag = 0
    for v in range(2, n+1):  # 控制次数
        if flag:
            for k in range(v, 6*v+1):
                data1[k] = sum([data2[k-j] for j in range(1, 7) if k > j])
            flag = 0
        else:
            for k in range(v, 6*v+1):
                data2[k] = sum([data1[k-j] for j in range(1, 7) if k > j])
            flag = 1
    ret = []
    total = 6 ** n
    data = data2[n:] if flag else data1[n:]
    for v in data:
        ret.append(v*1.0/total)
    print data
    return ret

面试题44 扑克牌的顺子
要求:从扑克牌中随机抽取5张牌,判断是不是顺子,大小王可以当任意值

从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2-10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字。

 

第一种方法是以大小王做0,计算空缺位置,若不大于0的个数则为匹配。该方法需要首先将牌进行排序。

思路: 使用排序

def is_continuous(numbers):
    """ 判断抽取的5张牌是否是顺子。

    Args:
        numbers: 抽取的5张牌。

    Returns:
        布尔变量,表示是否是顺子。
    """
    if len(numbers) < 1:
        return False
    numbers.sort()

    num_of_zero = numbers.count(0) # 统计数组中0的个数
    num_of_gap = 0

    # 统计数组中的间隔数目
    small = num_of_zero
    big = small + 1
    while big < len(numbers):
        if numbers[small] == numbers[big]: # 有重复元素,不可能是顺子
            return False
        num_of_gap += numbers[big] - numbers[small] - 1
        small += 1
        big += 1
    return False if num_of_gap > num_of_zero else True

 

面试题45 圆圈中最后剩下的数字
要求:0到n-1排成一圈,从0开始每次数m个数删除,求最后剩余的数

0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

思路:当 n > 1 时: f(n,m) = [f(n-1, m)+m]%n,当 n = 1 时: f(n,m)=0,关键是推导出关系表达式

def last_num(n, m):
    ret = 0
    if n == 1:
        return 0
    for i in range(2, n+1):
        ret = (m + ret) % i
    return ret

面试题46 求1+2...+n
要求:不能使用乘除、for、while、if、else等

方法一:使用range和sum

方法二:使用reduce

def get_sum1(n):
    return sum(range(1, n+1))


def get_sum2(n):
    return reduce(lambda x, y: x+y, range(1, n+1))

面试题47 不用加减乘除做加法
要求:不用加减乘除做加法

方法一:使用位运算,Python中大整数会自动处理,因此对carry需要加个判断

方法二:使用sum

def bit_add(n1, n2):
    carry = 1
    while carry:
        s = n1 ^ n2
        carry = 0xFFFFFFFF & ((n1 & n2) << 1)
        carry = -(~(carry - 1) & 0xFFFFFFFF) if carry > 0x7FFFFFFF else carry
        n1 = s
        n2 = carry
    return n1


def add(n1, n2):
    return sum([n1, n2])

面试题48 不能被继承的类
Python中不知道怎么实现不能被继承的类。以后补充代码或者原因。

 

 

面试题49 把字符串转化成整数
要求:把字符串转化成整数

测试用例:正负数和0,空字符,包含其他字符

备注:使用raise抛出异常作为非法提示

def str_to_int(string):
    if not string:  # 空字符返回异常
        raise Exception('string cannot be None', string)
    flag = 0  # 用来表示第一个字符是否为+、-
    ret = 0  # 结果
    for k, s in enumerate(string):
        if s.isdigit():  # 数字直接运算
            val = ord(s) - ord('0')
            ret = ret * 10 + val
        else:
            if not flag:
                if s == '+' and k == 0:  # 避免中间出现+、-
                    flag = 1
                elif s == '-' and k == 0:
                    flag = -1
                else:
                    raise Exception('digit is need', string)
            else:
                raise Exception('digit is need', string)
    if flag and len(string) == 1:  # 判断是不是只有+、-
        raise Exception('digit is need', string)
    return ret if flag >= 0 else -ret

 

面试题50 树中两个结点的最低公共祖先
要求:求普通二叉树中两个结点的最低公共祖先

方法一:先求出两个结点到根结点的路径,然后从路径中找出最后一个公共结点

备注:文件fifty.py中包含该代码的具体测试数据

class Solution(object):

    def __init__(self, root, node1, node2):
        self.root = root  # 树的根结点
        self.node1 = node1
        self.node2 = node2  # 需要求的两个结点

    @staticmethod
    def get_path(root, node, ret):
        """获取结点的路径"""
        if not root or not node:
            return False
        ret.append(root)
        if root == node:
            return True
        left = Solution.get_path(root.left, node, ret)
        right = Solution.get_path(root.right, node, ret)
        if left or right:
            return True
        ret.pop()

    def get_last_common_node(self):
        """获取公共结点"""
        route1 = []
        route2 = []  # 保存结点路径
        ret1 = Solution.get_path(self.root, self.node1, route1)
        ret2 = Solution.get_path(self.root, self.node2, route2)
        ret = None
        if ret1 and ret2:  # 路径比较
            length = len(route1) if len(route1) <= len(route2) else len(route2)
            index = 0
            while index < length:
                if route1[index] == route2[index]:
                    ret = route1[index]
                index += 1
        return ret

 

posted @ 2020-01-28 18:18  秋华  阅读(465)  评论(0编辑  收藏  举报