《剑指Offer》-面试题Python实现合集

《剑指Offer》作为互联网行业求职必看的书籍,其中收录了各大公司招聘时常问到的一些面试题,对于大家通过技术面颇有裨益。因此这个月我也花了接近三周早上的时间,用于通览全书。由于书中是用C++实现的,而本人主要使用Python,因此在掌握解题思路后,采用Python对每个面试题进行了重构。本着先过一遍再慢慢消化和吸收的原则,仓促完成了所有面试题的重构。如有问题,欢迎大家交流讨论~

全部代码放在了Github上:《剑指Offer》Python实现, 欢迎大家star和fork~~~

文章目录

面试题3:二维数组中的查找

【题目】:
在一个二维数组中,每一行都按照从左到右递增的顺序排列,每一列按照从上到下的顺序排列。完成一个函数,使得输入一个二维数组和一个整数,判断数组中是否有该整数。

【解题思路】:
每次从左下角或者右上角的数字开始进行比较(书中是从右上角开始)。这里我们选取左下角的数开始比较:

  1. 如果左下角的数等于查找数, 则返回True;
  2. 如果左下角的数大于查找数, 则比较同一列中的上一个数字;
  3. 如果左下角的数小于查找数, 则比较同一行中的右边一位的数字;
def Find(grid, number) -> bool:
    if not grid:
        return False
    row, col = len(grid), len(grid[0])
    cur_i, cur_j = row - 1, 0

    while cur_i >= 0 and cur_j < col:
        if grid[cur_i][cur_j] == number:
            return True
        elif grid[cur_i][cur_j] < number:
            cur_j += 1
        else:
            cur_i -= 1
    
    return False
grid = [[1, 2, 8, 9],
       [2, 4, 9, 12],
       [4, 7, 10, 13],
       [6, 8, 11, 15]]

grid1 = []

number = 9

Find(grid1, number)
False

面试题4 替换空格

【题目】:
实现一个函数,将字符串中的每个空格替换为"%20"。

【解题思路】:
这个题如果用python做,直接替换就行。

这里采书上的做法:

  1. 先统计空格的个数;
  2. 设置两个指针,一个指向原字符串的末尾,一个指向新字符串的末尾;
  3. 遇到空格就替换为"%20"
def ReplaceBlank(s: str) -> str:
    if not s:
        return 
    # 统计字符串中空格的长度
    numberOfBlank = 0
    for i in range(len(s)):
        if s[i] == " ":
            numberOfBlank += 1
    
    indexOfNew = len(s) + numberOfBlank * 2 - 1
    indexOfOrigin = len(s) - 1
    s = s + " "*numberOfBlank*2
    s = list(s)
    while indexOfOrigin >= 0 and indexOfNew >  indexOfOrigin:
        if s[indexOfOrigin] == " ":
            s[indexOfNew] = '0'; indexOfNew -= 1;
            s[indexOfNew] = '2'; indexOfNew -= 1;
            s[indexOfNew] = '%'; indexOfNew -= 1;
        else:
            s[indexOfNew] = s[indexOfOrigin]; indexOfNew -= 1;
        indexOfOrigin -= 1
    s = "".join(s)
    return s

def ReplaceBlankPy(s: str) -> str:
    if not s:
        return 
    s = list(s)
    
    def replace(char):
        if char == " ":
            return "%20"
        else:
            return char
        
    s = list(map(replace, s))
    
    return "".join(s)
s = "    We are     happy "
print("ReplaceBlank:", ReplaceBlank(s))
print("ReplaceBlankPy:", ReplaceBlankPy(s))
ReplaceBlank: %20%20%20%20We%20are%20%20%20%20%20happy%20
ReplaceBlankPy: %20%20%20%20We%20are%20%20%20%20%20happy%20

面试题 5 从尾到头打印链表

【题目】:
输入一个链表的头节点,从尾到头打印每个节点的值。

【解题思路】:

  1. 栈: 每读出一个结点的值,压入栈中,最后出栈得到结果;
  2. 递归: 每次先答应下一个节点的值再打印自身节点。
# data structure
class LinkNode():
    def __init__(self, val):
        self.val = val
        self.next = None

# Create the Linklist
def createLink(l: list) -> ListNode:
    if not l:
        return 
    p = head = LinkNode(l[0])
    
    for i in l[1:]:
        p.next = LinkNode(i)
        p = p.next
    
    return head

# print linklist
def printLink(head):
    while head:
        print(head.val, end=" ")
        head = head.next
    print()

a = [1,2,3,4,5]
head = createLink(a)
def reversePrintLinkByStack(head):
    if not head:
        return
    stack = []
    while head:
        stack.append(head.val)
        head = head.next
    
    for i in stack[::-1]:
        print(i, end=" ")
    print()

def reversePrintLinkByRecursive(head):
    if head:
        if head.next:
            reversePrintLinkByRecursive(head.next)
        print(head.val, end=" ")

reversePrintLinkByStack(head)
5 4 3 2 1 
reversePrintLinkByRecursive(head)
5 4 3 2 1 

面试题6 重建二叉树

【题目】:
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树,并返回重建后二叉树的根节点。
假设两个遍历的结果中没有重复的数字。

【解题思路】:

  1. 从前序遍历中找到第一个结果作为根节点;
  2. 在中序遍历中找到这个值,则这个值左边的值是左子节点;右边则是右子节点;
  3. 利用递归,采用同样的方法重构二叉树。
# Define functions for binary tree
class treeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

# preOrder function
def preOrder(root):
    if not root:
        return 
    print(root.val, end="->")
    preOrder(root.left)
    preOrder(root.right)

# inOrder function
def inOrder(root):
    if not root:
        return 
    inOrder(root.left)
    print(root.val, end="->")
    inOrder(root.right)
    

# Construct the binary tree
def constructBinaryTree(preOrder: list, inOrder: list) -> treeNode:
    if not preOrder or not inOrder or (len(preOrder) != len(inOrder)):
        return 
    
    def construct(preOrder, inOrder):
        if not preOrder:
            return None
        
        # build root node
        root = treeNode(preOrder[0])
        
        # find the root node in the inOrder
        for i in range(len(inOrder)):
            if inOrder[i] == root.val:
                break
        
        # build left tree
        root.left = construct(preOrder[1:len(inOrder[:i])+1], inOrder[:i])
        # build right tree
        root.right = construct(preOrder[len(inOrder[:i])+1:], inOrder[i+1:])
        
        return root
    
    return construct(preOrder, inOrder)

#  test
pre = [1, 2, 4, 7, 3, 5, 6, 8]
ino = [4, 7, 2, 1, 5, 3, 8, 6]
root = constructBinaryTree(pre, ino)
print("PreOrder:")
preOrder(root)
print("None\nInOrder:")
inOrder(root)
print("None")
PreOrder:
1->2->4->7->3->5->6->8->None
InOrder:
4->7->2->1->5->3->8->6->None

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

【题目】:
用两个栈实现一个队列,实现这个队列的删除头部deleteHead和插入尾部appendTail的功能。

【解题思路】:
构造两个栈:stack1, stack2:

  1. 从队列中删除一个头节点: 先将数据压入到stack1中, 要删除头节点的时候,将stack1中的元素出栈再压入到stack2中,这时stack2栈顶的元素就是头节点;
  2. 插入一个为尾节点:直接将数据压入到stack1中。
class myQueue():
    def __init__(self):
        self.stack1 = []
        self.stack2 = []
    
    def appendTail(self, val):
        self.stack1.append(val)
        print("The %d is added to tail of the queue"%val)
        
    def deleteHead(self):
        if self.stack2:
            print("The head is deleted from the queue, the head value is:", self.stack2.pop(-1))
        else:
            while self.stack1:
                self.stack2.append(self.stack1.pop(-1))
            if not self.stack2:
                print("The queue is empty!")
                return
            print("The head is deleted from the queue, the head value is:", self.stack2.pop(-1))

# test
q = myQueue()
# delete head from an empty queue
print("# delete head from an empty queue")
q.deleteHead()
# add [1,2,3] to the queue, and then delete the head
print("# add [1,2,3] to the queue, and then delete the head")
for i in [1, 2, 3]: 
    q.appendTail(i)
q.deleteHead()

# add [4, 5] to the queue, and the delete the head twice
print("# add [4, 5] to the queue, and the delete the head twice")
for i in [4, 5]:
    q.appendTail(i)
for i in range(2):
    q.deleteHead()
    
# delete the head 3 times
print("# delete the head 3 times")
for i in range(3):
    q.deleteHead()
# delete head from an empty queue
The queue is empty!
# add [1,2,3] to the queue, and then delete the head
The 1 is added to tail of the queue
The 2 is added to tail of the queue
The 3 is added to tail of the queue
The head is deleted from the queue, the head value is: 1
# add [4, 5] to the queue, and the delete the head twice
The 4 is added to tail of the queue
The 5 is added to tail of the queue
The head is deleted from the queue, the head value is: 2
The head is deleted from the queue, the head value is: 3
# delete the head 3 times
The head is deleted from the queue, the head value is: 4
The head is deleted from the queue, the head value is: 5
The queue is empty!

面试题8 旋转数组的最小数字

【题目】:
把一个数组最开始的若干元素搬到数组的末尾,我们称之为数组的旋转。输入一个【递增】排序的数组的一个旋转,输出旋转数组的最小元素。
如{3, 4, 5, 1, 2}是{1, 2, 3, 4, 5}的一个旋转,最小元素是1。

【解题思路】:
设置前后指针,通过更新指针,直到两个位置只差为1的时候,第二个指针所指向的位置就是最小元素。

需要注意的是两个指针指向的位置的元素一样时,就需要遍历了,如{1, 1, 1, 0, 1}

def Min(nums: list) -> int:
    if not nums:
        return "None"
    index1, index2 = 0, len(nums)-1
    indexMid = index1
    
    while nums[index1] >= nums[index2]:
        if index2 - index1 == 1:
            return nums[index2]
        
        indexMid = (index1 + index2) // 2
        # 如果index1, index2, indexMid指向的元素相同的话,需要进行遍历
        if nums[index1] == nums[index2] and nums[index1] == nums[indexMid]:
            minNum = nums[index1]
            for i in nums[index1+1: index2+1]:
                minNum = min(minNum, i)
            
            return minNum
        
        if nums[index1] <= nums[indexMid]:
            index1 = indexMid
        elif nums[index1] >= nums[indexMid]:
            index2 = indexMid
    
    return nums[indexMid]

# test
nums = [3,4,5,1,2]
print("[3,4,5,1,2]:", Min(nums))
nums = [1,1,1,0,1]
print("[1,1,1,0,1]:", Min(nums))
[3,4,5,1,2]: 1
[1,1,1,0,1]: 0

面试题9 斐波那契数列

