剑指offer思路整理(个人向)3-16

(题目和部分思路来自leetcode 剑指offer)

剑指03.数组中重复的数字

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例 1:

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 

1.使用哈希表,遍历数组,第一次出现的数字放入哈希表里,若哈希表里已经存在这个数字,则直接return这个数字。

class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
            dic = set()
            for num in nums:
                if num in dic:
                    return num
                dic.add(num)
            return

**自我反思:**哈希表,直接用set就可以了(add, remove, pop),只有那种需要键值对的才用字典{}(比如统计元素出现次数),不要只会用字典。。。

2.原地算法,即要求不能使用额外的数据结构。遍历数组nums,把nums[i]放到nums[nums[i]]的位置(交换),比如数组的第一位是4,就把4放到nums[4]那里。直到发现nums[i]==nums[nums[i]],即那个位置已经放的是他自己了,那就证明这个数字重复了。

class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
            i = 0
            while i < len(nums):
                if nums[i] == i:
                    i += 1
                    continue
                if nums[i] == nums[nums[i]]:return nums[i]
                nums[nums[i]],nums[i] = nums[i],nums[nums[i]]
            return

**自我反思:**说实话,这种真不太容易想,如果没有要求必须原地算法,最好还是乖乖用哈希表吧。

剑指04.二维数组的查找

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

示例:

现有矩阵 matrix 如下:
[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

1.第一次做时,用了个又臭又烂又长的方法。先找边界,然后在这个边界内遍历。

class Solution:
    def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
        if matrix==[] or matrix==[[]]:
            return False
        max_col = len(matrix[0])-1
        max_row = len(matrix)-1
        if target<matrix[0][0] or target>matrix[max_row][max_col]:
            return False
        for i in range(len(matrix[0])):
            if matrix[0][i]>target:
                max_col = i
        for j in range(len(matrix)):
            if matrix[j][0]>target:
                max_row = j
        for i in range(max_row+1):
            for j in range(max_col+1):
                if matrix[i][j] == target:
                    return True
        return False

**自我反思:**基本就是完全顺着题意的无脑解法,首先遍历第一行,找到第一个大于target的行号,再遍历列,找到列号的边界,然后在这个区域内遍历,找target。

2.K神之究极解法,从矩阵的最左下角的值开始,如果target比他大,那么就消去这一列,因为这一列肯定都比他小了;如果target比他小,那么久消去这一行,因为这一行肯定都比他大了。

class Solution:
    def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
        i, j = len(matrix)-1, 0
        while i >= 0 and j < len(matrix[0]):
            if matrix[i][j] > target: i-=1
            elif matrix[i][j] < target: j+=1
            else: return True
        return False

**自我反思:**确实妙啊,矩阵左下角的值含义非常特殊,刚好是这一行的最小值,又是这一列的最大值,同理右上角应该也可以。

剑指05.替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1:

输入:s = "We are happy."
输出:"We%20are%20happy."

1.抖机灵做法(不可取)。只能说这种题用python真的十分简单。先用split把字符串按空格分开,再用%20把它们join起来。

class Solution:
    def replaceSpace(self, s: str) -> str:
        return "%20".join(s.split(" "))

2.遍历然后替换就可以了,只不过python的字符串是不可修改的,所以必须用一个新的空间来放元素。

class Solution:
    def replaceSpace(self, s: str) -> str:
        temp = []
        for x in s:
            if x == ' ':temp.append('%20')
            else: temp.append(x)
        return ''.join(temp)

**自我反思:**抖机灵可能笔试还行,面试肯定是通不过的。

剑指06.从头到尾打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例 1:

输入:head = [1,3,2]
输出:[2,3,1]

1.正向遍历链表,把value放进数组里,然后反向输出数组。

class Solution:
    def reversePrint(self, head: ListNode) -> List[int]:
        result = []
        while head:
            result.append(head.val)
            head = head.next
        return result[::-1]

2.K神之递归法,其实和树的后序遍历有点像,其实只是看起来代码简介而已,其实递归大家懂得都懂,慢的一批。

class Solution:
    def reversePrint(self, head: ListNode) -> List[int]:
        return self.reversePrint(head.next) + [head.val] if head else []

**自我反思:**正向遍历然后反向输出,其实就是栈。

剑指07.重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]

返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

1.巨长。其实说白了,就是利用前序遍历的第一个节点,就是根节点,以此来区分左右两颗子树。

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if preorder == []:
            return
        def recursion(tree,preO,inO):
            if len(preO)==0:
                return
            mid = inO.index(preO[0])
            tree.val = preO[0]
            left_in = inO[:mid]
            right_in = inO[mid+1:]
            left_pre = preO[1:len(left_in)+1]
            right_pre = preO[len(left_in)+1:]
            if len(left_in)>0:
                tree.left = TreeNode(0)
            if len(right_in)>0:
                tree.right = TreeNode(0)
            recursion(tree.left,left_pre,left_in)
            recursion(tree.right,right_pre,right_in)
        root = TreeNode(0)
        recursion(root, preorder, inorder)
        return root

