剑指offer刷题记录(上)

 

记录刷题过程,方便自己后期回顾

题目来自《剑指offer》,在牛客上OC,思路大多来自剑指offer,偶尔来自自己的碎碎念,代码自己瞎写的,如果有更优的方法请告诉我,谢谢大佬们

语言:python2.7,我知道它有点过时,但是我现在好像只会这个,其他的都想不起来了。就这样吧,下次一定用C++

 

写之前先写一些下面会用到的基础知识:

a = float('inf').    # 正无穷

b = float('-inf')   # 负无穷

二进制的位运算:与(&)、或(|)、异或(^)、左移(<<)、右移(>>)

 

1、赋值运算符函数

2、单例模式

 

3、 二维数组中的查找

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

思路:从右上角开始,要查找元素等于当前元素返回True,要查找元素小于当前元素->向左,要查找元素大于当前元素->向下,直到行小于0或列大于最大列。或者从左下角开始

# -*- coding:utf-8 -*-
class Solution:

    def Find(self, target, array):
        if array and array[0]:
            row = len(array)-1
            col = len(array[0]) -1

            r, c = 0, col
            while r <= row and c >= 0:
                if array[r][c] == target:
                    return True
                elif array[r][c] < target:
                    r += 1
                else:
                    c -= 1
        return False

 

4、替换空格

把字符串中的每个空格替换成“%20”

思路:从后向前遍历+替换。先遍历一遍得到所有的空格数,替换后的长度=替换前的长度+空格数*2,双指针old new,old指向原始字符串末尾,new指向新字符串末尾(其实是一个字符串)

这里其实有个bug:python字符串不可变,所以只能先转成列表再转回来,但是这样就占用了额外的空间,时间复杂度是O(2n),既然已经占用额外空间了,为什么不直接正着计算,遇到空格extends(['2', '0', '%'])就好了呀,时间复杂度是0(1n)

查了一下之前提交的代码:= s.replace(' ''%20'),我为什么这么皮

# -*- coding:utf-8 -*-
class Solution:

    def replaceSpace(self, s):
        if not s:
            return s
        
        length, blank_num = 0, 0 # 字符串长度,空格个数
        for i in s:
            if i == ' ':
                blank_num += 1
            length += 1

        old_index = length - 1    
        new_index = old_index + 2 * blank_num

        s = list(s)
        s.extend([' ']*blank_num*2)   # 多一个空格,长度多2
        
        while old_index >= 0:
            if s[old_index] == ' ':
                s[new_index] = '0'
                new_index -= 1
                s[new_index] = '2'
                new_index -= 1
                s[new_index] = '%'
            else:
                s[new_index] = s[old_index]
            old_index -= 1
            new_index -= 1
        s = ''.join(s)
        return s

 

5.01、链表末尾插入一个节点 (代码以后补)

5.02、删除链表中第一个含有某值的节点(代码以后补)

 

5、从尾到头打印链表

输入一个链表的头结点,从尾到头反过来打印出每个节点的值

栈:from collection import dqueue  或者list.append() + list.reverse()  或者list.insert(0, ) 或者list.append() + list.pop()(凭印象写的)

递归(MARK一下):

-*- coding:utf-8 -*-
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def printListFromTailToHead(self, listNode):

        if listNode == None:
            return []
        return self.printListFromTailToHead(listNode.next)+[listNode.val]

 

常见的树:二叉树、二叉搜索树、堆、红黑树

 

6、重建二叉树

根据前序和中序遍历结果,重建二叉树(不含重复元素)

递归:

#-*- coding:utf-8 -*-
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution:
    # 返回构造的TreeNode根节点
    def reConstructBinaryTree(self, pre, tin):
        # write code here
        if not pre or not tin:
            return None
        root = TreeNode(pre[0])
        root.left = self.reConstructBinaryTree(pre[1:tin.index(pre[0])+1], tin[:tin.index(pre[0])])
        root.right = self.reConstructBinaryTree(pre[tin.index(pre[0])+1:], tin[tin.index(pre[0])+1:])
        return root

 

7、两个栈实现队列

思路:两个栈,一个模拟进队列(进栈),一个模拟出队列(出栈),直接进,出栈如果为空,就把进栈的所有元素都给出栈