【题目】:
写一个函数,要求输入n,输出斐波那契数列的的第n项。
斐波那契数列定义如下:
f ( n ) = { 0 , n = 0 1 , n = 1 f ( n − 1 ) + f ( n − 2 ) , n > 1 {f(n)} = \left\{ \begin{array}{l} 0,n = 0\\ 1,n = 1\\ f(n - 1) + f(n - 2),n > 1 \end{array} \right. f(n)=0,n=01,n=1f(n1)+f(n2),n>1

f ( n ) = [ 1 + 5 2 ] n + 1 − 5 2 n 1 + 5 2 − 1 − 5 2 f(n) = \frac{[\frac{1+\sqrt 5}{2}]^n+{\frac{1-\sqrt 5}{2}}^n}{\frac{1+\sqrt 5}{2}-\frac{1-\sqrt 5}{2}} f(n)=21+5 215 [21+5 ]n+215 n

【解题思路】:

  1. 递归
  2. 迭代
  3. 通项公式
    实验证明:迭代法要比递归更加高效
# use recursive method
def fibRecursive(n: int) -> int:
    if n <= 0:
        return 0
    if n == 1:
        return 1
    
    return fibRecursive(n-1) + fibRecursive(n-2)

# use the iterative method
def fibIterative(n: int) -> int:
    if n <= 0:
        return 0
    if n == 1:
        return 1
    
    first, second = 0, 1
    
    for i in range(2, n+1):
        first, second = second, first + second
    return second

# use the function
def fibUseFunction(n):
    return int( (((1+5**0.5)/2)**n - ((1-5**0.5)/2)**n) / 5**0.5 )


# test
import time
n = 35
start = time.time()
print("Recursive method:", fibRecursive(n), end=" Time consumption=")
end = time.time()
print(end-start)

start = time.time()
print("Iterative method:", fibIterative(n), end=" Time consumption=")
end = time.time()
print(end-start)

start = time.time()
print("Function method:", fibUseFunction(n), end=" Time consumption=")
end = time.time()
print(end-start)
Recursive method: 9227465 Time consumption=4.571698904037476
Iterative method: 9227465 Time consumption=0.000457763671875
Function method: 9227465 Time consumption=0.0

面试题10 二进制中1的个数

【题目】:
请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。
如9的二进制为:1001,输出结果为2。

【解题思路】:

  1. 利用位移的思想,每次先与1进行与运算,再将该数右移一位;
  2. 先将该数字对2求余,再除以2,直到结果为0。

**Note:**位运算比除法效率要高

def numberOf1(n: int) -> int:
    count = 0
    while n:
        if n & 1:
            count += 1
        n = n >> 1
    return count

def numberOf1_2(n: int) -> int:
    count = 0
    while n:
        if n % 2 != 0:
            count += 1
        n = n // 2
    
    return count

# 这个方法非常巧妙,且效率很高,因为数字中有多少个1就会进行几次循环
def numberOf1_3(n: int) -> int:
    count = 0
    while n:
        count += 1
        n = (n - 1) & n
    
    return count

# test
n = 9
print("numberOf1: ", numberOf1(n))
print("numberOf1_2: ", numberOf1_2(n))
print("numberOf1_3: ", numberOf1_3(n))
numberOf1:  2
numberOf1_2:  2
numberOf1_3:  2

面试题11 数值的整数次方

【题目】:
实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑最大数问题。

【解题思路】:
需要考虑exponent为负数和0的情况。

def Power(base: float, exponent: int) -> float:
    if exponent == 0:
        return 1
    if base - 0.0 < 0.0000001 and base - 0.0 > -0.0000001:
        if exponent < 0:
            return "Inf"
        else:
            return 0
    
    result, flag = 1, 1
    if exponent > 0:
        flag = 0
    else:
        exponent = - exponent
    
    for i in range(exponent):
        result *= base
    
    if flag:
        result = 1 /  result
        
    return result
    
# test
print(Power(0.0, 9))
print(Power(0.0, -10))
print(Power(-10, 3))
print(Power(-10, -2))
0
Inf
-1000
0.01
# more efficient

def PowerWithUnsignedExponent(base, exponent):
    if exponent == 0:
        return 0
    if exponent == 1:
        return base
    
    result = PowerWithUnsignedExponent(base, exponent >> 1)
    result *= result
    if exponent & 1 == 1:
        result *= base
    
    return result


def Power2(base: float, exponent: int) -> float:
    if exponent == 0:
        return 1
    if base - 0.0 < 0.0000001 and base - 0.0 > -0.0000001:
        if exponent < 0:
            return "Inf"
        else:
            return 0
    
    result, flag = 1, 1
    if exponent > 0:
        flag = 0
    else:
        exponent = - exponent
    # use a more efficient algorithm
    result = PowerWithUnsignedExponent(base, exponent)
    
    if flag:
        result = 1 /  result
        
    return result

# test
print(Power2(0.0, 9))
print(Power2(0.0, -10))
print(Power2(-10, 3))
print(Power2(-10, -2))
0
Inf
-1000
0.01

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

【题目】:
输入数字n,按顺序打印从1到最大的n位十进制数。比如输入3,则打印1,2,3,…,999。

【解体思路】:

  1. 这个题目需要考虑大数的问题,使用字符串来表示这个数字,然后在字符串上模拟加法。
  2. 还可以使用递归方法,将问题转化成数字的全排列。
# Iterative method
def printDigits_Iterative(n: int):
    if n <= 0:
        return 
    number = [0]*n
    while True:
        isOverFlow = False
        carry = 0
        for i in range(len(number)-1, -1, -1):
            if i == len(number) - 1:
                isum = number[i] + 1
                if isum == 10:
                    if i == 0:
                        return
                    carry = 1
                    number[i] = 0
                else:
                    carry = 0
                    number[i] = isum
            else:
                if carry == 1:
                    isum = number[i] + carry
                    if isum == 10:
                        if i == 0:
                            return
                        carry = 1
                        number[i] = 0
                    else:
                        carry = 0
                        number[i] = isum
                else:
                    break
                    
        printNumber(number)
        
def printNumber(number):
    for i in range(len(number)):
        if number[i] != 0:
            break
    if i == len(number)-1 and number[i] == 0:
        return
    res = "".join(str(j) for j in number[i:])
    print(res, end = " ")

printDigits_Iterative(2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 
# Recursive method
def printDigits_Recursive(n: int):
    if n <= 0:
        return
    number = [0]*n
    
    def recursive(number, index):
        if index == len(number):
            printNumber(number)
            return
        for i in range(10):
            number[index] = i
            recursive(number, index+1)
            
    recursive(number, 0)
    
printDigits_Recursive(2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 

面试题13 在O(1)时间删除链表节点

【题目】:
给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该节点。
void deleteNode(ListNode* pListHead, ListNode* pToBeDeleted)

【解题思路】:
从三个方面来考虑:

  1. 如果待删除节点不是尾结点:将该节点的值由下一个节点的值来代替,然后指向下下个节点;
  2. 如果待删除节点是尾结点同时也是头节点,链表中只有一个节点: 将头节点置为NULL;
  3. 如果是尾巴节点,则需要遍历到尾节点前的一个节点,然后进行删除操作。
# define the linklist
class ListNode():
    def __init__(self, val):
        self.val = val
        self.next = None

# create a linklist
def createLinkList(li: list) -> ListNode:
    if not li:
        return 
    p = head = ListNode(li[0])
    
    for i in li[1:]:
        p.next = ListNode(i)
        p = p.next
    
    return head

# print linklist
def printLinkList(head: ListNode):
    if not head:
        print("This is an empty linklist!")
        return 
    while head:
        print(head.val, end="->")
        head = head.next
    print(None)
    
# Delete a node in O(1)
def deleteNode(head: ListNode, toBeDeleted: ListNode) -> ListNode:
    if not head or not toBeDeleted:
        return
    if toBeDeleted.next != None:
        toBeDeleted.val = toBeDeleted.next.val
        toBeDeleted.next = toBeDeleted.next.next
    elif head == toBeDeleted:
        head = None
    else:
        p = head
        while p.next != toBeDeleted:
            p = p.next
        p.next = None
    
    return head

# test
# 1. create a linklist
head = createLinkList([1,2,3,4,5])
printLinkList(head)
# 2. delete a node in the middle of linklist
toBeDeleted = head.next.next
newhead = deleteNode(head, toBeDeleted)
printLinkList(newhead)
# 3. delete a node in bottom of the linklist
toBeDeleted = head.next.next.next
newhead = deleteNode(head, toBeDeleted)
printLinkList(newhead)
# 4. test a one node linklist
head = createLinkList([1])
newhead = deleteNode(head, head)
printLinkList(newhead)
1->2->3->4->5->None
1->2->4->5->None
1->2->4->None
This is an empty linklist!

面试题14 调整数组顺序使奇数位于偶数前面

【题目】:
输入一个整数数组,实现一个函数来调整数组中数字的顺序,使得所有的奇数位于数组前半部分,偶数位于数组后半部分。

【解题思路】:
使用双指针,一个从前往后搜索【指向偶数】,一个从后往前搜索【指向奇数】。直到来给你个指针重合。

def reorderOddEven(nums: list) -> list:
    if len(nums) == 0:
        return 
    first, second = 0, len(nums)-1
    
    while first < second:
        while first < len(nums) and nums[first] & 1 == 0:
            first += 1
        while second > 0 and nums[second] & 1 == 1:
            second -= 1
        
        if first < second:
            temp = nums[first]
            nums[first] = nums[second]
            nums[second] = temp
    
    return nums

# test
reorderOddEven([2,1,3,5,4,6])
[2, 6, 4, 5, 3, 1]

面试题15 链表中倒数第k个节点

【题目】:
输入一个链表,输出该链表中倒数第k个节点。

【解题思路】:

  1. 使用双指针,一个先跑到第k个节点,然后第二个指针从头开始与第一个指针同时遍历,直到第一个指针指向空,此时第二个指针就指向倒数第k个节点;
  2. 使用递归的回溯法。
# define the linklist
class ListNode():
    def __init__(self, val):
        self.val = val
        self.next = None

# create a linklist
def createLinkList(li: list) -> ListNode:
    if not li:
        return 
    p = head = ListNode(li[0])
    
    for i in li[1:]:
        p.next = ListNode(i)
        p = p.next
    
    return head

def findKthToTail(head: ListNode, k: int):
    if not head or k == 0:
        return
    pAhead, pBehind = head, None
    for i in range(k-1):
        if pAhead.next != None:
            pAhead = pAhead.next
        else:
            return None
    
    pBehind = head
    while pAhead.next:
        pAhead = pAhead.next
        pBehind = pBehind.next
    
    print(pBehind.val)

def findKthToTail_Recursive(head, k):
    if not head or k == 0:
        return
    
    def recursive(head, k):
        if head == None:
            return 0
        
        i = recursive(head.next, k) + 1

        if i == k:
            print(head.val)
            
        return i
    
    recursive(head, k)
    
    
# test
head = createLinkList([1,2,3,4,5,6])
print("findKthToTail", end=": ")
findKthToTail(head, 2)
print("findKthToTail_Recursive", end=": ")
findKthToTail_Recursive(head, 2)
findKthToTail: 5
findKthToTail_Recursive: 5

面试题16 反转链表

【题目】:
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

【解题思路】:

  1. 定义三个指针,一个指向当前,一个指向上一个节点,一个指向下一个节点;
  2. 每次将当前节点的next指向上一个节点,并保存下一个节点;
  3. 直到输出最后一个节点作为反转链表的头节点。
# define the linklist
class ListNode():
    def __init__(self, val):
        self.val = val
        self.next = None

# create a linklist
def createLinkList(li: list) -> ListNode:
    if not li:
        return 
    p = head = ListNode(li[0])
    
    for i in li[1:]:
        p.next = ListNode(i)
        p = p.next
    
    return head

# print linklist
def printLinkList(head: ListNode):
    if not head:
        print("This is an empty linklist!")
        return 
    while head:
        print(head.val, end="->")
        head = head.next
    print(None)
    
# reverse the linklist
def reverseLinkList(head: ListNode) -> ListNode:
    pReverseHead = None
    pNode = head
    pPrev = None
    while pNode:
        pNext = pNode.next
        if not pNext:
            pReverseHead = pNode
        pNode.next = pPrev
        pPrev = pNode
        pNode = pNext
    return pReverseHead

# test
# 1. create link list
head = createLinkList([1, 2, 3, 4, 5])
# 2. reverse the link list
reverse = reverseLinkList(head)
# 3. print the link list
printLinkList(reverse)
5->4->3->2->1->None

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

【题目】:
输入两个递增的排序链表,合并这两个链表使得新链表中的节点仍然是递增的。

【解题思路】:
使用递归的思想:

  1. 先判断两个头节点,其值小的作为头节点;
  2. 再判断头节点的下一个节点与前一个链表头节点的大小。
# define the linklist
class ListNode():
    def __init__(self, val):
        self.val = val
        self.next = None

# create a linklist
def createLinkList(li: list) -> ListNode:
    if not li:
        return 
    p = head = ListNode(li[0])
    
    for i in li[1:]:
        p.next = ListNode(i)
        p = p.next
    
    return head

# print linklist
def printLinkList(head: ListNode):
    if not head:
        print("This is an empty linklist!")
        return 
    while head:
        print(head.val, end="->")
        head = head.next
    print(None)
    
def mergeTwoSortedLinkList(head1, head2):
    if not head1:
        return head2
    elif not head2:
        return head1
    
    pMergeHead = None
    
    if head1.val < head2.val:
        pMergeHead = head1
        pMergeHead.next = mergeTwoSortedLinkList(head1.next, head2)
    else:
        pMergeHead = head2
        pMergeHead.next = mergeTwoSortedLinkList(head1, head2.next)
    
    return pMergeHead

# test
# 1. create two linklist
head1 = createLinkList([1, 3, 5, 7])
head2 = createLinkList([2, 4, 6, 8])
# 2. merge
mergeHead = mergeTwoSortedLinkList(head1, head2)
# 3. print merge linklist
printLinkList(mergeHead)
1->2->3->4->5->6->7->8->None

面试题18 树的子结构

【题目】:
输入两颗二叉树A和B,判断B是不是A的子结构。

【解题思路】:
还是利用递归的思想:

  1. 先找到A中与B的根节点相同的节点;
  2. 再判断其结构是否一样;
# define the tree
class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

# create a tree
def createTree(nums, index):
    if not nums:
        return None
    if index >= len(nums):
        return None
    
    root = TreeNode(nums[index])
    root.left = createTree(nums, 2*index+1)
    root.right = createTree(nums, 2*index+2)
    
    return root

# print tree
def preOrder(root):
    if not root:
        return 
    print(root.val, end=" ")
    preOrder(root.left)
    preOrder(root.right)

    
# Solution
def hasSubTree(root1, root2):
    result = False
    if root1 and root2:
        if root1.val == root2.val:      # has the same root value
            result = doseTree1HasTree2(root1, root2)
        if not result:
            result = hasSubTree(root1.left, root2)
        if not result:
            result = hasSubTree(root1.right, root2)
    
    return result

def doseTree1HasTree2(root1, root2):
    if not root2:
        return True
    if not root1:
        return False
    if root1.val != root2.val:
        return False
    
    return doseTree1HasTree2(root1.left, root2.left) and doseTree1HasTree2(root1.right, root2.right)


root1 = createTree([8,8,7,9,2,None,None,None,None,4,7], 0)
root2 = createTree([8,9,2], 0)
hasSubTree(root1, root2)
True

面试题19 二叉树的镜像

【题目】:
完成一个函数,输入一个二叉树,该函数输出它的镜像。

【解题思路】:
还是使用递归的思想:

  1. 先交换左右子树;
  2. 再对子树中的子树进行交换。
# define the tree
class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

# create a tree
def createTree(nums, index):
    if not nums:
        return None
    if index >= len(nums):
        return None
    
    root = TreeNode(nums[index])
    root.left = createTree(nums, 2*index+1)
    root.right = createTree(nums, 2*index+2)
    
    return root

# print tree
def preOrder(root):
    if not root:
        return 
    print(root.val, end=" ")
    preOrder(root.left)
    preOrder(root.right)
    
# Solution
def mirrorRecursively(root):
    if not root:
        return
    if not root.right and not root.left:  # if node is the leaf
        return 
    temp = root.left
    root.left = root.right
    root.right = temp
    
    mirrorRecursively(root.left)
    mirrorRecursively(root.right)
    
# test
root = createTree([8, 6, 10, 5, None, None, 11], 0)
pNode = root
print("Before:")
preOrder(root)
mirrorRecursively(pNode)
print("\nAfter:")
preOrder(root)
Before:
8 6 5 None 10 None 11 
After:
8 10 11 None 6 None 5 

面试题20 顺时针打印矩阵

【题目】:
输入一个矩阵,按照从外向里顺时针的顺序依次打印出每一个数字。如:
1 2 3
4 5 6
7 8 9
打印结果为: 1 2 3 6 9 8 7 4 5

【解题思路】:

  1. 按照一圈一圈的思想来进行打印,每次打印的起始位置是对角元素。
  2. 使用 columns > start2 and row > start2 来判断该元素有没有被打印。
def printMatrixCircle(nums):
    if not nums:
        return 
    row, col = len(nums), len(nums[0])
    
    start = 0
    while row > start*2 and col > start*2:
        printCircle(nums, start, row, col)
        start += 1
    print("End")
    
def printCircle(nums, start, row, col):
    endX = row - start
    endY = col - start
    
    # from left to right
    for i in range(start, endY):
        print(nums[start][i], end="->")
    
    # up to down
    if start < endX-1:
        for i in range(start+1, endX):
            print(nums[i][endY-1], end="->")
            
    # left to right
    if start < endX-1 and start < endY-1:
        for i in range(endY-1-1, start-1, -1):
            print(nums[endX-1][i], end="->")
    
    # down to up
    if start < endY-1 and start < endX-1:
        for i in range(endX-1-1, start, -1):
             print(nums[i][start], end="->")
                
# test
nums = [[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]]

nums2 = [[1,2,3,4],
        [5,6,7,8],
        [9,10,11,12]]

nums3 = [[1,2,3,4],
        [5,6,7,8],
        [9,10,11,12],
        [13,14,15,16]]

nums4 = [[1,2,3]]

nums5 = [[1],
        [2],
        [3]]

nums6 = [[1]]

printMatrixCircle(nums)
printMatrixCircle(nums2)
printMatrixCircle(nums3)
printMatrixCircle(nums4)
printMatrixCircle(nums5)
printMatrixCircle(nums6)
1->2->3->6->9->8->7->4->5->End
1->2->3->4->8->12->11->10->9->5->6->7->End
1->2->3->4->8->12->16->15->14->13->9->5->6->7->11->10->End
1->2->3->End
1->2->3->End
1->End

面试题21 包含min函数的栈

【题目】:
定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。在该栈中调用min、push和pop的时间复杂度都是O(1)。

【解题思路】:
使用辅助栈来保存当前状况下的最小值。

class myStack():
    def __init__(self):
        self.stack = []
        self.min = []
        self.count = 0
    
    def pop(self):
        if self.count == 0:
            print("The stack is empty!")
        else:
            print(self.stack.pop(-1))
            self.min.pop(-1)
            self.count -= 1
    
    def push(self, val):
        if self.count == 0:
            self.stack.append(val)
            self.min.append(val)
        else:
            self.stack.append(val)
            self.min.append(min(self.min[-1], val))
        self.count += 1
    
    def minValue(self):
        if self.count == 0:
            print("The stack is empty!")
        else:
            print("The minimize value of the stack is :", self.min[-1])

# test
t = myStack()
t.pop()
t.push(1)
t.push(2)
t.push(0)
t.push(4)
t.minValue()
t.pop()
t.minValue()
t.pop()
t.minValue()
t.pop()
t.minValue()
The stack is empty!
The minimize value of the stack is : 0
4
The minimize value of the stack is : 0
0
The minimize value of the stack is : 1
2
The minimize value of the stack is : 1

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

【题目】:
输入两个整数序列,第一个表示栈的压入序列,请判断第二个序列是否为改栈的弹出序列。
假设压入栈的所有数字均不相等!

【解题思路】:
利用辅助栈,模拟进栈与出栈。

def isPopOrder(pushes, pops):
    if not pushes or not pops or len(pushes) != len(pops):
        return False
    
    push_stack = []
    while pops:
        val = pops.pop(0)
        if push_stack and push_stack[-1] == val:
            push_stack.pop(-1)
        else:
            while pushes:
                nums = pushes.pop(0)
                if nums == val:
                    break
                else:
                    push_stack.append(nums)
        
    if not pops and not push_stack:
        return True
    else:
        return False
    
# test
pops = [4, 5, 3, 2, 1]
pushes = [1, 2, 3, 4, 5]
isPopOrder(pushes, pops)
True

面试题23 从上往下打印二叉树(层次遍历)

【题目】:
从上往下打印出二叉树的每一个节点同一层的节点按照从左到右的顺序打印。

【解题思路】:
使用队列。

# define the tree
class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

# create a tree
def createTree(nums, index):
    if not nums:
        return None
    if index >= len(nums):
        return None
    
    root = TreeNode(nums[index])
    root.left = createTree(nums, 2*index+1)
    root.right = createTree(nums, 2*index+2)
    
    return root

def printFromTopToBottom(root):
    if not root:
        return
    queue = [root]
    
    while queue:
        treeNode = queue.pop(0)
        if treeNode.left:
            queue.append(treeNode.left)
        if treeNode.right:
            queue.append(treeNode.right)
        
        print(treeNode.val, end=" ")
    
    print()

# test
# 1. create a tree
root = createTree([1,2,3,4,5,6], 0)
# print
printFromTopToBottom(root)
1 2 3 4 5 6 

面试题24 二叉搜索树的后序遍历序列

【题目】:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果,是返回True,不是返回False。
假设输入的数组任意两个数字都互不相同

【解题思路】:
在后序遍历中,最后一个数字就是树的根节点的值。而数组前面的部分可以分成两个部分:

  1. 第一部分是左子树节点的值,他们都比根节点的值小;
  2. 第二部分是右子树节点的值,他们都比根节点的值大。
def verifySquenceOfBST(nums: list) -> bool:
    if not nums:
        return False
    
    if len(nums) == 1:
        return True
    
    root = nums[-1]
    i, j = 0, 0
    # find the value in nums less than the root
    for i in range(len(nums)-1):
        if nums[i] > root:
            break
    
    # find the value in nums gather than the root
    for j in range(i, len(nums)-1):
        if nums[j] < root:
            return False
    
    # is left subtree a BST?
    left = True
    if i > 0:
        left = verifySquenceOfBST(nums[:i])
    
    # is right subtree a BST?
    right = True
    if i < len(nums)-1:
        right = verifySquenceOfBST(nums[i:len(nums)-1])
    
    return left and right
    
verifySquenceOfBST([5,7,6,9,11,10,8])
True

面试题25 二叉树中和为某一值的路径

【题目】:
输入一颗二叉树和一个整数,打印出二叉树中节点值和为输入整数的所有路径。
规定: 从树的根节点开始往下一直到叶节点所经过的节点形成一条路径

【解题思路】:
这道题可以利用前序遍历来做,纪录从根节点到叶子节点的所有值,如果路径上节点的和等于输入整数,就存下来。

# define the tree
class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

# create a tree
def createTree(nums, index):
    if not nums:
        return None
    if index >= len(nums):
        return None
    
    root = TreeNode(nums[index])
    root.left = createTree(nums, 2*index+1)
    root.right = createTree(nums, 2*index+2)
    
    return root

def findPath(root, expected):
    if not root:
        return
    
    res = []
    def recursive(root, expected, temp):
        temp.append(root.val)
        if root.left==None and root.right==None:
            if sum(temp) == expected:
                print(temp)

        
        if root.left != None:
            recursive(root.left, expected, temp)
        if root.right != None:
            recursive(root.right, expected, temp)
        # delete current treeNode before return to the parent node
        temp.pop(-1)
        
    recursive(root, expected, [])
    




# test
# 1. create tree
root = createTree([10, 5, 12, 4, 7], 0)
# 2. find path
findPath(root, 22)
[10, 5, 7]
[10, 12]

面试题26 复杂链表的复制

【题目】:
请实现函数ComplexListNode* Clone(ComplexListNode* pHead),复制一个复杂链表。在链表中,每个结点除了有一个m_pNext指针指向下一个节点外,还有一个m_pSibling指向链表中的任意节点或NULL。

【解题思路】:

  1. 根据原始链表中的每个节点N创建对应的节点N’,并链接在N的后面;
  2. 设置N’的m_pSibling;(假设N指向S,那么N’也要指向S’,而S’就在S的后面);
  3. 将这个长链表拆分成两个链表:把奇数位置上的节点链接起来就是原始链表,把偶数位上的节点连接起来就是复制后的链表;
# define the ComplexListNode
class ComplexListNode():
    def __init__(self, val):
        self.val = val
        self.next = None
        self.sibling = None

# create a complexlink
def createComplexLinkList():
    A = ComplexListNode("A")
    B = ComplexListNode("B")
    C = ComplexListNode("C")
    D = ComplexListNode("D")
    E = ComplexListNode("E")
    A.next = B; B.next = C; C.next = D; D.next = E
    A.sibling = C; B.sibling = E; D.sibling = B
    
    return A

# print complexLinkList
def printComplexLinkList(head):
    p = head
    while p:
        if p.sibling:
            print("{0}[{1}]".format(p.val, p.sibling.val), end="->")
        else:
            print("{0}".format(p.val), end="->")
        p = p.next
    print("End")

# Clone ComplexLinkList
def cloneNode(head):
    p = head
    while p:
        clone = ComplexListNode(0)
        clone.val = p.val+"'"
        clone.next = p.next
        
        p.next = clone
        p = clone.next
    
    return head

def connectSiblingNodes(head):
    p = head
    while p:
        clone = p.next
        if p.sibling:
            clone.sibling = p.sibling.next
        p = clone.next
    
    return head

def reconnectNodes(head):
    if not head:
        return 
    p = head
    cloneHead = cloneNode = p.next
    p.next = cloneNode.next
    p = p.next
    
    while p:
        cloneNode.next = p.next
        cloneNode = cloneNode.next
        p.next = cloneNode.next
        p = p.next
    
    return cloneHead
    
def clone(head):
    return reconnectNodes(connectSiblingNodes(cloneNode(head)))

# test
head = createComplexLinkList()
printComplexLinkList(clone(head))
# head = cloneNode(head)
# printComplexLinkList(head)
# head = connectSiblingNodes(head)
# printComplexLinkList(head)
# head = reconnectNodes(head)
# printComplexLinkList(head)
A'[C']->B'[E']->C'->D'[B']->E'->End

面试题27 二叉搜索树与双向链表

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

【解题思路】:
对于二叉搜索树使用中序遍历就能得到排序的结果;
利用递归的思想:
当遍历到根节点的时候,将他和左子树的最大节点链接起来;和右子树的最小节点链接起来。

# define the tree
class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

# create a tree
def createTree(nums, index):
    if not nums:
        return None
    if index >= len(nums):
        return None
    
    root = TreeNode(nums[index])
    root.left = createTree(nums, 2*index+1)
    root.right = createTree(nums, 2*index+2)
    
    return root

# print doubly LinkList
def printDoublyLinkList(head):
    if not head:
        return "Empty doubly Linklist!"
    
    print("left-to-right:")
    while head.left:
        print(head.val, end="->")
        head = head.left
    print("End\nrigth-to-left:")
    while head.right:
        print(head.val, end="->")
        head = head.right
    print("End")

    
    
def convertBST2BoublyLinklist(root):
    if not root:
        return None, None
    
    l1, r1 = convertBST2BoublyLinklist(root.left)
    left_most = l1 if l1 else root
    l2, r2 = convertBST2BoublyLinklist(root.right)
    right_most = r2 if r2 else root
    
    root.left = r1
    if r1:
        r1.right = root
    root.right = l2
    if l2:
        l2.left = root
    
    return left_most, right_most
    
    

        
# test
# 1. create a BST
root = createTree([10, 6, 14, 4, 8, 12, 16], 0)
# 2. convert to  doubly linklist
left_most, right_most = convertBST2BoublyLinklist(root)
# 3. print
printDoublyLinkList(right_most)
left-to-right:
16->14->12->10->8->6->End
rigth-to-left:
4->6->8->10->12->14->End

面试题28 字符串排列

【题目】:
输入一个字符串,打印出该字符串中所有字符的全排列,例如输入abc,输出abc, acb, bac, bca, cab, cba。

【解题思路】:

  1. 把字符串分成两个部分,一部分是字符串的第一个字符,另一个是字符串后的所有字符。
  2. 拿第一个字符与后面的字符逐个交换。
def Permutation(strings: str):
    if not str:
        return 
    
    def findAll(strings, index):
        if index >= len(strings):
            print("".join(strings), end=" ")
        else:
            for i in range(index, len(strings)):
                temp = strings[i]
                strings[i] = strings[index]
                strings[index] = temp
                
                findAll(strings, index+1)
                
                # recovery
                temp = strings[i]
                strings[i] = strings[index]
                strings[index] = temp
    
    findAll(list(strings), 0)
    
Permutation("abc")
abc acb bac bca cba cab 

面试题28-拓展1 字符组合

【题目】:
如果不是求字符的排列而是求字符的所有组合。即"abc"的所有组合为:a, b, c, ab, ac, bc, abc。

【解题思路】:
对于n个字符求长度为m(m<=n)的组合时,也可以先将字符串分成两个部分:第一个字符和所有字符:

  1. 如果组合中不包含第一个字符,就在剩余的n-1个字符中选取m个字符;
  2. 如果组合中包含第一个都反映,则在剩余的字符中选取m-1个字符。
def Combine(strings: str):
    if not strings:
        return
    
    def findAll(strings, m, index, temp):
        if m == 0:
            print(temp, end=" ")
        else:
            for i in range(index, len(strings)-m+1):
                a = temp.copy()
                a.append(strings[i])
                findAll(strings, m-1, i+1, a)
    
    for i in range(1, len(strings)+1):
        findAll(list(strings), i, 0, [])
    
Combine("abc")
['a'] ['b'] ['c'] ['a', 'b'] ['a', 'c'] ['b', 'c'] ['a', 'b', 'c'] 

面试题29 数组中出现次数超过一半的数字

【题目】:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
如{1,2,3,2,2,2,5,4,2}, 2在数组中出现了5次,因此输出2。

【解题思路】:
一、基于Partition的O(n)解法:

  1. 数组中有数字出现的次数超过数组长度的一半,那么将其排序,【中位数】就是我们要找的数字;
  2. 利用O(n)的算法找数组中任意第k大的数字。
    a). 任意选择一个数字,将比他大的放在左边,比他小的放在右边;
    b). 如果这个选中的数字下标为n/2,那么他就是中位数;
    c). 如果大于n/2,则中位数在其左边,否则在其右边。

