四刷 剑指offer

前言

经过面试发现一点很重要的问题,在面试的时候挺多时候考的就是手撕算法原题,这个时候需要注意两点:

  1. 不能表现出是死记硬背,而是有思考过程,知道每一步是为什么
  2. 要死记硬背。。。或者说对代码非常熟悉,在面试的时候能把所有细节都正确写出来才可以,面试官很强,知道重点是什么,也知道你代码写出来之后对不对,如果你记得不熟,就会出现问题,又不能运行调试,现看很难发现问题。

剑指Offer11.旋转数组的最小数字

在这里插入图片描述
这个题,反复看了很多次了,这次基本上算是自己做出来了,还有点磕磕绊绊,二分法确实是软肋。

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        a = 0
        b = len(numbers)-1
        while a <= b:
            m = (a + b) // 2
            if numbers[m] < numbers[b]:
                b = m   # 不能b=m-1,因为这个时候numbers[m]可能就是最小值了,减一就跳到左边了
            elif numbers[m] > numbers[b]:
                a = m + 1  # 这里是正常的
            else:
                b -= 1  # 这个事情就很有意思,会发现numbers[m]==numbers[b],
                		# 就不知道最小值在numbers[m]的左边还是右边了,但是,
                		# 既然有这个等式,那就说明b可以左移一位,
                		# 因为即使numbers[b]是最小值,那numbers[m]也能是最小值
        return numbers[a]

剑指Offer19.正则表达式匹配

这个题的状态转移实在是太复杂了。。。还有dp的循环起始点也很奇怪,做多少遍都不行。。orz

剑指Offer20.表示数值的字符串

我滴个亲娘。。。这状态转移要命啊,我放弃
在这里插入图片描述

剑指Offer24.反转链表

这题要是面试准得懵,其实逻辑很简单

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        pre, cur = None, head
        while cur:
            nxt = cur.next
            cur.next = pre
            pre = cur
            cur = nxt
        return pre

剑指Offer26.树的子结构

在这里插入图片描述
注意一个问题,如果这个结点不能满足,那么要继续判断左子树右子树能不能满足。

class Solution:
    def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
        if not A or not B:
            return False
        
        def f(A, B):
            if not B:
                return True
            if not A:
                return False
            if A.val == B.val:
                return f(A.left, B.left) and f(A.right, B.right)
            else:
                return False
        
        return f(A,B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B)

剑指Offer34.二叉树中和为某一值的路径

值得注意的是,记录路径时若直接执行res.append(path),则是将path对象加入了res;
后续path改变时,res中的path对象也会随之改变。

正确做法:res.append(list(path)) ,相当于复制了一个 path 并加入到 res 。

这个事情比较重要。。写的时候就是因为这个问题导致存了都是一样的list,然后还在变

剑指Offer35.复杂链表的复制

在这里插入图片描述
三步流程:

  1. 复制各节点,构建拼接链表
  2. 构建新链表各节点的 random 指向
  3. 拆分原 / 新链表

注意第二步,容易忽略,这个很重要!

剑指Offer36.二叉搜索树与双向链表

好像喜欢考那种需要很多细节的问题
这个题折过戟,一定要把握好

class Solution:
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        if not root:
            return None
        self.last = None
        self.head = None
        def f(root):
            if not root:
                return
            f(root.left)
            if not self.last:
                self.head = root
            else:
                self.last.right = root
                root.left = self.last
            self.last = root
            self.tail = root
            f(root.right)
        f(root)
        self.head.left = self.tail
        self.tail.right = self.head
        return self.head

剑指Offer37.序列化二叉树

这个题就前序遍历输出和读取

class Codec:

    def serialize(self, root):
        """Encodes a tree to a single string.
        
        :type root: TreeNode
        :rtype: str
        """
        if not root:
            return 'None'
        return str(root.val) + ',' + str(self.serialize(root.left)) + ',' + str(self.serialize(root.right))
       
    def deserialize(self, data):
        """Decodes your encoded data to tree.
        
        :type data: str
        :rtype: TreeNode
        """
        l = data.split(',')

        def f(l):
            r = l.pop(0)
            if r == 'None':
                return None
            root = TreeNode(int(r))
            root.left = f(l)
            root.right = f(l)
            return root
        return f(l)        

剑指Offer40.最小的k个数

三种解法:

  1. 排序后取前k个
  2. 堆排序,用大顶堆保存前k个,然后从k+1开始到后面,每一个判断一下是不是比堆顶要小,小的话就pop然后加入堆,最后返回这个堆就行了。注意:python没有大顶堆,只能取负后用小顶堆来做。
class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        if k == 0:
            return list()

        hp = [-x for x in arr[:k]]
        heapq.heapify(hp)
        for i in range(k, len(arr)):
            if -hp[0] > arr[i]:
                heapq.heappop(hp)
                heapq.heappush(hp, -arr[i])
        ans = [-x for x in hp]
        return ans
  1. 快排思想,每次分成两部分,比pivot小的在前面,比它大的在后面,有pivot的index,然后看index的位置,如果等于k了就返回前k个数就成了,不然还要分类一下。
class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        
        def partition(arr,l,r):
            pivot = arr[r]
            i = l
            for j in range(l,r+1):
                if arr[j] <= pivot:
                    arr[i],arr[j] = arr[j],arr[i]
                    i += 1
            return i-1

        def sort(arr,l,r,k):
            idx = partition(arr,l,r)
            num = idx - l +1
            if num == k:
                return 
            elif num < k:
                sort(arr, idx+1, r, k - num)
            else:
                sort(arr, l, idx-1, k)

        
        if k == 0:
            return []
        sort(arr, 0, len(arr)-1,k)
        return arr[:k]