好像是我人生中第一场面试,面试官问了一道很难的算法题,我说了思路但是代码没写出来,然后就被问了这道题~ 当时题量为0,也还不知道剑指offer是什么,生想出来的,后来才发现,那场面试的好多题都来自剑指offer,不知道面试官小哥哥是不是看上了我的聪明才智哈哈哈哈哈哈(呸呸呸 太不要脸了)

# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.stack1 = []
        self.stack2 = []
    def push(self, node):
        # write code here
        self.stack1.append(node)
    def pop(self):
        # return xx
        if not self.stack2:
            while self.stack1:
                self.stack2.append(self.stack1.pop())
        return self.stack2.pop()

两个队列实现一个栈

初始时,随便选一个队列作为插入队列,然后一个向这个队列插入元素,pop时,所所有元素都移到另一个队列,弹出最后一个元素。再像另一个队列插入,弹出时还是先转移元素再删除

总结:插入时,如果两个stack都没有元素,随便插入一个,否则插入有元素的stack,弹出时,先转移元素再删除

 

8、旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。(NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。)

经典面试题目,旋转数组相关的题目我被问到过两次

思路:重点就是二分法+双指针+定区间

二分法没什么问题

双指针的意思是:旋转数组可以理解为两个递增子数组,最小值是第二个递增子数组的第一个元素。两个指针,初始化时一个指向队首,一个指向队尾,通过mid不断更新两个指针,最终第一个指针会指向第一个递增子数组的最后一个元素,第二个指针会指向第二个递增子数组的第一个元素,返回第二个指针对应的值。

定区间的意思是,正常情况下,left-mid和mid-right总有一个区间是单调增的,另一个区间又可以被分为两个子数组,去掉单调区间,选择另一个区间,不断循环

再考虑两个特殊情况:数组旋转=0 -> return l[0];l[left] = l[mid] = l[right] -> 遍历

# -*- coding:utf-8 -*-
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        length = len(rotateArray)
        if length == 0:
            return 0
        if length == 1:
            return rotateArray[0]

        l,r = 0, length - 1

        # 特例1: 左面元素小于右面元素 -> 没有旋转 -> 直接返回
        if rotateArray[l] < rotateArray[r]:
            return rotateArray[l]
        # 进入二分循环
        while l < r:

            if r - l == 1:
                return rotateArray[r]
            m = (l + r) / 2
            # 特例2
            if rotateArray[l] == rotateArray[m] and rotateArray[l] == rotateArray[r]:
                min_num = rotateArray[l]
                for i in range(l+1, r+1):
                    min_num = min(rotateArray[i], min_num)
                return min_num
            # 左面不是单增区间
            elif rotateArray[l] >= rotateArray[m]:
                r = m
            # 右面是单增区间
            else:
                l = m

只a了13% ??????很奇怪

 

9、佩波那契数列

n=0, f(n)=0; n=1, f(n)=1; else:f(n)=f(n-1)+f(n-2)

 递归:本地跑是对的,线上超时?????


class Solution:
    def Fibonacci(self, n):
        # write code here
        if n == 0:
            return 0
        if n == 1:
            return 1
        return self.Fibonacci(n-1) + self.Fibonacci(n-2)

s =  Solution()
print s.Fibonacci(10)

动态规划:

class Solution:
    def Fibonacci(self, n):
        a ,b = 0, 1
        for i in range(n):
            a, b = b, a+b
        return a

 

跳台阶:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)

f(n) = f(n-1) + f(n-2),本级台阶来自于前一个台阶跳一级和前两个台阶跳两级。

# -*- coding:utf-8 -*-
class Solution:
    def jumpFloor(self, number):
        a, b = 1, 2
        for i in range(1,number):
            a, b = b, a+b
        return a

变态跳台阶:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

矩形覆盖:我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

 

 

10、二进制中1的个数

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

n&(n-1) 可以把该整数最右面的一个1变为0

# -*- coding:utf-8 -*-
class Solution:
    def NumberOf1(self, n):
        count = 0
        if n < 0:
            n = n & 0xffffffff
        while n:
            n = n & (n-1)
            count += 1
        return count

 

11、数值的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

思路:递归。exponent为奇数时b^e = b^{(e-1)/2}*b^{(e-1)/2}*b exponent为偶数时 b^e = b^{e/2} * b^{e/2}