二、基于数组的特点:
在遍历数组的时候保存两个值:一个是数组中的一个数字;一个是次数。

  1. 当我们遍历到下一个数字的时候,如果与我们之前保存的数字相同,则次数+1;
  2. 如果下一个数字与上一个数字不同时,次数-1;
  3. 如果次数为0,我们需要保存下一个数字,并将次数设置为1;
    由于我们要找的数字比其他所有数字出现次数之和还要多,所以我们要找的数字就是最后一次将次数设置为1的那个数字
def partition(A, p, r):
    x = A[r]
    i = p - 1
    for j in range(p, r):
        if A[j] <= x:
            i += 1
            temp = A[j]
            A[j] = A[i]
            A[i] = temp
    temp = A[r]
    A[r] = A[i+1]
    A[i+1] = temp
    return i+1 

def quicksort(A, p, r):
    if p < r:
        print('before:', A, end="")
        q = partition(A, p, r)
        print('after:', q, A)
        quicksort(A, p, q-1)
        quicksort(A, q+1, r)

# method 1: use partition
def moreThanHalfNum1(nums):
    middle = len(nums) // 2
    start, end = 0, len(nums) - 1
    index = partition(nums, start, end)
    while index != middle:
        if index > middle:
            index = partition(nums, start, index-1)
        else:
            index = partition(nums, index+1, end)
    
    result = nums[middle]
    
    count = 0
    for i in nums:
        if i == result:
            count += 1
        if count > middle:
            return result
    
    return "No such number!"