剑指Offer41.数据流中的中位数

这个题,不得不说还是,秒哇!
弄俩堆,一个是小顶堆A,存大的那一半,另一个是大顶堆B,存小的那一半

加数addNum:

  • 如果A和B的长度相等,就往A里面加个数。方法是先加到B里,堆调整之后,再放A里;
  • 如果A和B的长度不等,就往B里面加个数。方法是先加到A里,堆调整之后,再放B里。
class MedianFinder:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.a = []
        self.b = []

    def addNum(self, num: int) -> None:
        if len(self.a) == len(self.b):
            heapq.heappush(self.a, -heapq.heappushpop(self.b,-num))
        else:
            heapq.heappush(self.b, -heapq.heappushpop(self.a,num))

    def findMedian(self) -> float:
        if len(self.a) == len(self.b):
            if not self.a:
                return None
            else:
                return (self.a[0] - self.b[0]) / 2
        else:
            return self.a[0]

剑指Offer43.1~n整数中1出现的次数

得找到计算的公式,感觉面试不会考这种,没意思

class Solution:
    def countDigitOne(self, n: int) -> int:
        digit = 1
        high = n//10
        low = 0
        cur = n % 10
        total = 0

        while high != 0 or cur != 0:
            if cur == 0:
                total += high * digit
            elif cur == 1:
                total += high * digit + 1 + low
            else:
                total += (high + 1) * digit
            
            low += cur * digit
            cur = high % 10
            high //= 10
            digit *= 10
        return total

剑指Offer44.数字序列中某一位的数字

还是不会,服了

class Solution:
    def findNthDigit(self, n: int) -> int:
        digit, start, count = 1, 1, 9
        while n > count: # 1.
            n -= count
            start *= 10
            digit += 1
            count = 9 * start * digit
        num = start + (n - 1) // digit # 2.
        return int(str(num)[(n - 1) % digit]) # 3.

剑指Offer45.把数组排成最小的数

快排

class Solution:
    def minNumber(self, nums: List[int]) -> str:
        def quick_sort(l , r):
            if l >= r: return
            i, j = l, r
            while i < j:
                while strs[j] + strs[l] >= strs[l] + strs[j] and i < j: j -= 1
                while strs[i] + strs[l] <= strs[l] + strs[i] and i < j: i += 1
                strs[i], strs[j] = strs[j], strs[i]
            strs[i], strs[l] = strs[l], strs[i]
            quick_sort(l, i - 1)
            quick_sort(i + 1, r)
        
        strs = [str(num) for num in nums]
        quick_sort(0, len(strs) - 1)
        return ''.join(strs)

剑指Offer53-II.0~n-1中缺失的数字

a是右边的第一个
b是左边最后一个

class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        a,b = 0, len(nums)-1
        while a <= b:
            m = (a + b) // 2
            if nums[m] == m:
                a = m + 1
            else:
                b = m - 1        
        return a

剑指Offer51.数组中的逆序对

这个题就用归并排序来做吧,计数方式需要注意一下
按理说当nums[i] > nums[j]的时候说明会产生一个逆序对,但实际上写代码做不到这个过程
可以理解为当nums[i] <= nums[j]的时候,也就是nums[j-1]和之前的都小于nums[i]了,这个时候对前面的进行计数就可以了。

class Solution:
    def mergeSort(self, nums, tmp, l, r):
        if l >= r:
            return 0

        mid = (l + r) // 2
        inv_count = self.mergeSort(nums, tmp, l, mid) + self.mergeSort(nums, tmp, mid + 1, r)
        i, j, pos = l, mid + 1, l
        while i <= mid and j <= r:
            if nums[i] <= nums[j]:
                tmp[pos] = nums[i]
                i += 1
                inv_count += (j - (mid + 1))
            else:
                tmp[pos] = nums[j]
                j += 1
            pos += 1
        for k in range(i, mid + 1):
            tmp[pos] = nums[k]
            inv_count += (j - (mid + 1))
            pos += 1
        for k in range(j, r + 1):
            tmp[pos] = nums[k]
            pos += 1
        nums[l:r+1] = tmp[l:r+1]
        return inv_count

    def reversePairs(self, nums: List[int]) -> int:
        n = len(nums)
        tmp = [0] * n
        return self.mergeSort(nums, tmp, 0, n - 1)

剑指Offer55-II.平衡二叉树

最简单的办法就是遍历,然后每一次都算一下深度,左右子树深度超过1了就gg
最优解是后序遍历算深度+剪枝:

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        def recur(root):
            if not root: return 0
            left = recur(root.left)
            if left == -1: return -1
            right = recur(root.right)
            if right == -1: return -1
            return max(left, right) + 1 if abs(left - right) <= 1 else -1

        return recur(root) != -1

剑指Offer56-I.数组中数字出现的次数

剑指Offer56-II.数组中数字出现的次数II

位运算。。。到底需要不???

剑指Offer57.和为s的两个数字

双指针

剑指Offer57-II.和为s的连续正数序列

滑动窗口,还挺简单的

剑指Offer62.圆圈中最后剩下的数字

看看题解吧,理解了还挺对的

剑指Offer65.不用加减乘除做加法

位运算的问题还有补码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值