同时考虑e<0,b=0 的特殊情况(考虑b=0是因为,e<0时要对base取倒数)

class Solution():

    def Power(self, base, exponent):
        # 指数小于0且底数=0时
        if abs(base-0.0)<1e-7 and exponent<0:
            return False
        if exponent < 0:
            result = 1.0/self.power_with_exp(base,exponent*(-1))
        else:
            result = self.power_with_exp(base, exponent)
        return result

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

 

12、打印1到最大的n位数

如:输入3,输出1,2,3,。。。。999

思路:主要考虑用列表代替数字,因为存在大数溢出问题。但是这题python又是个bug,因为python int可以无限大 唉。。。。。

模拟加法+打印数字+判断是否到最大值跳出循环

 

递归:

(代码以后补)

 

13、在O(1)时间删除链表节点

思路:要删除的节点i,下一个节点是j i.val = j.val, i.next = j.next 然后删掉j

要考虑两个特殊情况:要删除的节点是尾节点;链表只有一个节点

 

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

思路:双指针,第一个指针从前向后扫描直到遇到偶数,第二个指针从后向前扫描直到遇到奇数,交换元素位置

 

保证重排后奇数和奇数,偶数和偶数之间的相对位置不变

思路:这个是牛客上的,没想到什么简单的思路,遍历两遍又用了一个辅助数组

# -*- coding:utf-8 -*-
class Solution:
    def reOrderArray(self, array):
        ans = []
        for i in array:
            if i%2:
                ans.append(i)
        for i in array:
            if not i%2:
                ans.append(i)
        return ans

 

15、链表中倒数第k个节点

思路:两个指针,初始时都放在头节点的位置,第一个指针向前走k-1步,这样两个指针就差了k-1个节点,然后两个指针同时向后移动,当第一个指针到最后一个节点时,第二个指针到倒数第k个节点

class Solution:
    def FindKthToTail(self, head, k):
        if not head or k <= 0:
            return None
        fore = head
        behind = head
        for i in range(k-1):
            if not fore.next:
                return None
            fore = fore.next
        while fore.next:
            fore = fore.next
            behind = behind.next
        return behind

 

16、反转链表

为了保证反转后的链表不断裂,需要三个指针:当前节点,当前节点的前一个节点,当前节点的下一个节点

# -*- coding:utf-8 -*-
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def ReverseList(self, pHead):
        reversed_head = None
        cur = pHead
        pre = None
        # post = pHead.next
        
        while cur:
            post = cur.next
            if not post:
                reversed_head = cur
            cur.next = pre
            pre = cur
            cur = post
        return reversed_head

 

17、合并两个排序的链表

# -*- coding:utf-8 -*-
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    # 返回合并后列表
    def Merge(self, pHead1, pHead2):
        # write code here
        if not pHead1:
            return pHead2
        if not pHead2:
            return pHead1
        new_head = ListNode(0)    # 我当时是怎么想起来这么机智的做法的!!!!
        p = new_head
        while pHead1 and pHead2:
            if pHead1.val <= pHead2.val:
                p.next = pHead1
                p = p.next
                pHead1 = pHead1.next
            else:
                p.next = pHead2
                p = p.next
                pHead2 = pHead2.next
        if pHead1:
            p.next = pHead1
        if pHead2:
            p.next = pHead2
        return new_head.next

 

18、树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

思路:遍历+递归。先在树A中查找和树B根节点相同的值, 然后递归地判断A中该节点的子树和B根节点的子树是否相同。递归终止条件是遇到不同的值,或者到达了A或B的叶子节点

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def HasSubtree(self, pRoot1, pRoot2):
        # write code here
        if not pRoot1 or not pRoot2:
            return False
        return self.IsSubtree(pRoot1, pRoot2) or self.HasSubtree(pRoot1.left, pRoot2) or self.HasSubtree(pRoot1.right, pRoot2)
     
    def IsSubtree(self, pRoot1, pRoot2):
        if not pRoot2:
            return True
        if not pRoot1 or pRoot1.val != pRoot2.val:
            return False
         
        return self.IsSubtree(pRoot1.left, pRoot2.left) and self.IsSubtree(pRoot1.right, pRoot2.right)

 

19、二叉树的镜像

操作给定的二叉树,将其变换为源二叉树的镜像

