剑指offer的一些刷题记录

Posted by 王院长 on September 25, 2019

面试题3 数组中找出重复的数字

长度n+1的数组,范围[1,n],其中至少有一个数字是重复的,要求找出任一重复的数字

def get_duplication(nums, length):

    # 求原数组中,[left, right] 有几个元素
    def count_range(left, right):
        if left > right:
            return 0
        count = 0
        for i in range(len(nums)):
            if left <= nums[i] <= right:
                count += 1
        return count

    l, r = 1, length
    mid = -1
    while l <= r:
        # 如果不加此判断,会产生死循环
        if (l+r)//2 == mid:
            break
        mid = (l + r) // 2
        l_count = count_range(l, mid)
        if l_count > mid - l + 1:
            r = mid
            # [l, r]双闭区间,l_count 不满足,则 mid 也有可能是重复的,所以 mid 不能排除
        else:
            l = mid + 1
            # 如果 l_count 满足条件,mid 是可以排除的
    return mid

if __name__ == '__main__':
    print(get_duplication([1, 2, 3, 2], 3))
    print(get_duplication([1], 1))

面试题7 重建二叉树

from queue import Queue


class TreeNode():

    def __init__(self, value=0, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right


class Solution():

    def ConstructCore(self, preorder, inorder):
        if len(preorder) == 0 or len(inorder) == 0:
            return None
        root = TreeNode(preorder[0])
        for i in range(len(inorder)):
            if inorder[i] == preorder[0]:
                root.left = self.ConstructCore(preorder[1:i + 1], inorder[0:i])
                root.right = self.ConstructCore(
                    preorder[i + 1:], inorder[i + 1:])
                break
        return root

    def convert2list(self, root):
        res = []
        queue = Queue()
        queue.put(root)
        while not queue.empty():
            cur = queue.get()
            if cur:
                res.append(cur.value)
                queue.put(cur.left)
                queue.put(cur.right)
            else:
                res.append(None)
        return res


if __name__ == '__main__':
    s = Solution()
    preorder = [1, 2, 4, 7, 3, 5, 6, 8]
    inorder = [4, 7, 2, 1, 5, 3, 8, 6]
    tree = s.ConstructCore(preorder, inorder)
    print(s.convert2list(tree))


面试题8 二叉树的下一个节点

class TreeLinkNode:

    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None
        self.next = None  # 父节点


class Solution:

    def GetNext(self, pNode):
        # write code here
        if not pNode:
            return pNode
        if pNode.right:  # 该节点有右子树
            left_node = pNode.right
            # 下一个节点是右子树最左的节点
            while left_node.left:
                left_node = left_node.left
            return left_node

        while pNode.next:  # 该节点有父节点
            parent = pNode.next
            if parent.left == pNode:  # 该节点是其父节点的左节点
                return parent  # 下一个节点是其父节点
            pNode = parent  # 否则继续寻找


面试题9 用两个栈实现队列

class Que():
    def __init__(self, data = []):
        self.stack1 = [] # 出队用
        self.stack2 = [] # 进队用
        for item in data:
            self.appendTail(item)
    def appendTail(self, value):
        if isinstance(value, list):
            for i in value:
                self.stack2.append(i)
        else:
            self.stack2.append(value)

    def deleteHead(self):
        if not self.stack1:
            if not self.stack2:
                return
            while self.stack2:
                self.stack1.append(self.stack2.pop())
        return self.stack1.pop()

    def __repr__(self):
        return str(self.stack1[::-1] + self.stack2)


if __name__ == '__main__':
    queue = Que([1, 2])
    print(queue.deleteHead())
    queue.appendTail([3, 4])
    print(queue)

面试题12 矩阵中的路径

class Solution:

    def hasPath(self, matrix, rows, cols, path):
        for i in range(rows):
            for j in range(cols):
            # 如果 matrix[i*rows+j] 等于 path[0],
            # 继续寻找 (i, j) 上下左右四个点是否等于 path[1]
                if matrix[i * rows + j] == path[0]:
                    if self.find_path(list(matrix), rows, cols, path[1:], i, j):
                        return True
        return False

    def find_path(self, matrix, rows, cols, path, i, j):
        if not path:
            return True
            
        # 遍历过的需要标记
        matrix[i * rows + j] = 0
        
        # 判断(i, j)上下左右四个点是否相等
        # 如果相等,继续转化成子问题
        if j + 1 < cols and matrix[i * rows + j + 1] == path[0]: 
            return self.find_path(matrix, rows, cols, path[1:], i, j + 1)
        elif j - 1 >= 0 and matrix[i * cols + j - 1] == path[0]:
            return self.find_path(matrix, rows, cols, path[1:], i, j - 1)
        elif i + 1 < rows and matrix[(i + 1) * cols + j] == path[0]:
            return self.find_path(matrix, rows, cols, path[1:], i + 1, j)
        elif i - 1 >= 0 and matrix[(i - 1) * cols + j] == path[0]:
            return self.find_path(matrix, rows, cols, path[1:], i - 1, j)
        else:
            return False

if __name__ == '__main__':
    solution = Solution()
    matrix = 'ABCEHJIGSFCSLOPQADEEMNOEADIDEJFMVCEIFGGS'
    rows = 5
    cols = 8
    path = 'SGGFIECVAASABCEHJIGQEMS'
    ans = solution.hasPath(matrix, rows, cols, path)
    print(ans)


面试题13 机器人的运动范围

# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.vis = {}

    def movingCount(self, threshold, rows, cols):
        return self.moving(threshold, rows, cols, 0, 0)

    def moving(self, threshold, rows, cols, row, col):
        rowans, colans = 0,0
        rowtemp, coltemp = row, col
        while rowtemp > 0:
            rowans = rowans + rowtemp%10
            rowtemp = rowtemp//10
        while coltemp > 0:
            colans = colans + coltemp%10
            coltemp = coltemp//10

        if rowans + colans>threshold: # 如果数字之和大于k
            return 0
        if row >= rows or col >= cols or row < 0 or col < 0: # 如果(row, cos)越界
            return 0
        if (row, col) in self.vis: # 如果已访问
            return 0
        self.vis[(row, col)] = 1

        return 1 + self.moving(threshold, rows, cols, row - 1, col) +\
               self.moving(threshold, rows, cols, row + 1,col) + \
               self.moving(threshold, rows,cols, row,col - 1) + \
               self.moving(threshold, rows, cols, row, col + 1)


if __name__=='__main__':
    solution=Solution()
    threshold=10
    rows,cols=1,100
    ans=solution.movingCount(threshold,rows,cols)
    print(ans)

面试题14 割绳子

长度为n的绳子,割成几段,我们可以把它转化成一个子问题,即割成两段,两段乘积最大。状态转移方程: f(n) = max(f(i)*f(n-i)) f(i)表示长度为i的绳子,所能取得的最大乘积

def maxProductAtferCutting(length):
    if length < 2:
        return 0
    if length == 2:
        return 1
    if length == 3:
        return 2

    # products 数组存储绳子长度为i时,所能得到的最大乘积
    # 由于必须割一刀,所以 length < 4 时,需要特殊处理,提前 return
    products = [0] * (length + 1)
    for i in range(4):
        products[i] = i
    for i in range(4, length + 1):
        res = 0
        for j in range(1, i // 2 + 1):
            product = products[j] * products[i - j]
            res = max(product, res)
        products[i] = res

    return products[-1]

print(maxProductAtferCutting(8))

面试题16 数值的整数次方

def power(base, n):
    if base == 0 or base == 1:
        return 1

    if n == 0:
        return 1
    if n == -1: # 考虑n小于0的情况
        return 1 / base

    result = power(base, n >> 1)
    result *= result
    if n & 1 == 1: # 判断是否是奇数
        result *= base
    return result

print(power(-2, -1))


面试题17 打印从1到最大的n位数

全排列

def print_1to_max(n):
    def helper(n, tmp):
        if n == 0:
            print_num(tmp)
            return # IMPORTANT

        for i in range(10):
            helper(n - 1, tmp + str(i))

    def print_num(strs):
        i = 0
        while i < len(strs):
            if strs[i] != '0':
                break
            i += 1
        if i < len(strs):
            print(strs[i:])

    helper(n, "")


print_1to_max(3)

面试题40 最小的 k 个数

方法一:快排思想

def minimum_k(nums, k):
    n = len(nums)
    if n == 0 or k > n or k <= 0:
        return

    start, end = 0, n - 1

    def partition(nums, start, end):
        pivot = nums[start]
        j = start
        for i in range(start + 1, end + 1):
            if nums[i] < pivot:
                j += 1
                nums[i], nums[j] = nums[j], nums[i]
        nums[j], nums[start] = nums[start], nums[j]
        return j

    index = partition(nums, start, end)
    while index != k - 1:
        if index < k - 1:
            end = index - 1
        else:
            start = index + 1
        index = partition(nums, start, end)
    return nums[:index + 1]

print(minimum_k([4, 5, 1, 6, 2, 7, 3, 8], 4))

方法二:使用堆,或红黑树 1)使用 heapq

import heapq

def minimum_k(nums, k):
    n = len(nums)
    if n == 0 or k > n or k <= 0:
        return

    return heapq.nsmallest(k, nums)

print(minimum_k([4, 5, 1, 6, 2, 7, 3, 8], 4))

2)自己写堆