# method 2: use the character of the array
def moreThanHalfNum2(nums):
    if not nums:
        return
    result, times = nums[0], 1
    for i in range(1, len(nums)):
        if times == 0:
            result = nums[i]
            times = 1
        elif nums[i] == result:
            times += 1
        else:
            times -= 1
    
    count = 0
    for i in nums:
        if i == result:
            count += 1
        if count > len(nums) >> 1:
            return result
    
    return "No such number!"

moreThanHalfNum2([1,2,3,3,2,2,2,5,2])
2

面试题30 最小的k个数

【题目】:
输入n个正整数,找出其中最小的k个数,如输入4, 5, 1, 6, 2, 7, 3, 8这8个数字,最小的4个数字是1,2,3,4。

【解题思路】:

  1. 可以先排序,然后再取前k个数字,时间复杂度:O(nlogn);
  2. 利用快排的思想找到第k大的数字,然后k右边的数字就是最小的k个数,这个方法的平均复杂度为O(n);
  3. 建立大小为k的辅助空间,依次从n中读入数据,用小的数据替换k中的最大的数据,遍历完成之后,k中就是最小的k个数。
# method 1: use partition
def partition(A, p, r):
    x = A[r]
    i = p - 1
    for j in range(p, r):
        if A[j] <= x:
            i += 1
            temp = A[j]
            A[j] = A[i]
            A[i] = temp
    temp = A[r]
    A[r] = A[i+1]
    A[i+1] = temp
    return i+1

def getLeastNumbers_Partition(nums, k):
    if not nums or k <= 0:
        return
    start, end = 0, len(nums)-1
    index = partition(nums, start, end)
    
    while index != k:
        if  index > k-1:
            index = partition(nums, start, index-1)
        else:
            index = partition(nums, index-1, end)
    
    return nums[:k]

# --------------------------------------------------------

def max_heapify(A, i, len):
    '''
    维护最大堆
    '''
    l = 2*i + 1
    r = 2*i + 2
    
    if l < len and A[l] > A[i]:
        largest = l
    else:
        largest = i
    if r < len  and A[r] > A[largest]:
        largest = r
    
    if largest != i:
        temp = A[i]
        A[i] = A[largest]
        A[largest] = temp
        max_heapify(A, largest, len)
    return A