**自我反思:**看了一下其他人的答案,其实大家的思路是一样的。

剑指09.用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

示例 1:

输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]

1.定义两个数组,append的时候就往数组1里面加,delete的时候就把数组1倒进数组2,然后pop,再倒回数组1。

class CQueue:

    def __init__(self):
        self.stack1 = []
        self.stack2 = []

    def appendTail(self, value: int) -> None:
        self.stack1.append(value)

    def deleteHead(self) -> int:
        if len(self.stack1)==0:
            return -1
        while self.stack1:
            self.stack2.append(self.stack1.pop())
        head =  self.stack2.pop()
        while self.stack2:
            self.stack1.append(self.stack2.pop())
        return head

2.之前一直很疑惑,方法肯定没问题,但是运行时间特长。。。其实delete的时候,根本不需要把stack2在倒回去。

def deleteHead(self) -> int:
        if self.stack2: return self.stack2.pop()
        if len(self.stack1)==0: return -1
        while self.stack1:
            self.stack2.append(self.stack1.pop())
        return self.stack2.pop()

**自我反思:**有的时候方法还是欠优化吧,做好优化才能提升效率。

剑指10-1.斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:1

1.我愿称之为动态规划的鼻祖。

class Solution:
    def fib(self, n: int) -> int:
        a = 0
        b = 1
        for _ in range(n):
            a, b = a+b, a
        return a%1000000007

**自我反思:**万恶资源,由此引出了太多太多的动态规划题。。。

剑指10-2.青蛙跳台阶问题

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

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:2

1.其实就是个斐波那契问题,就是最后一步爬一阶+最后一步爬两阶。

class Solution:
    def numWays(self, n: int) -> int:
        a = 1
        b = 1
        if n==0 or n==1:return a
        for i in range(2,n+1):
            a, b = a+b, a     
        return a%1000000007

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

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

示例 1:

输入:[3,4,5,1,2]
输出:1

1.呆呆办法,之间遍历数组,找到那个突然减小的数,就是最小数。

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        for i in range(len(numbers)-1):
            if numbers[i]>numbers[i+1]:
                return numbers[i+1]
        return numbers[0]

2.二分法,两个指针i和j,求中间位置mid,然后比较mid和j的大小,如果mid>j,证明mid在左排序数组里,那最小值就在[mid+1,j]中,如果mid<j,那么mid在右排序数组中,最小值就在[i,mid]中,如果mid==j,两种可能都有,所以缩小j的范围,继续查。

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        i, j = 0, len(numbers)-1
        while i != j:
            mid = int((i+j)/2)
            if numbers[mid] > numbers[j]: i = mid + 1
            elif numbers[mid] < numbers[j]: j = mid
            else: j -= 1
        return numbers[i]

**自我反思:**暴力搜索的时间复杂度为O(N),而二分法为O(logN)

剑指12.矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。

[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]

但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

示例 1:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true

1.整理题目的时候,对自己答案的长度十分震惊。具体来讲就是先找到所有可能的开始的位置,然后从这些可能的开始位置出发进行深度优先搜索,每次都只能前进到当前位置的周围四个位置,而且这个位置还不能在之前出现过,直到找到全部字符串。

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        may = []
        self.max_row = len(board)
        self.max_col = len(board[0])
        self.board = board
        self.result = False
        if len(word)>self.max_row*self.max_col:
            return False
        for i in range(self.max_row):
            for j in range(self.max_col):
                if self.board[i][j] == word[0]:
                    may.append([i,j])
        if len(word) == 1 and len(may)>0:
            return True
        for op in may:
            if self.result:
                break
            self.recursion([],op,word[1:],[op])
        return self.result
    def recursion(self,pre,loc,s,done):
        if self.result == True:
            return 
        r = loc[0]
        c = loc[1]
        around = [[r-1,c],[r+1,c],[r,c-1],[r,c+1]]
        if pre in around:
            around.remove(pre)
        for location in around:
            if location[0]>=self.max_row or location[1]>=self.max_col or location[0]<0 or location[1]<0 or [location[0],location[1]] in done:
                continue
            if self.board[location[0]][location[1]] == s[0]:
                done.append([location[0],location[1]])
                if len(s)==1:
                    self.result = True
                    return
                else:
                    self.recursion(loc,[location[0],location[1]],s[1:],done)
                done.pop()

2.其实解题的思路都是一样的,但是我写的实在是太啰嗦了,仿照着K神的思路,简化了一波代码:

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        def recursion(i,j,k):
            if not 0<=i<len(board) or not 0<=j<len(board[0]) or board[i][j]!=word[k]:
                return False
            if k == len(word)-1:
                return True
            board[i][j] = '.'
            result = recursion(i-1,j,k+1) or recursion(i+1,j,k+1) or recursion(i,j-1,k+1) or recursion(i,j+1,k+1)
            board[i][j] = word[k]
            return result
        
        for i in range(len(board)):
            for j in range(len(board[0])):
                if recursion(i,j,0):
                    return True
        return False