思路:前序遍历二叉树,对于遍历到的每个节点,交换他们的左右子树

# -*- coding:utf-8 -*-

class Solution:
    # 返回镜像树的根节点
    def Mirror(self, root):
        if not root:
            return
        if not root.left and not root.right:
            return
        temp_node = root.left
        root.left = root.right
        root.right = temp_node
        self.Mirror(root.left)
        self.Mirror(root.right)
或
class Solution:
    # 返回镜像树的根节点
    def Mirror(self, root):
        if root:
            temp_node = root.left
            root.left = root.right
            root.right = temp_node
            self.Mirror(root.left)
            self.Mirror(root.right)

 

20、顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字

思路:按圈打印,第一圈从(1, 1)开始,第二圈从(2, 2)开始。。。。一共需要打印 (min(行,列)+1)/2圈(4行打印两圈,5行打印三圈)。对于每一圈:从左到右、从上到下、从右到左、从下到上依次打印。最后一圈存在特殊情况:一个元素,一行,一列,两行,两列等->最后一圈存在只需要一步、两步、三步的情况,所以进行每步打印时需要判断。

剑指offer刷题记录(上)_第1张图片

# -*- coding:utf-8 -*-
class Solution(object):
    # matrix类型为二维列表,需要返回列表
    def printMatrix(self, matrix):
        # write code here
        ans = []
        row = len(matrix)
        if matrix[0]:
            col = len(matrix[0])
            iter = min((row+1)/2,(col+1)/2)
            for i in range(iter):
                #print i
                self.print_circle(matrix,i,row-2*i, col-2*i, ans) #(矩阵,左上角坐标,要打印的行数,要打印的列数)
        return ans
 
    def print_circle(self, matrix, start, row, col, ans):
        end_x = row + start - 1
        end_y = col + start - 1
        #print end_x, end_y
        for i in range(start, end_y+1):
            ans.append(matrix[start][i])
        if end_x > start:
            for i in range(start+1, end_x+1):
                ans.append(matrix[i][end_y])
        if end_y > start and end_x > start:
            for i in range(end_y-1,start-1,-1):
                ans.append(matrix[end_x][i])
        if end_x - start > 1 and end_y >start:
            for i in range(end_x-1,start,-1):
                ans.append(matrix[i][start])
        return ans

 

21、包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数,push pop min的时间复杂度都为O(1)。 

注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。

思路:辅助栈。每push一个元素,辅助栈都会push min(当前元素,辅助栈栈顶元素)

剑指offer刷题记录(上)_第2张图片

# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.stack_data = []   # 栈
        self.stack_min = []    # 辅助栈

    def push(self, node):
        self.stack_data.append(node)
        if self.stack_min == [] or node < self.stack_min[-1]:
            self.stack_min.append(node)
        else:
            self.stack_min.append(self.stack_min[-1])

    def pop(self):
        if self.stack_data:
            self.stack_min.pop()
            self.stack_data.pop()

    def top(self):
        return self.stack_data[-1]
    
    def min(self):
        return self.stack_min[-1]

 

22、栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。

思路:从弹出序列的角度判断。对于弹出序列的每个元素,如果和压入栈当前栈顶元素相同,则弹出,继续判断弹出栈下一个元素和当前栈顶元素。。。。。。不相同时,压入元素,直到和弹出序列元素相同。当栈顶元素和弹出序列元素不同且所有元素都入栈时,return false。看代码吧还是。。。。

class Solution(object):
    def IsPopOrder(self, pushV, popV):
        length_push = len(pushV)
        length_pop = len(popV)
        if length_push == 0 or length_push != length_pop:
            return False
        stack = []
        index_pop = 0
        for i in pushV:
            stack.append(i)
            while stack and index_pop < length_pop and stack[-1] == popV[index_pop]:
                index_pop += 1
                stack.pop()
        if stack:
            return False
        return True

 

23、从上往下打印二叉树

思路:层序遍历

class Solution:
    def PrintFromTopToBottom(self, root):
        l = []
        if not root:
            return l
        queue = []
        queue.append(root)
        while queue:
            l.append(queue[0].val)
 
            if queue[0].left:
                queue.append(queue[0].left)
            if queue[0].right:
                queue.append(queue[0].right)
            del queue[0]
        return l

 