def minimum_k(nums, k):
    n = len(nums)
    if n == 0 or k > n or k <= 0:
        return
    res = nums[:k]
    # 构建最大堆
    def heap_build(res):
        n = len(res)
        for i in range((n - 2) // 2, -1, -1):
            heapify(res, i)
        return res

    def heapify(res, i):
        n = len(res)
        while True:
            max_pos = i
            if i * 2 + 1 < n and res[i * 2 + 1] > res[max_pos]:
                max_pos = i * 2 + 1
            if i * 2 + 2 < n and res[i * 2 + 2] > res[max_pos]:
                max_pos = i * 2 + 2
            if max_pos == i:
                break
            res[max_pos], res[i] = res[i], res[max_pos]  # IMPORTANT
            i = max_pos

    heap_build(res)
    for i in range(k, len(nums)):
        # print(f"i = {i}, res = {res}")
        if nums[i] > res[0]:
            continue
        res[0] = nums[i]
        heapify(res, 0)

    return res

print(minimum_k([4, 5, 1, 6, 2, 7, 3, 8], 4))


面试题46 把数字翻译成字符串

def how_many(num):
    if num <= 0:
        return 0

    num = str(num)
    n = len(num)
    memo = {}

    # 第 i,i+1 位数字拼接起来是否在[10, 25]范围内
    def can_trans(i):
        if i < n - 1 and 10 <= int(num[i:i + 2]) <= 25:
            return 1
        else:
            return 0

    # dp[i] 表示以 第i数字开头的不同翻译的数量
    def dp(i):
        if i not in memo:
            # 处理边界
            if i >= n-1:
                memo[i] = 1
                return memo[i]
            # dp(i) = dp(i+1) + can_trans(i)*dp(i+2)
            memo[i] = dp(i + 1) + can_trans(i) * dp(i + 2)
        return memo[i]

    dp(0)
    return memo[0]

print(how_many(25))
print(how_many(1))
print(how_many(12258))

面试题48 最长不含重复字符的子字符串

19年浦发秋招机试上机题 方法一:双指针

# i, j 左右两个指针,cache 存储 i, j 两个指针之间(包括i, j)字母的个数
# j 右移,如果新加入的字母已存在,那么移动左指针,直到
# cache[strs[j]] = 0,再新加入 cache 中
# 保证 cache 中每个字母的个数都不大于1


def find_no_dup(strs):
    cache = {}
    i, j = 0, 0
    n = len(strs)
    if n == 0:
        return 0

    max_len = 1
    while j < n:
        if cache.get(strs[j], 0) > 0:
            while cache.get(strs[j], 0) != 0:
                cache[strs[i]] -= 1
                i += 1
        cache[strs[j]] = 1
        max_len = max(j - i + 1, max_len)
        j += 1
    return max_len

print(find_no_dup('ababab'))
print(find_no_dup('aaaaaaaaaaa'))
print(find_no_dup('abcdefg'))
print(find_no_dup('pwawbcdec'))
print(find_no_dup(''))

方法二:动态规划(剑指offer P237)

def max_no_dup_len(strs):
    n = len(strs)
    if n <= 1:
        return n

    cache = {}  # 存储上一个字母出现的位置
    cache[strs[0]] = 0

    # dp[i]表示以i个字符结尾的最长相异子字符串的长度
    dp = [1] * n
    for i in range(1, n):
        # 若该字符串没有出现过,dp[i] = dp[i - 1] + 1
        if strs[i] not in cache:
            cache[strs[i]] = i
            dp[i] = dp[i - 1] + 1
        else:
            # 若该字符串在之前出现过:
            # 1、出现的位置较远,即 d > dp[i - 1],不影响 dp[i]
            # 如 i = 8, r出现的位置较远
            # 2、出现在dp[i-1]所表示的相异字符串的内部,则dp[i] = d
            # 如 i = 5,a出现的位置较近,dp[5] = 5 - 2 = 3
            d = i - cache[strs[i]]
            cache[strs[i]] = i
            if d > dp[i - 1]:
                dp[i] = dp[i - 1] + 1
            else:
                dp[i] = d

    print(dp)
    return max(dp)

print(max_no_dup_len('arabcacfr'))


面试题55 二叉树的深度,及判断是否是平衡二叉树

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


# 递归的解法,容易写,但是一个节点会被遍历多次
def isBalanced(tree):
    if not tree:
        return True

    l_depth = depth(tree.left)
    r_depth = depth(tree.right)

    if abs(l_depth - r_depth) > 1:
        return False
    else:
        return isBalanced(tree.left) and isBalanced(tree.right)
        



class Tree:

    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None


t = Tree(0)
t.left = Tree(1)
t.right = Tree(2)

print(depth(t))
print(isBalanced(t))


面试题56 数组中数字出现的次数

找出数组中只出现一次的两个数字

def find_appear_twice(nums):
    def first_bit1(n): # 找出二进制最右为1的位数
        index = 0
        while n & 1 == 0:
            n >> 1
            index += 1
        return index

    def is_bit1(n, index): # 判断二进制从右到左第index位是否为1
        n = n >> index
        return n & 1

    n = len(nums)
    if n < 2:
        return

    # 找出这两个数字异或的结果,这个数的二进制至少一位为1
    tmp = 0
    for num in nums:
        tmp = tmp ^ num

    index = first_bit1(tmp)

    nums1, nums2 = [], []
    for num in nums:
        if is_bit1(num, index):
            nums1.append(num)
        else:
            nums2.append(num)

    res1, res2 = 0, 0
    for num in nums1:
        res1 = res1 ^ num
    for num in nums2:
        res2 = res2 ^ num

    return res1, res2


print(find_appear_twice([1, 1, 2, 3, 4, 4, 5, 5]))


面试题62 约瑟夫环

def LastRemaining(n, m):
    if n < 1 or m < 1:
        return -1
    last = 0
    for i in range(2, n+1):
        last = (last + m) % i
    return last

递推规律:

f(n, m) = [f(n-1, m) + m] % n
n = 1, f(1, m) = 0
f(2, m) = [f(1, m) + m] % 2
f(3, m) = [f(2, m) + m] % 3
...
f(n, m) = [f(n-1, m) + m] % n