**自我反思:**其实当时的思路里存在很多无用功,比如没必要先找出可能的起始点,比如可以采用剪枝的思路,将遍历过的位置编程‘.’,不必像我那样专门整个数组,放走过的路径。。。虽然最后时间效率上提升那么大(因为基本思路一致),但这种小细节的改进确实能提升代码的可读性和效率。

剑指13.机器人的运动范围

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

示例 1:

输入:m = 2, n = 3, k = 1
输出:3

1.自己的方法依然十分拉垮,打败了5%的用户。总结来讲就两个字,硬走。设计了两个函数,一个judge判断这个位置符不符合条件,然后写了一个深度优先搜索,去遍历,每踩到一个合适的位置,就把这个位置写进可行解集合里,最后返回集合的长度。

class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
        self.gone = [[0,0]]
        def judge(i,j):
            temp = i%10 + i//10%10 + i//10//10%10 + j%10 + j//10%10 + j//10//10%10
            if temp <= k and i>=0 and i < m and j >=0 and j < n:
                return True
            return False

        def recursion(loc):
            around = [[loc[0]-1,loc[1]],[loc[0]+1,loc[1]],[loc[0],loc[1]-1],[loc[0],loc[1]+1]]
            for next_step in around:
                if next_step not in self.gone and judge(next_step[0],next_step[1]):
                    self.gone.append(next_step)
                    recursion(next_step)
        recursion([0,0])
        return len(self.gone)

2.K神解法,dfs的话,其实只需要向下或者向右走就可以了。

class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
        def recursion(i,j,si,sj):
            if i >=m or j >=n or k<si+sj or (i,j) in visited: return 0
            visited.add((i,j))
            return 1 + recursion(i+1,j,si+1 if (i+1)%10!=0 else si-8,sj) + recursion(i,j+1,si,sj+1 if (j+1)%10!=0 else sj-8)

        visited = set()
        return recursion(0,0,0,0)

3.广度优先算法待补充
**自我反思:**说实话,看大佬的代码,真的跟诗一样,真的值得反复揣摩。反观自己的代码,真的又臭又烂又长,效率还低。

剑指14-1.剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

1.这道题最最关键的地方在于,一定要想到绳子尽量都剪成3的长度,最终的乘积才是最大的。但是,当时以为2也可以,所以分了两种可能。

class Solution:
    def cuttingRope(self, n: int) -> int:
        self.result = []
        if n == 2:
            return 1
        if n == 3:
            return 2
        self.cut = [2,3]
        def recursion(lenth,temp,m):
            if lenth <= 0:
                if lenth == 0 and m > 1:
                    self.result.append(temp)
                return
            for may in self.cut:
                lenth = lenth - may
                temp = temp * may
                m += 1
                recursion(lenth,temp,m)
                lenth = lenth + may
                temp = temp / may
                m -= 1
        recursion(n,1,0)
        return int(max(self.result))
  1. k神代码,其实只需要切3就可以了,如果剩1,就拆一个3出来,变成2+2,如果剩2,就直接乘2.
class Solution:
    def cuttingRope(self, n: int) -> int:
        if n <= 3: return n-1
        a,b = n//3,n%3
        if b == 0: return 3**a
        if b == 1: return (3**(a-1))*4
        return (3**a)*2

**自我反思:**这道题的难点就在于选择合适的切绳长度,但如果是第一次遇到这种题或者在有限的时间里,其实很难想到。

剑指14-2.剪绳子2

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

1.其实题目跟上一个题是一样的,但是多了溢出的问题。这里抄了份最简单的代码。(因为我一开始考虑错了,我直接套用了上一题的代码,然后只在最后的结果那取了余,其实这是不对的,因为过程中就可能已经溢出了。)

class Solution:
    def cuttingRope(self, n: int) -> int:
        result = 1
        if n < 4:
            return n - 1
        while n > 4:
            result = result * 3 %1000000007
            n -= 3
        return result * n % 1000000007

**自我反思:**真的太naive了。完全没有考虑过程中会溢出的问题,这种要是面试,会被当场逮住吧。

剑指15.二进制中1的个数

请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。

示例 1:

输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。

1.考虑获得数字的二进制表示,除2取余,或者python用函数bin。

class Solution:
    def hammingWeight(self, n: int) -> int:
        return list(bin(n)).count('1')

2.通过位运算符,每次判断n的最后一位是不是1,将n和1相与,然后将n右移1位。

class Solution:
    def hammingWeight(self, n: int) -> int:
        result = 0
        while n:
            result += n&1
            n >>= 1
        return result

**自我反思:**面试的时候还是用第二种会加分

剑指16.数值的整数次方

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000

1.因为n有可能非常大,如果用傻瓜算法,去循环N次,一定会超时。所以每次把n缩小一半,让x*x

class Solution:
    def myPow(self, x: float, n: int) -> float:
        result = 1
        if n == 0:return 1
        if n<0:
            x = 1/x
            n = -n
        while n!= 1:
            temp = n%2
            n = n//2
            if temp == 1:
                result = result * x
            x = x*x
        return result*x

**自我反思:**基本思路就是这样,如果要考虑越界问题,每次乘的时候还得加一个取模。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值