24、二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

思路:后序遍历:左右根。根据二叉搜索树的性质可知:根节点左子树的所有节点都比根节点小,右子树的所有节点都比根节点大。递归判断

class Solution(object):
    def VerifySquenceOfBST(self, sequence):
        if not sequence:
            return False

        # 判断根节点左右子树的位置,i之前的都是左子树,i及之后的都是右子树
        for i in range(len(sequence)):
            if sequence[i] > sequence[-1]:
                break
        for j in range(i,len(sequence)-1):
            if sequence[j] < sequence[-1]:
                return False

        left = True
        right = True
        if i>0:
            left = self.VerifySquenceOfBST(sequence[:i])
        if i

 

25、二叉树中和为某一值的路径

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

思路:前序遍历和递归,递归过程中用一个列表保存路径

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution(object):
    # 返回二维列表,内部每个列表表示找到的路径
    def FindPath(self, root, expectNumber):
        if not root:
            return []
        if not root.left and not root.right and root.val == expectNumber:
            return [[root.val]]
        res = []
        left = self.FindPath(root.left, expectNumber-root.val)
        right = self.FindPath(root.right, expectNumber-root.val)
        for i in left+right:
            res.append([root.val]+i)
        return res

 

26、复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。

思路:step1:复制val和next,对于原始链表中的每个节点N,都复制一份N'并链接在N的后面。

step2:复制random指针,N->S 那么 N.next.random -> S.next

step3:拆成两个链表

# -*- coding:utf-8 -*-
class RandomListNode:
    def __init__(self, x):
        self.label = x
        self.next = None
        self.random = None

class Solution:
    def Clone(self, pHead):
        if not pHead:
            return None
        # step1
        cur = pHead
        while cur:
            tmp = RandomListNode(cur.label)
            tmp.next = cur.next
            cur.next = tmp
            cur = tmp.next
        # step2
        cur = pHead
        while cur:
            tmp = cur.next
            if cur.random:
                tmp.random = cur.random.next
            cur = tmp.next
        # step3
        cur = pHead
        res = pHead.next
        while cur.next:
            tmp = cur.next
            cur.next = tmp.next
            cur = tmp
        return res

 

27、二叉搜索树与双向链表

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

思路:中序遍历,左根右。先递归地处理左子树,然后把左子树和根节点连起来,然后递归地处理右子树,把根节点和右子树连接起来。

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
# 这个应该可以再优化一下 连接根节点和左右子树那里复杂了,但是我简单的没跑通
class Solution:
    def Convert(self, pRootOfTree):
        if not pRootOfTree:
            return pRootOfTree
        if not pRootOfTree.left and not pRootOfTree.right:
            return pRootOfTree
        # 处理左子树
        self.Convert(pRootOfTree.left)
        left=pRootOfTree.left
 
        # 连接根与左子树最大结点
        if left:
            while(left.right):
                left=left.right
            pRootOfTree.left,left.right=left,pRootOfTree
 
        # 处理右子树
        self.Convert(pRootOfTree.right)
        right=pRootOfTree.right
 
        # 连接根与右子树最小结点
        if right:
            while(right.left):
                right=right.left
            pRootOfTree.right,right.left=right,pRootOfTree
             
        while(pRootOfTree.left):
            pRootOfTree=pRootOfTree.left
        return pRootOfTree

 

28、字符串的排列

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba

思路:递归。

剑指offer刷题记录(上)_第3张图片

# -*- coding:utf-8 -*-
class Solution:
    def Permutation(self, ss):
        # write code here
        if len(ss) == 0:
            return []
        res = []
        #flag = [1] * len(ss)
        temp = ''
        res = self.dfs(ss, temp, res)
        return sorted(set(res))
 
    def dfs(self, ss, temp, res):
        if len(ss) == 1:
            temp += ss
            res.append(temp)
            return res
        else:
            for i in range(len(ss)):
                self.dfs(ss[:i]+ss[i+1:], temp+ss[i], res)
        return res

 

 

剑指offer刷题记录(上)_第4张图片

剑指offer刷题记录(上)_第5张图片

 

剑指offer刷题记录(上)_第6张图片

 

剑指offer刷题记录(上)_第7张图片

 

 

 

你可能感兴趣的:(我的刷题记录)