def build_max_heapify(A):
    '''
    构建最大堆
    '''
    for i in range(len(A) // 2-1, -1, -1):
        max_heapify(A, i, len(A))
        

# method 2: use extra space
def getLeastNumbers_AssistSpace(nums, k):
    if not nums or k <= 0:
        return 
    k_help, count = [], 0
    for i in nums:
        if count < k:
            k_help.append(i)
            # 构建最大堆
            build_max_heapify(k_help)
            count += 1
        else:
            # 比较k_help中的最大值和i
            k_help[0] = min(k_help[0], i)
            # 维护最大堆
            max_heapify(k_help, 0, k)
    
    return k_help


# test
nums = [4, 5, 1, 6, 2, 7, 3, 8]
print("Method 1: use partition:", getLeastNumbers_Partition(nums, 4))
print("Method 2: use extra space:", getLeastNumbers_AssistSpace(nums, 4))
Method 1: use partition: [1, 2, 3, 4]
Method 2: use extra space: [4, 3, 2, 1]

面试题31 连续子数组的最大和

【题目】:
输入一个整型数组,数组里有正数也有负数。数组中一个或连续多个整数组成一个子数组,求所有子数组和的最大值。要求时间复杂度为O(n)。
如{1, -2, 3, 10, -4, 7, 2, -5}和最大的子数组为{3, 10, -4, 7, 2}。

【解题思路】:

  1. 分析数组的规律:
    从头到尾累加,如果发现累加的和小于0,则丢弃之前的数据,从新的数据开始累加;
    如果加入新的数据后,和下降,则保留原来最大的和。
  2. 利用动态规划:
    用函数f(i)表示以第i个数字结尾的字数组的最大和,最后只需要求出max(f(i)),可以利用如下的递推公式:
    f ( n ) = { D a t a [ i ] , i = 0   o r   f ( i − 1 ) < 0 f ( i − 1 ) + D a t a [ i ] , i ≠ 0   a n d   f ( i − 1 ) > 0 {f(n)} = \left\{ \begin{array}{l} Data[i],i = 0\ or\ f(i-1) < 0\\ f(i-1)+Data[i],i \neq 0\ and\ f(i-1) > 0 \end{array} \right. f(n)={Data[i],i=0 or f(i1)<0f(i1)+Data[i],i=0 and f(i1)>0
# method 1
def findGreatestSumOfSubArray(nums):
    if not nums:
        return
    
    greatestSum, curSum = -2**31, 0
    for val in nums:
        if curSum < 0:
            curSum = val
        else:
            curSum += val
        
        if curSum > greatestSum:
            greatestSum = curSum
    
    return greatestSum


# method 2: use DP
def findGreatestSumOfSubArray_DP(nums):
    if not nums:
        return 
    
    f = [0]*len(nums)
    for i, val in enumerate(nums):
        if i == 0 or f[i-1] <= 0:
            f[i] = val
        else:
            f[i] = f[i-1] + val
    
    return max(f)
            

# test
# nums = [1, -2, 3, 10, -4, 7, 2, -5]
nums = [-1, -2, -3, -4, -5]
print("Method 1:", findGreatestSumOfSubArray(nums))
print("Method 2:", findGreatestSumOfSubArray_DP(nums))
Method 1: -1
Method 2: -1

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

【题目】:
输入一个整数n,要求从1到n这n个整数的十进制表示中1出现的次数。
如输入12,则包含1的数为1, 10, 11, 12,故1出现的次数为5。

【解题思路】:
利用递归的思想:
如234:

  1. 0-4: 1的数量是1
  2. 4-34: 1的数量10+3X1=13,十位为1的数有10个,然后10位有三种取法1,2,3;只能取1,于是就有10+3X1;
  3. 34-234: 百位上是1的个数为: 1 0 ( l e n − 1 ) 10^{(len-1)} 10(len1),然后第一位可以取1,2,后面两位都可以从0取到9,选择一个位置取1,再将剩下的位置全排列:2X2X10.
    【所以1的数量为154】

这里指的注意的是:
如果最高位大于1,那么最高位为1的数量为 1 0 l e n − 1 10^{len-1} 10len1;
如果最高位等于1,那么最高位为1的数量为除去最高位的数+1,如123, 最高位为1的数有23+1=24个,即100-123。

def numberOf1(nums):
    if not nums:
        return 
    
    nums = str(nums)
    first = int(nums[0])
    
    length = len(nums)
    
    if length == 1 and first == 0:
        return 0
    if length == 1 and first > 0:
        return 1
    
    numFisrtDigit = 0
    if first > 1:
        numFisrtDigit = 10 ** (length - 1)
    elif first == 1:
        numFisrtDigit = int(nums[1:]) + 1

    numOtherDigits = first * (length - 1) * (10 ** (length - 2))
    
    numRecursive = numberOf1(nums[1:])
    
    return numFisrtDigit + numOtherDigits + numRecursive


        
numberOf1(234)
154

面试题33 把数组排列成最小的数

【题目】:
输入一个正整数数组,把数组中所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
如输入{3, 32, 321},最小的拼接数为321323。

【解题思路】:
思路一: 使用全排列来做,找出最小的值即可;
思路二: 《剑指offer》—把数组排成最小的数
大致思路:
(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)将字符串列表按照比较函数的规定进行冒泡排序(或其它方法排序),将定义为”大”的字符串放到最后。而”小”的字符串放在前面。最后将字符串列表链接起来,便是所求。

# define the compare function
def compare(str1, str2):
    combine1 = str1 + str2
    combine2 = str2 + str1
    return combine1 > combine2

def printMinNumber(nums):
    if not nums:
        return
    # convert to string
    nums = [str(i) for i in nums]
    
    # use bubble sort 
    for i in range(len(nums)-1):
        for j in range(len(nums)-i-1):
            if compare(nums[j], nums[j+1]):
                temp = nums[j]
                nums[j] = nums[j+1]
                nums[j+1] = temp
    
    return "".join(nums)

nums = [32, 32, 321]
nums = [121, 57, 101]
printMinNumber(nums)
'10112157'

面试题34 丑数

【题目】:
我们把只包含因子2,3和5的数称为丑数(Ugly Number)。求按从小到大的顺序的第1500个丑数。例如6,8是丑数,但是14不是。习惯上我们把1当作第一个丑数。

【解题思路】:
思路一: 写一个丑数的判断程序,然后遍历到指定个数的丑数即可【效率低】;
思路二:记录最新的2, 3, 5的倍数,然后选取最小的作为新的丑数,这样可以使丑数的生成是按照顺序的。

# Method 1, 
def isUglyNumber(num):
    if num < 1:
        return "Error"
    while num % 2 == 0:
        num = num / 2
    while num % 3 == 0:
        num = num / 3
    while num % 5 == 0:
        num = num / 5
    
    return True if num == 1else False

def getUglyNumber(index):
    if index < 1:
        return "Error"
    count, num = 0, 0
    while count < index:
        num += 1
        if isUglyNumber(num):
            count += 1
    
    return num


# Method 2
def getUglyNumber2(index):
    if index < 0:
        return
    currentUgly, multiply2, multiply3, multiply5 = [0]*index, 0, 0, 0
    currentUgly[0] = 1
    count = 1
    
    while count < index:
        currentUgly[count] = min(currentUgly[multiply2]*2, currentUgly[multiply3]*3, currentUgly[multiply5]*5)
        
        while currentUgly[multiply2]*2 <= currentUgly[count]:
            multiply2 += 1
        while currentUgly[multiply3]*3 <= currentUgly[count]:
            multiply3 += 1
        while currentUgly[multiply5]*5 <= currentUgly[count]:
            multiply5 += 1
            
        count += 1
    
    return currentUgly[-1]
  
# test
getUglyNumber2(1500)   # more efficient
# getUglyNumber2(1500) # slowly 859963392
859963392

面试题35 第一个只出现一次的字符

【题目】:
在字符串中找出第一个只出现一次的字符。如输入“abaccdeff”,输出“b”。

【解题思路】:
建立一个dict,用于存储字符串中各个字符出现的次数;然后再遍历一次字符串,直到找到第一个字符个数为1的字符输出。

def firstNotRepeatingChar(strings):
    if not strings:
        return
    charCounter = {}
    for s in strings:
        if s not in charCounter.keys():
            charCounter[s] = 1
        else:
            charCounter[s] += 1
    
    for s in strings:
        if charCounter[s] == 1:
            return s
    
    return "No such character!"

# test
firstNotRepeatingChar("abaccdeff")
'b'

面试题36 数组中的逆序对

【题目】:
在数组中的两个数字如果前面的一个数字大于后面的一个数字,则这两个数字组成一个逆序对。输入一个逆序对,求出这个数组中逆序对的个数。

【解题思路】:
利用归并排序的方式统计数组中的逆序对。

def inversePairs(data):
    if not data:
        return 
    copy = data.copy()
    return inversePairsCore(data, copy, 0, len(data)-1)

def inversePairsCore(data, copy, start, end):
#     print("start:", start, "end:", end, "copy:", copy)
    if start == end:
        copy[start] = data[start]
        return 0
    
    length = (end - start) // 2
    
    left = inversePairsCore(copy, data, start, start + length)
    right = inversePairsCore(copy, data, start+length+1, end)
    
    # 前半段最后一个数字的下标
    i = start + length
    # 后半段最后一个数字的下标
    j = end
    indexCopy = end
    count = 0
    
    while i >= start and j >= (start+length+1):
        if data[i] > data[j]:
            copy[indexCopy] = data[i]
            indexCopy -= 1; i -= 1;
            count += j - start - length
        else:
            copy[indexCopy] = data[j]
            indexCopy -= 1; j -= 1;
        
        
    while i >= start:
        copy[indexCopy] = data[i]
        indexCopy -= 1; i -= 1;
    while j >= start+length+1:
        copy[indexCopy] = data[j]
        indexCopy -= 1; j -= 1;
        
    print("start:", start, "end:", end, "left:", left, "right:", right, "count:", count, "copy:", copy)
    return left + right + count

# test
# data = [1, 2, 3, 4, 7, 6, 5]
data = [7, 5, 6, 4]
inversePairs(data)
start: 0 end: 1 left: 0 right: 0 count: 1 copy: [5, 7, 6, 4]
start: 2 end: 3 left: 0 right: 0 count: 1 copy: [5, 7, 4, 6]
start: 0 end: 3 left: 1 right: 1 count: 3 copy: [4, 5, 6, 7]





5

面试题37 两个链表的第一个公共节点

【题目】:
输入两个链表,找出他们的第一个公共节点。

【解题思路】:
方案一: 利用栈的思想,返回最后一个相同的节点;
方案二: 遍历链表得到长度,再将长链表遍历到短链表一样的长的位置,同时开始遍历,直到找到第一个相同的节点。

class ListNode():
    def __init__(self, val):
        self.val =  val
        self.next = None
        

# Method 1: use stack
def findFirstCommonNode_stack(head1, head2):
    if not head1 or not head2:
        return
    
    stack1, stack2 = [], []
    while head1:
        stack1.append(head1)
        head1 = head1.next
        
    while head2:
        stack2.append(head2)
        head2 = head2.next
    
    while stack1 and stack2:
        s1, s2 = stack1.pop(-1), stack2.pop(-1)
        if s1 == s2 and stack1[-1] != stack2[-1]:
            return s1
    
    return "No such Node!"

# Method 2: use length
def findFirstCommonNode_length(head1, head2):
    if not head1 or not head2:
        return
    p1, p2 = head1, head2
    
    # calculate the length of each linklist
    len1, len2 = 0, 0
    while p1:
        len1 += 1
        p1 = p1.next
    while p2:
        len2 += 1
        p2 = p2.next
    
    # go to the same length
    if len1 > len2:
        for i in range(len1-len2):
            head1 = head1.next
    else:
        for i in range(len2-len1):
            head2 = head.next
            
    while head1 and head2 and head1 != head2:
        head1 = head1.next
        head2 = head2.next
    
    return head1
    


# test
def createTestSample():
    A = ListNode("A")
    B = ListNode("B")
    C = ListNode("C")
    D = ListNode("D")
    E = ListNode("E")
    F = ListNode("F")
    G = ListNode("G")
    A.next = B; B.next = C; C.next = F; F.next = G;
    D.next = E; E.next = F; F.next = G;
    
    return A, D

head1, head2 = createTestSample()
common_stack = findFirstCommonNode_stack(head1, head2)
common_length = findFirstCommonNode_length(head1, head2)
print("Use stack:", common_stack.val)
print("Use length:", common_length.val)
Use stack: F
Use length: F

面试题38 数字在排序数组中出现的次数

【题目】:
统计一个数字在排序数组中出现的次数。例如输入一个排序数组{1, 2, 3, 3, 3, 3, 4, 5}和数字3,输出结果为4。

【解题思路】:
分别使用二分法找到第一个和最后一个目标数的位置。

def findFirstK(nums, k, start, end):
    if not nums:
        return
    if start > end:
        return -1
    
    mid = (start + end) // 2
    
    if nums[mid] == k:
        if mid > 0 and nums[mid-1] != k or mid == 0:
            return mid
        else:
            end = mid - 1
    elif  nums[mid] > k:
        end = mid - 1
    else:
        start = mid + 1
    
    return findFirstK(nums, k, start, end)

def findLastK(nums, k, start, end):
    if not nums:
        return
    if start > end:
        return -1
    
    mid = (start + end) // 2
    
    if nums[mid] == k:
        if mid < len(nums)-1 and nums[mid+1] != k or mid == len(nums)-1:
            return mid
        else:
            start = mid + 1
    elif  nums[mid] > k:
        end = mid - 1
    else:
        start = mid + 1
    
    return findLastK(nums, k, start, end)

def getNumberOfK(nums, k):
    if not nums:
        return
    
    first = findFirstK(nums, k, 0, len(nums)-1)
    last = findLastK(nums, k, 0, len(nums)-1)
    
    if first != -1 and last != -1:
        return last - first + 1
    else:
        return 0

# test
nums = [1,2,3,3,3,3,4,5,6]
getNumberOfK(nums, 3)
4

面试题39 二叉树的深度

【题目】:
输入一颗二叉树的根节点,求该二叉树的深度。二叉树的深度是从根节点到叶子节点所形成的最长的路径。

【解题思路】:
典型的递归的题目,分别求取左子树和右子数的深度,然后将最大的值加1进行返回。

class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None
        
def treeDepth(root):
    if not root:
        return 0
    
    left = treeDepth(root.left)
    right = treeDepth(root.right)
    
    return max(left, right) + 1

# test
# create a tree
def createTree(nums, index):
    if not nums:
        return None
    if index >= len(nums):
        return None
    
    root = TreeNode(nums[index])
    root.left = createTree(nums, 2*index+1)
    root.right = createTree(nums, 2*index+2)
    
    return root

root = createTree([1, 2, 3, None, 5, None, 6, None, None, 7], 0)
res = True
treeDepth(root)

4

面试题39-拓展 判断二叉树的是否为平衡二叉树

【题目】:
输入一个二叉树,判断这个二叉树是不是平衡二叉树。平衡二叉树是指任意节点左右子树高度之差不超过1。

【解题思路】:
在遍历的过程中,判断每个节点是不是平衡二叉树,返回高度和判断信息。

class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

def isBalance(root, depth):
    if not root:
        return (True, 0)
    
    left = isBalance(root.left, depth)
    right = isBalance(root.right, depth)
    
    return (left[0] & right[0] & (abs(left[1] - right[1]) > 1), max(left[1], right[1]) + 1)

# test
# create a tree
def createTree(nums, index):
    if not nums:
        return None
    if index >= len(nums):
        return None
    
    root = TreeNode(nums[index])
    root.left = createTree(nums, 2*index+1)
    root.right = createTree(nums, 2*index+2)
    
    return root

root = createTree([1, 2, 3, None, 5, None, 6, None, None, 7], 0)
res = isBalance(root, 0)
print("Depth =", res[1], "isBalance =", res[0])
Depth = 4 isBalance = False

面试题40 数组中只出现一次的数字

【题目】:
一个整数数组中,除了两个数字外,其他的数字都出现了2次。请编程找出只出现一次的数字。
要求: 时间复杂度: O ( n ) O(n) O(n),空间复杂度: O ( 1 ) O(1) O(1)

【解题思路】:
可以使用异或,因为最多数字出现2次,那么重复的数字在异或运算中就会消失。
对于数组中只有一个只出现一次的数:直接异或每一个数字,最终的结果就是只出现一次的数;
如果数组中有两个只出现一次的数:将数组分成两个部分,即每个部分都只包含一个重复的数。其方法为:

  1. 先将所有数进行异或,找到最终异或运算结果的二进制中第一个1出现的位置,记作第n位;
  2. 根据第n位置是不是1,将数组分成两组,最后将这两个子数组进行异或运算,就能得到只出现一次的两个数。
def findNumsAppearOnce(nums):
    if not nums:
        return
    OR_result = 0
    for i in nums:
        OR_result ^= i
    
    indexOf1 = findFirstBitOf1(OR_result)
    
    num1, num2 = 0, 0
    
    for i in nums:
        if isBit1(i, indexOf1):
            num1 ^= i
        else:
            num2 ^= i
    
    return num1, num2

def findFirstBitOf1(OR_result):
    indexBit = 0
    while OR_result & 1 == 0:
        OR_result = OR_result >> 1
        indexBit += 1
    return indexBit

def isBit1(num, indexOf1):
    num = num >> indexOf1
    return num & 1

# test
nums = [2, 4, 3, 6, 3, 2, 5, 5]
findNumsAppearOnce(nums)
(6, 4)

面试题41 和为s的两个数字VS和为s的连续正数序列

【题目一】:
输入一个递增排序的数组和一个数s,在数组中查找两个数,使得他们的和正好是s。如果有多对数字的和为s,输出任意一对即可。

【题目二】:
输入一个正整数s,打印出所有和为s的连续正数序列(至少包含两个数)。如输入15,则1+2+3+4+5=4+5+6=7+8=15,所以输出3个连续序列1-5,4-6,7-8。

【解题思路】:
题目一: 利用双指针的思想解题即可;
题目二: 还是利用双指针,先递增右指针(指向较大的数),直到和等于target,如果大于target,则递增左指针(指向较小的数),直到满足要求或者左指针指向 1 + t a r g e t 2 \frac{1+target}{2} 21+target

# Question 1
def findNumbersWithSum(nums, target):
    if not nums:
        return
    start, end = 0, len(nums)-1
    
    while start < end:
        if nums[start] + nums[end] == target:
            return [nums[start], nums[end]]
        elif nums[start] + nums[end] > target:
            end -= 1
        else:
            start += 1
    
    return "No such two numbers!"

# Quesetion 2
def findContinuousSequence(target):
    if target < 3:
        return
    small, big = 1, 2
    mid = (1 + target) // 2
    curSum = small + big
    
    while small < mid:
        if curSum == target:
            print([i for i in range(small, big+1)])
            big += 1
            curSum += big
        elif curSum > target:
            curSum -= small
            small += 1
        else:
            big += 1
            curSum += big


# test
nums = [1, 2, 4, 7, 11, 15]
findNumbersWithSum(nums, 15)
findContinuousSequence(100)
[9, 10, 11, 12, 13, 14, 15, 16]
[18, 19, 20, 21, 22]

面试题42 翻转单词顺序 VS 左旋转字符串

【题目一】:
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变,为简单起见,标点符号和普通字符一样处理。如输入I am a student. 输出:student. a am I。

【题目二】:
字符串的左旋操作是将字符串前面的若干个字符串转移到字符串的尾部,如输入"abcdefg"和数字2,输出结果为"cdefgab"。

【解题思路】:
题目一: 在python中,可以先将句子按空格进行划分,再反序输出即可。通用解法是先整体翻转,然后再将每个单词翻转回来。
题目二: 在python中,可以利用list切片来进行反转。通用解法是先反转前面n个字符,再反转后面的字符,最后总体进行反转。

# Question 1 - Method  1
def reverseSentence_python(sentence):
    if not sentence:
        return
    s = sentence.split(" ")
    
    res = ""
    for i in range(len(s)-1, 0, -1):
        res += s[i]
        res += " "
    res += s[0]
    
    return res

# Question 1 - Method 2
def reverse(sentence, start, end):
    if not sentence or start > end:
        return sentence
    while start <= end:
        temp = sentence[start]
        sentence[start] = sentence[end]
        sentence[end] = temp
        
        start += 1; end -= 1;
    
    return sentence

def reverseSentence_common(sentence):
    if not sentence:
        return
    sentence = list(sentence)
    start, end = 0, len(sentence)-1
    # reverse whole sentence
    sentence = reverse(sentence, start, end)
    
    start, end = 0, 0
    while end < len(sentence):
        if sentence[end] != " ":
            end += 1
        else:
            sentence = reverse(sentence, start, end-1)
            start = end = end + 1
    
    return "".join(sentence)

# Question 2 - Method 1
def leftRotateString_python(sentence, n):
    if not sentence or n > len(sentence):
        return
    sentence = sentence[n:] + sentence[:n]
    return sentence

def leftRotateString_common(sentence, n):
    if not sentence or n > len(sentence):
        return
    
    if n == 0:
        return sentence
    
    sentence = list(sentence)
    
    # reverse the first n characters
    sentence = reverse(sentence, 0, n-1)
    # reverse the last characters
    sentence = reverse(sentence, n, len(sentence)-1)
    # reverse the whole sentence
    sentence = reverse(sentence, 0, len(sentence)-1)
    
    return "".join(sentence)

# test
sentence = "I am a student."
print("Q1_Pyhton:", reverseSentence_python(sentence))
print("Q1_Common:", reverseSentence_common(sentence))
sentence = "abcdefg"
print("Q2_Pyhton:", leftRotateString_python(sentence, 7))
print("Q2_Common:", leftRotateString_common(sentence, 7))
Q1_Pyhton: student. a am I
Q1_Common: student. a am I
Q2_Pyhton: abcdefg
Q2_Common: abcdefg

面试题43 n个骰子的点数

【题目】:
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能值出现的概率。

【解题思路】:
递归法: 建立一个6n-n+1的数组存储和为s的点数出现的次数存储到s-n的位置中。【时间效率低】
循环法:

# Method 1
def printProbability(number):
    if number < 1:
        return
    maxSum = number*6
    probabilities = [0]*(6*number-number+1)
    
    probability(number, probabilities)
    
    sumProb = sum(probabilities)
    for i in range(number, maxSum+1):
        print("{0}:{1}".format(i, probabilities[i-number] / sumProb))
    
def probability(number, prob):
    for i in range(1, 6+1):
        prob_recursive(number, number, i, prob)

def prob_recursive(original, current, sum, prob):
    if current == 1:
        prob[sum - original] += 1
    else:
        for i in range(1, 6+1):
            prob_recursive(original, current-1, i+sum, prob)

# Method 2
def printProbability_iterative(number):
    if number < 1:
        return
    prob = [0]*2
    prob[0] = [0]*(6*number+1)
    prob[1] = [0]*(6*number+1)
    
    flag = 0
    
    for i in range(1, 6+1):
        prob[flag][i] = 1
    
    for k in range(2, number+1):
        for i in range(k):
            prob[1-flag][i] = 0
        for i in range(k, k*6+1):
            prob[1-flag][i] = 0
            for j in range(1, i+1):
                if j > 6:
                    break
                prob[1-flag][i] += prob[flag][i-j]
        flag = 1 - flag
    
    probabilities = prob[flag]
    sumProb = sum(probabilities)
    for i in range(number, 6*number+1):
        print("{0}:{1}".format(i, probabilities[i] / sumProb))
    
# test
print("Method 1:")
printProbability(2)
print("Method 2:")
printProbability_iterative(2)
Method 1:
2:0.027777777777777776
3:0.05555555555555555
4:0.08333333333333333
5:0.1111111111111111
6:0.1388888888888889
7:0.16666666666666666
8:0.1388888888888889
9:0.1111111111111111
10:0.08333333333333333
11:0.05555555555555555
12:0.027777777777777776
Method 2:
2:0.027777777777777776
3:0.05555555555555555
4:0.08333333333333333
5:0.1111111111111111
6:0.1388888888888889
7:0.16666666666666666
8:0.1388888888888889
9:0.1111111111111111
10:0.08333333333333333
11:0.05555555555555555
12:0.027777777777777776

面试题44 扑克牌的顺子

【题目】:
从扑克牌中随机抽取5张牌,判断是不是个顺子,即这5张牌是不是连续的。2-10为数字本身,A为1,J为11,Q为12,K为13,大小王为任意数字。

【解题思路】:
先将扑克牌除去大小王外定义成2-13的数字,然后大小王设置为0,将数组排序后,用0去填补空缺。主要步骤有:

  1. 先将数组排序;
  2. 统计0和空缺的个数,如果0的个数大于等于空缺的个数,就为顺子,否则不是;
  3. 如果出现对子,那么一定不是连续的。
def isContinuous(nums):
    if not nums:
        return False
    
    nums.sort()
    numOfZero, numOfGap = 0, 0
        
    # count the number of the zero
    for i in nums:
        if i == 0:
            numOfZero += 1
    
    # count the number of gap
    small = numOfZero
    big = small + 1
    while big < len(nums):
        # if the nums have the same value,return false
        if nums[small] == nums[big]:
            return False
        numOfGap += nums[big] - nums[small] - 1
        small = big
        big += 1
    
    return True if numOfZero >= numOfGap else False

# test
nums1 = [2,3,4,1,6]
nums2 = [0,0,4,7,9]
nums3 = [0,4,3,5,1]
nums4 = [0,0,1,1,2]

print("test1:", isContinuous(nums1))
print("test2:", isContinuous(nums2))
print("test3:", isContinuous(nums3))
print("test4:", isContinuous(nums4))
    
test1: False
test2: False
test3: True
test4: False

面试题45 圆圈中最后剩下的数字

【题目】:
0,1,2,3,…,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈中删除第m个数字。求圆圈中剩下的最后一个数字。
例如由0,1,2,3,4这5个数字组成一个环,从0开始每次删除第3个数字,则删除的前4个数字是2, 0, 4, 1,最后剩下的一个数字是3

【解题思路】:
这是有名的约瑟夫环问题

  1. 用环形链表模拟圆圈;
  2. 分析被删除数据的规律:
    a. 首先定义一个函数f(m,n),表示每次在n个数字0~n-1中每次删除第m个数字最后剩下的数字,删除的数字为k=(m-1)%n;
    b. 下次应该从序列k+1, k+2, … , n-1, 0, 1, …, k-1中删除第m个数字,起映射关系可以写为: P ( x ) = ( x − k − 1 ) % n P(x) = (x-k-1)\%n P(x)=(xk1)%n。如果映射前的数是x,那么映射后的数就是(x-k-1)%n,其逆映射为 P − 1 ( x ) = ( x + K + 1 ) % n P^{-1}(x) = (x+K+1)\%n P1(x)=(x+K+1)%n;
    c. f ( n , m ) = f ′ ( n − 1 , m ) = P − 1 [ f ( n − 1 , m ) ] = [ f ( n − 1 , m ) + k + 1 ] % n = [ f ( n − 1 , m ) + m ] % n f(n,m) = f^{'}(n-1,m)=P^{-1}[f(n-1, m)]=[f(n-1, m)+k+1]\%n=[f(n-1, m)+m]\%n f(n,m)=f(n1,m)=P1[f(n1,m)]=[f(n1,m)+k+1]%n=[f(n1,m)+m]%n
    d. 递推公式可以写为:
    f ( n , m ) = { 0 ,         n = 1 [ f ( n − 1 , m ) + m ] % n ,   n > 1 {f(n, m)} = \left\{ \begin{array}{l} 0, \ \ \ \ \ \ \ n=1\\ [f(n-1, m)+m]\%n,\ n> 1 \end{array} \right. f(n,m)={0,       n=1[f(n1,m)+m]%n, n>1
# Method 1: use circle linklist(list instead)
def lastRemaining(n, m):
    if n < 1 or m < 1:
        return
    
    nums = [i for i in range(n)]
    
    index = 0
    while len(nums) > 1:
        index = (index + m - 1) % len(nums)
        nums.remove(nums[index])
    
    return nums[0]

# Method 2
def lastRemaining2(n, m):
    if n < 1 or m < 1:
        return
    
    last = 0
    for i in range(2, n+1):
        last = (last + m) % i
        
    return last
    
# test
import time
start = time.time()
print("Method 1:", lastRemaining(40000, 997), end="  time=")
end = time.time()
print(end-start)

start = time.time()
print("Method 2:", lastRemaining2(40000, 997), end="  time=")
end = time.time()
print(end-start)
Method 1: 26856  time=6.141265392303467
Method 2: 26856  time=0.003548860549926758

面试题46 求1+2+3+…+n

【题目】:
求1+2+3+…+n。要求不能用乘法除法、for、while、if-else、switch-case等关键字以及条件判断语句A?B:C。

【解题思路】:
【C++】

  1. 使用构造函数,创建n个构造函数的实例;
  2. 利用虚函数;
  3. 利用函数指针;
  4. 利用模板类型求解。
# 使用了函数指针,当n减小为0的时候,return 0,其余都是直接相加
def solution_teminator(n):
    return 0

def sum_solution(n):
    d={False: solution_teminator, True: sum_solution}
    return n + d[not not n](n-1)

sum_solution(100)
5050

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

【题目】:
写一个函数,求两个整数之和,要求在函数体内不能使用四则运算符号。

【解题思路】:
如果不能使用四则运算,则需要采用位运算来实现加法:

  1. 先将两个数做异或运算;
  2. 再将两个数进行与运算,并左移一位形成进位,直到不产生进位为止。
def Add(num1, num2):
    sum, carry = 0, 0
    while num2 != 0:
        sum = num1 ^ num2
        carry = (num1 & num2) << 1
        
        num1 = sum
        num2 = carry
    
    return num1

# test
Add(0, -9)
-9

面试题48 不能被继承的类

用c++来设计一个不能被继承的类:

  1. 把构造函数设置为私有函数;
  2. 利用虚拟继承;

面试题49 把字符串转化为整数

【题目】:
输入一个字符串,将这个字符串转化为整数。

【解题思路】:
问题不难,但是需要考虑到的边界条件很多:

  1. 输入是否合法?
  2. 输出是否越界?
  3. 结果是否移除?
def strToInt(string):
    if not string:
        return "The string is empty"
    
    minus = False
    num = 0
    
    for i, s in enumerate(string):
        if i == 0:
            if s == "-":
                minus = True
            elif s == "+":
                minus = False
            elif s.isdigit():
                num = int(s)
            else:
                return "illegal input!"
        else:
            if s.isdigit():
                num = num * 10 + int(s)
            else:
                return "illegal input!"
    
    return max(-num, -2**31) if minus else min(num, 2**31-1)
    
# test
strToInt("-1231123")
-1231123

面试题50 树中两个结点的最低公共祖先

【题目】:
树种两个节点的最低公共祖先是一组题:
题1: 如果输入的是搜索二叉树,找到指定两个节点的最低公共祖先;
题2: 如果输入的是一颗普通的树,但是每个节点都有指向父节点的指针;
题3: 如果输入的是一颗普通的树,且没有指向父节点的指针;

【解题思路】:
题1
搜索二叉树的左子树的节点比父节点小,右子树的节点比父节点大。这时只需要从根节点出发,和两个节点进行比较,当前节点比两个节点值都大,那么最低公共祖先在左子树上,否则在右子树上。这样找的第一个在两者之间的节点就是最低公共祖先。

题2
如果输入的是一颗普通的树,但是每个节点都有指向父节点的指针。可以将题目转化为链表的第一个公共节点。每个节点都有指向父节点的指针,那么形成的链表的尾指针都是树的公共节点。那么最低公共祖先就是两个链表的第一个公共节点。

题3
如果输入的是一颗普通的树,且没有指向父节点的指针。可以利用额外的内存。建立两个链表用于存储从根节点到目标节点的路径,然后找到这两条路径的最后的公共节点就是最低公共祖先。

class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None
        self.parent = None
        
class LisNode():
    def __init__(self, val):
        self.val = val
        self.next = None
        
# Question 1: if the tree is a BST
def findLastCommonParent_BST(root, node1, node2):
    if not root or not node1 or not node2:
        return
    val1, val2 = node1.val, node2.val
    
    while True:
        if root.val > max(val1, val2):
            root = root.left
        elif root.val < min(val1, val2):
            root = root.right
        else:
            return root

# Quesetion 2: a normal tree with a parent pointer
def findLastCommonParent_parentPtr(root, node1, node2):
    if not root or not node1 or not node2:
        return
    p1, p2 = node1, node2
    len1, len2 = 0, 0
    
    # calculate the length from node to root
    while p1:
        len1 += 1
        p1 = p1.parent
    while p2:
        len2 += 1
        p2 = p2.parent
    
    # the two link go the same length
    if len1 > len2:
        for i in range(len1-len2):
            node1 = node1.parent
    else:
        for i in range(len2 - len1):
            node2 = node2.parent
    
    # find the first common node
    while node1 != node2:
        node1 = node1.parent
        node2 = node2.parent
    
    return node1

# Question 3: a normal tree without parent pointer
def findLastCommonParent_WithoutParentPtr(root, node1, node2):
    if not root or not node1 or not node2:
        return
    
    def findPath(root, node, path):
        if not root:
            return False
        if root == node:
            return True
        
        path.append(root)
        
        found = False
        if root.left:
            found = findPath(root.left, node, path)
        if not found and root.right:
            found = findPath(root.right, node, path)
        if not found:
            path.pop(-1)
        
        return found
    
    # find the path from root to node1 and node2
    path1, path2 = [], []
    findPath(root, node1, path1)
    findPath(root, node2, path2)
    
    longPath, shortPath = path1, path2
    if len(path1) < len(path2):
        longPath = path2
        shortPath =  path1
    
    found = False
    for i in range(len(shortPath)):
        if longPath[i] != shortPath[i]:
            found = True
            break
    
    return shortPath[i-1] if found else shortPath[i]
    
        
# test
# Create  a test sample
def createTestSample():
    '''
         A(5)
        /    \
      B(3)    C(7)
       / \     /  \
    D(2) E(4) F(6) G(8)
      /
    H(1)
    '''
    A = TreeNode(5)
    B = TreeNode(3)
    C = TreeNode(7)
    D = TreeNode(2)
    E = TreeNode(4)
    F = TreeNode(6)
    G = TreeNode(8)
    H = TreeNode(1)
    A.left = B; A.right = C;
    B.left = D; B.right = E; B.parent = A;
    C.left = F; C.right = G; C.parent = A;
    D.left = H; D.parent = B; H.parent = D;
    E.parent = B; F.parent = C; G.parent = C;
    
    return A, H, E

root, node1, node2 = createTestSample()
# 1. test for the findLastCommonParent_BST
commonNode = findLastCommonParent_BST(root, node1, node2)
print("findLastCommonParent_BST:", commonNode.val)
# 2. test for a common tree with parent pointer 
commonNode = findLastCommonParent_parentPtr(root, node1, node2)
print("findLastCommonParent_parentPtr:", commonNode.val)
# 3. test for a common tree without parent pointer
commonNode = findLastCommonParent_WithoutParentPtr(root, node1, node2)
print("findLastCommonParent_WithoutParentPtr:", commonNode.val)
findLastCommonParent_BST: 3
findLastCommonParent_parentPtr: 3
findLastCommonParent_WithoutParentPtr: 3

面试题51 数组中重复的数字

【题目】:
在一个长度为n的数组中,所有数字都在0-n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。如{2, 3, 1, 0, 2, 5, 3},那么对应输出的重复数字是2,或者3。

【解题思路】:
思路一:
利用dict,判断数字是否已经出现过,已经出现过的数字为重复数据,直接返回。【需要额外的内存】

思路二:

  1. 以此扫描这个数组,判断当前数字m与下标index是否相等,不相等则与下标为m的数字进行交换;
  2. 直到发现一个重复的数字;【时间复杂度: O ( n ) O(n) O(n), 空间复杂度: O ( 1 ) O(1) O(1)
# Method 1
def findDuplicate_extraMemory(nums):
    if not nums:
        return
    
    record = {}
    
    for i in nums:
        if i not in record.keys():
            record[i] = 0
        else:
            return i
    
    return "No such number!"

# Method 2
def findDuplicate_OneExtraMemory(nums):
    if not nums:
        return 
    
    # check the numbers of the array
    for i in nums:
        if i < 0 or i > len(nums) - 1:
            return "Illegal input, the number must between 0 and len(nums)-1!"
        
    # find the first duplicate number
    for i in range(len(nums)):
        while nums[i] != i:
            if nums[i] == nums[nums[i]]:
                return nums[i]
            temp = nums[i]
            nums[i] = nums[temp]
            nums[temp] = temp
        
    return "No such number!"

# test 
nums = [2, 3, 1, 0, 2, 5, 3]
print("Mehtod 1:", findDuplicate_extraMemory(nums))
print("Method 2:", findDuplicate_OneExtraMemory(nums))
Mehtod 1: 2
Method 2: 2

面试题52 构建乘积数组

【题目】:
给定一个数组 A [ 0 , 1 , . . . , n − 1 ] A[0, 1, ..., n-1] A[0,1,...,n1],请构建一个数组 B [ 0 , 1 , . . . , n − 1 ] B[0, 1, ..., n-1] B[0,1,...,n1],其中B的元素 B [ i ] = A [ 0 ] ∗ A [ 1 ] ∗ . . . ∗ A [ i − 1 ] ∗ A [ i + 1 ] ∗ . . . ∗ A [ n − 1 ] B[i] = A[0] * A[1] * ... *A[i-1] * A[i+1] * ... *A[n-1] B[i]=A[0]A[1]...A[i1]A[i+1]...A[n1]。不能使用除法

【解题思路】:

  1. 可以将 B [ i ] B[i] B[i]拆成两个部分:
    B [ i ] = A [ 0 ] ∗ A [ 1 ] ∗ . . . ∗ A [ i − 1 ] ∗ A [ i + 1 ] ∗ . . . ∗ A [ n − 1 ] = ( A [ 0 ] ∗ A [ 1 ] ∗ . . . ∗ A [ i − 1 ] ) ∗ ( A [ i + 1 ] ∗ . . . ∗ A [ n − 1 ] ) = C [ i ] ∗ D [ i ] 其 中 : C [ i ] = A [ 0 ] ∗ A [ 1 ] ∗ . . . ∗ A [ i − 1 ] D [ i ] = A [ i + 1 ] ∗ . . . ∗ A [ n − 1 ] B[i] = A[0] * A[1] * ... *A[i-1] * A[i+1] * ... *A[n-1] \\ = (A[0] * A[1] * ... *A[i-1]) * ( A[i+1] * ... *A[n-1]) \\ = C[i] * D[i] \\ 其中: C[i] = A[0] * A[1] * ... *A[i-1] \\ D[i] = A[i+1] * ... *A[n-1] B[i]=A[0]A[1]...A[i1]A[i+1]...A[n1]=(A[0]A[1]...A[i1])(A[i+1]...A[n1])=C[i]D[i]C[i]=A[0]A[1]...A[i1]D[i]=A[i+1]...A[n1]

  2. C[i]可以自上而下的计算: C [ i ] = C [ i − 1 ] ∗ A [ i − 1 ] C[i] = C[i-1] * A[i-1] C[i]=C[i1]A[i1],其中: C [ 0 ] = 1 C[0] = 1 C[0]=1

  3. D[i]可以自下而上的计算: D [ i ] = D [ i + 1 ] ∗ A [ i + 1 ] D[i] = D[i+1] * A[i+1] D[i]=D[i+1]A[i+1],其中: D [ n − 1 ] = 1 D[n-1] = 1 D[n1]=1

def multipy(A):
    if not A:
        return
    
    B = [0]*len(A)
    
    B[0] = 1
    for i in range(1, len(A)):
        # calculate C[i]
        B[i] = B[i-1] * A[i-1]
    
    temp = 1
    for i in range(len(A)-2, -1, -1):
        temp = temp * A[i+1]
        B[i] = B[i] * temp
    
    return B

# test
A = [1, 2, 3, 4, 5]
print(multipy(A))
[120, 60, 40, 30, 24]

面试题53 正则表达式匹配

【题目】:
请实现一个函数用来匹配包含’,‘和’*‘的正则表达式。模式中的任意字符’.'表示任意一个字符,而‘*’表示他前面的字符可以出现任意次(含0次)。在本题中匹配是指字符串的所有字符匹配整个模式。如"aaa"匹配"a.a"和"ab*ac*a"但是与"aa.a"和"ab*a"均不匹配。

【解题思路】:

  1. 如果字符串的字符是ch,而模式中的字符也是ch或者“.”,则两者匹配;
  2. 如果模式中的第二字符是“*”:
    a. 模式跳过两个字符,表示忽略*;
    b. 模式跳过两个字符,输入字符进入下一个字符,表示重复一次*前面的字符;
    c. 模式不变,输入字符进入下一个字符,表示重复n次*前面的字符。
    最后返回这三者结果的或。
def match(string, pattern):
    if string == None or pattern == None:
        return False
    return matchCore(string, pattern)

def matchCore(string, pattern):
    if len(string) == 0 and len(pattern) == 0:
        return True
    if len(string) != 0 and len(pattern) == 0:
        return False

    if len(pattern) > 1 and pattern[1] == "*":
        if len(string) > 0 and pattern[0] == string[0] or (pattern[0] == '.' and len(string) != 0):
            return matchCore(string[1:], pattern[2:]) or matchCore(string[1:], pattern) or matchCore(string, pattern[2:])
        else:
            return matchCore(string, pattern[2:])
    
    if len(string) > 0 and string[0] == pattern[0] or (pattern[0] == '.' and len(string) != 0):
        return matchCore(string[1:], pattern[1:])
    
    return False

# test
def Test(test_name_str, test_str, pattern_str, true_result):
    result = match(test_str, pattern_str)
    if result == true_result:
        print(test_name_str, ": OK!")
    else:
        print(test_name_str, ": Failed!")

Test("Test01", "", "", True);
Test("Test02", "", ".*", True);
Test("Test03", "", ".", False);
Test("Test04", "", "c*", True);
Test("Test05", "a", ".*", True);
Test("Test06", "a", "a.", False);
Test("Test07", "a", "", False);
Test("Test08", "a", ".", True);
Test("Test09", "a", "ab*", True);
Test("Test10", "a", "ab*a", False);
Test("Test11", "aa", "aa", True);
Test("Test12", "aa", "a*", True);
Test("Test13", "aa", ".*", True);
Test("Test14", "aa", ".", False);
Test("Test15", "ab", ".*", True);
Test("Test16", "ab", ".*", True);
Test("Test17", "aaa", "aa*", True);
Test("Test18", "aaa", "aa.a", False);
Test("Test19", "aaa", "a.a", True);
Test("Test20", "aaa", ".a", False);
Test("Test21", "aaa", "a*a", True);
Test("Test22", "aaa", "ab*a", False);
Test("Test23", "aaa", "ab*ac*a", True);
Test("Test24", "aaa", "ab*a*c*a", True);
Test("Test25", "aaa", ".*", True);
Test("Test26", "aab", "c*a*b", True);
Test("Test27", "aaca", "ab*a*c*a", True);
Test("Test28", "aaba", "ab*a*c*a", False);
Test("Test29", "bbbba", ".*a*a", True);
Test("Test30", "bcbbabab", ".*a*a", False);
Test01 : OK!
Test02 : OK!
Test03 : OK!
Test04 : OK!
Test05 : OK!
Test06 : OK!
Test07 : OK!
Test08 : OK!
Test09 : OK!
Test10 : OK!
Test11 : OK!
Test12 : OK!
Test13 : OK!
Test14 : OK!
Test15 : OK!
Test16 : OK!
Test17 : OK!
Test18 : OK!
Test19 : OK!
Test20 : OK!
Test21 : OK!
Test22 : OK!
Test23 : OK!
Test24 : OK!
Test25 : OK!
Test26 : OK!
Test27 : OK!
Test28 : OK!
Test29 : OK!
Test30 : OK!

面试题54 表示数值的字符串

【题目】:
请实现一个字符串用来判断字符串是否包含数值(包括整数和小数)。
如字符串“+100”, “5e2”, “-123”, “3.1416”以及“-1E-16”都表示数值;
如字符串“12e”,“1a3.14”,“1.2.3”,“±5”,“12e+5.4”都不是。

【解题思路】:
表示数值的字符串遵循以下规则:
[ s i g n ] i n t e g r a l − d i g i t s [ . [ f r a c t i o n a l − d i g i t s ] ] [ e ∣ E [ s i g n ] e x p o n e n t i a l − d i g i t s ] [sign]integral-digits[.[fractional-digits]][e|E[sign]exponential-digits] [sign]integraldigits[.[fractionaldigits]][eE[sign]exponentialdigits]

其中:

  1. [ ] [ ] []中的部分表示可有可无的部分;
  2. integral-digits,fractional-digits,exponential-digits表示n个0-9之间的数字;
  3. [sign]表示可有可无的正负号;
  4. e|E表示10的n次方。
def isNumeric(string: str) -> bool:
    if not string:
        return False
    if len(string) == 0:
        return False
    
    index, length = 0, len(string)
    flag = False
    # scan the first character, is the [sign]?
    if string[index] == "+" or string[index] == "-":
        index += 1
    if not string[index].isdigit():
        return False
    if index == length:
        return False

    # scan the integral-digits
    index = scan(string, index)
    
    if index == length-1:
        if string[index].isdigit() or string[index] == '.':
            return True
        else:
            return False
    
    # scan the "."
    if string[index] == '.':
        index += 1
        if index < length-1:
            # scan the digit behind the '.'
            index = scan(string, index)
            if index == length-1 and string[index].isdigit():
                return True
        else:
            return True 
    # scan the e|E
    if string[index] == "e" or string[index] == "E":
        index += 1
        if index <= length - 1:
            # scan the [sign] behind the e|E
            if string[index] == "+" or string[index] == "-":
                index += 1
            # scan the digit behind the e|E
            index = scan(string, index)
            if index == length-1 and string[index].isdigit():
                return True
            else:
                return False
        else:
            return False
    
    return False
            
    

def scan(string, index):
    for i in range(index, len(string)):
        if not string[i].isdigit():
            break
    return i 

# test
def Test(test_name_str, string, true_resulr):
    res = isNumeric(string)
    if res == true_resulr:
        print(test_name_str, ": OK!")
    else:
        print(test_name_str, ": Failed!")

Test("Test1", "100", True);
Test("Test2", "123.45e+6", True);   # false
Test("Test3", "+500", True);
Test("Test4", "5e2", True);    # false
Test("Test5", "3.1416", True);
Test("Test6", "600.", True);
Test("Test7", "-.123", False);
Test("Test8", "-1E-16", True);
Test("Test9", "1.79769313486232E+308", True);


Test("Test10", "12e", False);
Test("Test11", "1a3.14", False);
Test("Test12", "1+23", False);
Test("Test13", "1.2.3", False);
Test("Test14", "+-5", False);
Test("Test15", "12e+5.4", False);
Test("Test16", ".", False);    # false
Test("Test17", ".e1", False);
Test("Test18", "+.", False);   # false
Test1 : OK!
Test2 : OK!
Test3 : OK!
Test4 : OK!
Test5 : OK!
Test6 : OK!
Test7 : OK!
Test8 : OK!
Test9 : OK!
Test10 : OK!
Test11 : OK!
Test12 : OK!
Test13 : OK!
Test14 : OK!
Test15 : OK!
Test16 : OK!
Test17 : OK!
Test18 : OK!

面试题55 字符流中第一个不重复的字符

【题目】:
请实现一个函数用来找出字符流中第一个只出现一次的字符。
如“go”,第一个只出现一次的字符是g;
如“google”第一个只出现一次的字符是l。

【解题思路】:
在python中,利用dict进行统计,遍历两次字符串,第一次统计每个字符出现的次数,第二次找到第一个字符个数为1的字符返回。

def findAppearingOnce(string):
    if not string:
        return None
    
    count = {}
    for i in string:
        if i not in count.keys():
            count[i] = 1
        else:
            count[i] += 1
            
    for i in string:
        if count[i] == 1:
            return i
        
# test
findAppearingOnce("google")

'l'

面试题56 链表中环的入口节点

【题目】:
一个链表中包含环,如何找出环的入口节点。

【解题思路】:

  1. 先利用快慢指针,判断该链表中是否有环,并找到环中的一个节点;
  2. 从环中的一个节点出发,找到环中节点的数量;
  3. 从原链表中的头节点开始,设置两个指针,一个指针指向头节点,另一个指针先往前移动环中节点的个数相等的步数。然后两个指针同时移动,当两个指针相等的时候,就找了环的入口节点。
class ListNode():
    def __init__(self, val):
        self.val = val
        self.next = None
        
def findNodeInLoop(head):
    if not head:
        return None
    
    slow, fast = head, head.next
    
    while slow != None and fast != None:
        if fast == slow:
            return fast
        
        slow = slow.next
        fast = fast.next
        if fast != None:
            fast = fast.next
    
    return None

def findEntryNodeOfLoop(head):
    if not head:
        return None
    
    nodeInLoop = findNodeInLoop(head)
    
    if not nodeInLoop:
        return None
    
    nodesCountInLoop = 1
    pNode = nodeInLoop
    while pNode.next != nodeInLoop:
        pNode = pNode.next
        nodesCountInLoop += 1
    
    pNode1, pNode2 = head, head
    
    for i in range(nodesCountInLoop):
        pNode1 = pNode1.next
        
    while pNode1 != pNode2:
        pNode1 = pNode1.next
        pNode2 = pNode2.next
    
    return pNode1

# test
def createTestSample():
    A = ListNode("A")
    B = ListNode("B")
    C = ListNode("C")
    D = ListNode("D")
    E = ListNode("E")
    F = ListNode("F")
    A.next = B; B.next = C; C.next = D; D.next = E; E.next = F;
    F.next = C;
    
    return A

head = createTestSample()
meetnode = findEntryNodeOfLoop(head)
print(meetnode.val)
C

面试题57 删除链表中的重复节点

【题目】:
在一个排序的链表中,如何删除重复的节点?

【解题思路】:
设置两个指针,一个指向当前,一个指向前一个节点,如果当前的节点与下一个节点的值相等,那么这两个节点应该被删除。

class ListNode():
    def __init__(self, val):
        self.val = val
        self.next = None
        
def deleteDuplication(head):
    if not head:
        return 
    
    pPreNode, pNode = None, head
    
    while pNode != None:
        pNext = pNode.next
        needDelete = False
        
        if pNext != None and (pNext.val == pNode.val):
            needDelete = True
            
        if not needDelete:
            pPreNode = pNode
            pNode = pNode.next
        else:
            value = pNode.val
            pToBeDel = pNode
            while pToBeDel != None and (pToBeDel.val == value):
                pNext = pToBeDel.next
                pToBeDel = pNext
            
            if pPreNode == None:
                head = pNext
            else:
                pPreNode.next = pNext
            pNode = pNext
    
    return head


# test
def createLinkList(nums):
    if not nums:
        return None
    
    p = head = ListNode(nums[0])
    
    for i in nums[1:]:
        p.next = ListNode(i)
        p = p.next
    
    return head

def printLink(head):
    if not head:
        return 
    while head:
        print(head.val, end="->")
        head = head.next
    print("End")

# before
head = createLinkList([1,2,3,3,4,4,5])
printLink(head)
# after
head = createLinkList([1,1,2,3,3,3,4,4,5,5])
head = deleteDuplication(head)
printLink(head)
                
1->2->3->3->4->4->5->End
2->End

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

【题目】:
给定一个二叉树和其中的一个结点,如何找出中序遍历的顺序的下一个节点。结点除了有两个分别指向左右子节点以外的指针外,还有一个指向父节点的指针。
【解题思路】:

  1. 如果结点有右子树,那么下一个节点就是它右子树中最左子节点;
  2. 如果节点没有右子树,如果他是父节点的左子节点,那么下一个节点就是父节点;
  3. 如果没有右子树,且为父节点的右子节点,则沿着指向父节点的指针一直向上遍历,直到找到一个是他父节点的左子节点的节点就是下一个节点。
class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None
        self.parent = None

def getNextNode(node):
    if not node:
        return None
    
    pNext = None
    
    # if the node has a right child
    if node.right != None:
        right = node.right
        while right.left:
            right = right.left
        pNext = right
    
    # if the node has a parent but don't have a right child  
    elif node.parent != None:
        current = node
        parent = node.parent
        # find a parent and the parent is the left child of another node
        while parent != None and (current == parent.right):
            current = parent
            parent = parent.parent
        
        pNext = parent
    
    return pNext

# test
def createTestSamples():
    '''
            A
          /   \
         B     C
       /      / \
       D     E  F
    '''
    A = TreeNode("A")
    B = TreeNode("B")
    C = TreeNode("C")
    D = TreeNode("D")
    E = TreeNode("E")
    F = TreeNode("F")
    
    A.left = B; A.right = C;
    B.left = D; B.parent = A;
    C.left = E; C.right = F; C.parent = A;
    D.parent = B; E.parent = C; F.parent = C;
    
    return A, B, C, D, E, F

A, B, C, D, E, F = createTestSamples()
# in-order traversal: D->B->A->E->C->F
Anext = getNextNode(A)
Bnext = getNextNode(B)
Cnext = getNextNode(C)
Dnext = getNextNode(D)
Enext = getNextNode(E)
Fnext = getNextNode(F)
print("A next:", Anext.val)
print("B next:", Bnext.val)
print("C next:", Cnext.val)
print("D next:", Dnext.val)
print("E next:", Enext.val)
print("F next:", Fnext)
A next: E
B next: A
C next: F
D next: B
E next: C
F next: None

面试题59 对称的二叉树

【题目】:
其实现一个函数,用来判断一颗二叉树是不是对称的。

【解题思路】:

  1. 定义一个对称前序遍历:根->右->左;
  2. 然后同时进行前序遍历和对称前序遍历,每遍历一个节点就进行判断。
# define the tree
class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

# create a tree
def createTree(nums, index):
    if not nums:
        return None
    if index >= len(nums):
        return None
    
    root = TreeNode(nums[index])
    root.left = createTree(nums, 2*index+1)
    root.right = createTree(nums, 2*index+2)
    
    return root

def isSymmetrical(root1, root2):
    if root1 == None and root2 == None:
        return True
    
    if root1 == None or root2 == None:
        return False
    
    if root1.val != root2.val:
        return False
    
    return isSymmetrical(root1.left, root2.right) and isSymmetrical(root1.right, root2.left)

# test
# 1. create a symmetrical tree
root = createTree([8, 6, 6, 5, 7, 7, 5], 0)
print("symmetrical tree:", isSymmetrical(root, root))

# 2. create a non-symmetrical tree
root = createTree([8, 6, 9, 5, 7, 7, 5], 0)
print("non-symmetrical tree:", isSymmetrical(root, root))
symmetrical tree: True
non-symmetrical tree: False

面试题60 把二叉树打印成多行

【题目】:
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层一行。

【解题思路】:
这就是一个简单的层次遍历,利用队列即可实现。

# define the tree
class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

# create a tree
def createTree(nums, index):
    if not nums:
        return None
    if index >= len(nums):
        return None
    
    root = TreeNode(nums[index])
    root.left = createTree(nums, 2*index+1)
    root.right = createTree(nums, 2*index+2)
    
    return root

def printTree(root):
    if not root:
        return
    
    queue = [root]
    currentLayer, nextLayer = 1, 0
    
    while queue:
        node = queue.pop(0)
        print(node.val, end=" ")
        
        if node.left != None:
            queue.append(node.left)
            nextLayer += 1
        if node.right != None:
            queue.append(node.right)
            nextLayer += 1
        
        currentLayer -= 1
        if currentLayer == 0:
            print()
            currentLayer = nextLayer
            nextLayer = 0
    
# test
# 1. create a tree
root = createTree([8, 6, 10, 5, 7, 9, 11], 0)
# 2. print node by layer
printTree(root)
8 
6 10 
5 7 9 11 

面试题61 按之字形顺序打印二叉树

【题目】:
请实现一个函数,按照之字形顺序打印二叉树,即第一行从左到右,第二行从右到左以此类推。

【解题思路】:
与60题类似,这个题可以使用双栈的解题思路,当前层出栈,并将下一层的节点压入另一个栈。需要注意的是:
一个栈用于存奇数层,所以是先压入左节点,再压入右节点;
另一个栈用于村偶数层,要先压入右节点,再压如左节点。

# define the tree
class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

# create a tree
def createTree(nums, index):
    if not nums:
        return None
    if index >= len(nums):
        return None
    
    root = TreeNode(nums[index])
    root.left = createTree(nums, 2*index+1)
    root.right = createTree(nums, 2*index+2)
    
    return root


def printTreeAsZhi(root):
    if not root:
        return None
    
    stack = [[root], []]
    current, next = 0, 1
    
    layerFlag = True
    
    while stack[0] or stack[1]:
        node = stack[current].pop(-1)
        print(node.val, end=" ")
        
        if current == 0:
            if node.left:
                stack[next].append(node.left)
            if node.right:
                stack[next].append(node.right)
        else:
            if node.right:
                stack[next].append(node.right)
            if node.left:
                stack[next].append(node.left)
        
        if len(stack[current]) == 0:
            print()
            current = 1 - current
            next = 1 - next
        
# test
# 1. create a tree
root = createTree([i for i in range(1, 16)], 0)
# 2. print node by layer
printTreeAsZhi(root)     
1 
3 2 
4 5 6 7 
15 14 13 12 11 10 9 8 

面试题62 序列化二叉树

【题目】:
请实现两个函数,用来分别序列化和反序列化二叉树。
如二叉树:
​ 1
​ / \
2 3
/ / \
4 5 6
其序列化就是:$1,2,4,$,$,$,3,5,$,$,6,$,$$。

【解题思路】:
利用前序遍历的思想,当遇到None的时候,输出特殊字符代替。
同理,当反序列化时,也是按照前序遍历的思想,当发现该节点的下两个元素都是特殊符号时,表示该节点是叶子节点。

# define the tree
class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

# print tree
def printTree(root):
    if not root:
        return
    print(root.val, end=",")
    printTree(root.left)
    printTree(root.right)
    
# Serialize a tree
def serializeTree(root, res):
    if not root:
        res.append("$")
        return
    
    res.append(root.val)
    serializeTree(root.left, res)
    serializeTree(root.right, res)

# Deserialize a tree
def deserializeTree(seria):
    if not seria:
        return None
    number = seria.pop(0)
    if isinstance(number, int):
        root = TreeNode(number)
        
        root.left = deserializeTree(seria)
        root.right = deserializeTree(seria)
        
        return root
    else:
        return None

    
# test
# 1. create a tree
def createTestSample():
    A = TreeNode(1)
    B = TreeNode(2)
    C = TreeNode(3)
    D = TreeNode(4)
    E = TreeNode(5)
    F = TreeNode(6)
    A.left = B; A.right = C;
    B.left = D; C.left = E; C.right = F;
    
    return A

root = createTestSample()

# 2. test serialize tree
res = []
serializeTree(root, res)
print("1. pre-order traversal:")
printTree(root)
print("\n2. Serialize a Tree", res)
# 3. test deserialize a tree
deseriaTreeRoot = None
deseriaTreeRoot = deserializeTree(res)
print("3. After deserialization:")
printTree(deseriaTreeRoot)
1. pre-order traversal:
1,2,4,3,5,6,
2. Serialize a Tree [1, 2, 4, '$', '$', '$', 3, 5, '$', '$', 6, '$', '$']
3. After deserialization:
1,2,4,3,5,6,

面试题63 二叉搜索树的第k个节点

【题目】;
给定一棵二叉搜索树,请找出其中第k大的节点。

【解题思路】:
利用中序遍历的思想遍历得到的结果是排序的。

# define the tree
class TreeNode():
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None
        
# Deserialize a tree
def deserializeTree(seria):
    if not seria:
        return None
    number = seria.pop(0)
    if isinstance(number, int):
        root = TreeNode(number)
        
        root.left = deserializeTree(seria)
        root.right = deserializeTree(seria)
        
        return root
    else:
        return None
    
def findKthNode(root, k):
    if not root or k == 0:
        return None
    
    def kthNodeCore(root, res, k):
        if not root:
            return None

        if root.left != None:
            kthNodeCore(root.left, res, k)
        
        if len(res) != k:
            res.append(root.val)
        
        
        if target == None and root.right != None:
            kthNodeCore(root.right, res, k)
        

    res = []
    kthNodeCore(root, res, k)
    
    if len(res) < k:
        return "k gather than the number of tree nodes!"
    
    return res[-1]
    
# test
# 1. create a tree
root = deserializeTree([5, 3, 2, "$", "$", 4, "$", "$", 7, 6, "$", "$", 8, "$", "$"])
# 2. test find kth node
res = findKthNode(root, 3)
print("The kth node is:", res)
The kth node is: 4

面试题64 数据流中的中位数

【题目】:
如何得到一个数据流中的中位数?如果数据个数为奇数,那么中位数就是所有数值排序后的中间值;如果数据的个数为偶数,那么中位数就是所有数值排序后中间两个数的平均值。

【解题思路】:

  1. 排序的数组【Done】
  2. 排序的链表
  3. 二叉搜索树
  4. AVL树
  5. 最大堆和最小堆
def getMedian(dataStream):
    res = []
    count = 0
    for i in dataStream:
        count += 1
        res.append(i)
        res.sort()
        
        if count % 2 == 0:
            median = (res[count // 2] +  res[count // 2 - 1]) / 2
        else:
            median = res[count // 2]
        
        print("count=", count, "median=", median)
        
# test
dataStream = [2,1,3,1,4,6,7,3,1,5,3]
getMedian(dataStream)
count= 1 median= 2
count= 2 median= 1.5
count= 3 median= 2
count= 4 median= 1.5
count= 5 median= 2
count= 6 median= 2.5
count= 7 median= 3
count= 8 median= 3.0
count= 9 median= 3
count= 10 median= 3.0
count= 11 median= 3

面试题65 滑动窗口的最大值

【题目】:
给定一个数组和滑动窗口的大小,找出所有滑动窗口里的最大值。
如{2,3,4,2,6,2,5,1}和窗口大小3,那么一共有6个滑动窗口,每个滑动窗口的最大值为{4,4,6,6,6,5}。

【解题思路】:
利用队列的思想,将当前最大的数和可能最大数存起来,根据当前数与最大数的下标来判断最大的数是否已经滑出窗口。

def maxInWindows(nums, windowSize):
    if not nums or windowSize <= 0:
        return []
    if windowSize >= len(nums):
        return [max(nums)]
    
    queue, res = [], []
    
    # the first window
    for i in range(windowSize):
        while queue and nums[i] >= nums[queue[-1]]:
            queue.pop(-1)
        queue.append(i)
        
    # for the rest of the windows
    for i in range(windowSize, len(nums)):
        res.append(nums[queue[0]])
        
        while queue and nums[i] >= nums[queue[-1]]:
            queue.pop(-1)
        if queue and queue[0] <= (i - windowSize):
            queue.pop(0)
        
        queue.append(i)
    
    res.append(nums[queue[0]])
    
    return res

# test
nums = [2,3,4,2,6,2,5,1]
res = maxInWindows(nums, 3)
print(res)
[4, 4, 6, 6, 6, 5]

面试题66 矩阵中的路径

【题目】:
请设计一个函数,用来判断一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵的任意一格开始,每一步可以在矩阵中向上、下、左、右移动一格。

【解题思路】:
利用回溯法

def hasPath(matrix, string):
    if not matrix or not string:
        return False
    
    row, col = len(matrix), len(matrix[0])
    visited = [[0 for i in range(col)] for j in range(row)]
    
    for i in range(row):
        for j in range(col):
            if matrix[i][j] == string[0] and hasPathCore(matrix, string, i, j, visited):
                return True
    return False

def hasPathCore(matrix, string, row, col, visited):
    if len(string) == 0:
        return True
    
    hasPath = False
    if row >= 0 and row < len(matrix) and \
    col >=0 and col < len(matrix[0]) and \
    matrix[row][col] == string[0] and \
    visited[row][col] == 0:
        visited[row][col] = 1
        hasPath = hasPathCore(matrix, string[1:], row + 1, col, visited) or \
        hasPathCore(matrix, string[1:], row - 1, col, visited) or \
        hasPathCore(matrix, string[1:], row, col + 1, visited) or \
        hasPathCore(matrix, string[1:], row, col - 1, visited)
        
    return hasPath

# test
# 1. create a matrix
matrix = [["a", "b", "c", "e"],
          ["s", "f", "c", "s"],
          ["a", "d", "e", "e"]]

string = "bcced"

# 2. test the hasPath
print(hasPath(matrix, string))



# test
def Test(testName, matrix, string, rows, cols, true_result):
    matrix = [matrix[i*cols: (i+1)*cols] for i in range(rows)]
    res = hasPath(matrix, string)
    if res == true_result:
        print(testName, ": OK!")
    else:
        print(testName, ": FAILED!")
        
Test("test01", "ABCESFCSADEE", "ABCCED", 3, 4, True)
Test("test02", "ABCESFCSADEE", "SEE", 3, 4, True)
Test("test03", "ABCESFCSADEE", "ABCB", 3, 4, False)
Test("test04", "ABCEHJIGSFCSLOPQADEEMNOEADIDEJFMVCEIFGGS", "SLHECCEIDEJFGGFIE", 5, 8, True)
# Test("test05", "ABCEHJIGSFCSLOPQADEEMNOEADIDEJFMVCEIFGGS", "SGGFIECVAASABCEHJIGQEM", 5, 8, True)  # some trouble  with this test case
Test("test06", "ABCEHJIGSFCSLOPQADEEMNOEADIDEJFMVCEIFGGS", "SGGFIECVAASABCEEJIGOEM", 5, 8, False)
Test("test07", "ABCEHJIGSFCSLOPQADEEMNOEADIDEJFMVCEIFGGS", "SGGFIECVAASABCEHJIGQEMS", 5, 8, False)
Test("test08", "AAAAAAAAAAAA", "AAAAAAAAAAAA", 3, 4, True)
Test("test09", "AAAAAAAAAAAA", "AAAAAAAAAAAAA", 3, 4, False)
Test("test10", "A", "A", 1, 1, True)
Test("test11", "A", "B", 1, 1, False)
True
test01 : OK!
test02 : OK!
test03 : OK!
test04 : OK!
test06 : OK!
test07 : OK!
test08 : OK!
test09 : OK!
test10 : OK!
test11 : OK!

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

【题目】:
地上有一个m行n列的方格。一个机器人从坐标(0,0)的格子开始移动,它每一次可以向上、下、左、右移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。
如当k=18时,可以进入方格(35, 37),因为3+5+3+7=18=k;但是不能进入方格(35, 38),因为3+5+3+8=19>k。
请问机器人可以到达多少个格子?

【解题思路】:
回溯法

def movingCount(rows, cols, k):
    if rows <= 0 or cols <= 0:
        return 0
    
    visited = [[0 for i in range(cols)] for j in range(rows)]
    
    count = movingCountCore(rows, cols, k, visited, 0, 0)
    
    return count

def movingCountCore(rows, cols, k, visited, row, col):
    count = 0
    if check(rows, cols, k, visited, row, col):
        visited[row][col] = 1
        count = 1 + movingCountCore(rows, cols, k, visited, row - 1, col) + \
        movingCountCore(rows, cols, k, visited, row + 1,col) + \
        movingCountCore(rows, cols, k, visited, row, col - 1) + \
        movingCountCore(rows, cols, k, visited, row, col + 1)
    return count

def check(rows, cols, k, visited, row, col):
    if row >= 0 and row < rows and \
    col >= 0 and col < cols and \
    getSum(row) + getSum(col) <= k  and \
    visited[row][col] == 0:
        return True
    return False

def getSum(num):
    Sum = 0
    while num > 0:
        Sum += (num % 10)
        num = num // 10
    
    return Sum

# test
def test(testName, k, rows, cols, expected):
    count = movingCount(rows, cols, k)
    if count == expected:
        print(testName, " OK!")
    else:
        print(testName, " FAILED!")
        
test("Test1", 5, 10, 10, 21)
test("Test2", 15, 20, 20, 359)
test("Test3", 10, 1, 100, 29)
test("Test4", 10, 1, 10, 10)
test("Test5", 15, 100, 1, 79)
test("Test6", 15, 10, 1, 10)
test("Test7", 15, 1, 1, 1)
test("Test8", -10, 10, 10, 0)
Test1  OK!
Test2  OK!
Test3  OK!
Test4  OK!
Test5  OK!
Test6  OK!
Test7  OK!
Test8  OK!
  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值