剑指offer68题回顾

其实字节的面的coding真的不难,因为不一定是题库里面的,所以一些特殊的case其实也没有,主要还是自己输入case进行尝试,所以难度已经其实大大降低了,重点是保证思路正确,bug-free.

注意:
len(" ") = 2, len(“”) = 0

把字符串转换成整数

在这里插入图片描述
值得注意的点,不可以直接使用"5" - “4”,字符不可以直接与字符运算,可以使用ord(),转换为ASCII码.

class Solution:
    def StrToInt(self, s):
        # write code here
        if not s:
            return 0
        out = 0
        while s[0] == " ":
            s = s[1:]
        for i in range(len(s)):
            if i == 0 and (s[i] == "+" or s[i] == "-"):
                continue
            elif s[i] == ".":
                break
            elif s[i] > "9" or s[i] < "0":
                return 0
            else:
                out = out * 10 + (ord(s[i]) - ord("0"))
        if s[0] == "-":
            return -out
        else:
            return out

链表倒数第k个节点

在这里插入图片描述

class Solution:
    def FindKthToTail(self , pHead , k ):
        # write code here
        if k <= 0:###漏洞~~如果k<=0,牛客考虑一定要面面俱到
            return
        if not pHead:
            return 
        num = 1
        head1 = pHead
        head2 = pHead
        while head1.next:
            head1 = head1.next
            if num >=  k:
                head2 = head2.next
            num += 1
        if num < k:
            return 
        return head2

逆序对

在这里插入图片描述
其实这道题的关键是想到排序,逆序的话,那么只要纠正序的数量求出来即可.首先想一想快排,快排不行,因为快排会改变了原有的相对顺序,也就是之前说的稳定性比较低–虽然稳定性指的是相同的数,前后的相对位置改变了.

所以想到归并排序,可以直到归并排序是先分后并,所以相对位置是不会改变的.即可通过归并排序的过程发现的逆序对的数量进行叠加即可.

class Solution:
    def InversePairs(self, data):
        # write code here
        # 归并排序
        if not data:
            return 0
        self.count = 0
#         def merge(data, left, mid, right):
#             nums1 = data[left:mid+1]
#             nums2 = data[mid+1:right+1]
#             res1 = 0
#             res2 = 0
#             res = []
            
#             while res1 < len(nums1) and res2 < len(nums2):
#                 if nums1[res1] <= nums2[res2]:
#                     res.append(nums1[res1])
#                     res1 += 1
#                 else:
#                     res.append(nums2[res2])
#                     res2 += 1
#                     self.count += mid+1-left-res1 # 难点~~~只是加上当前还没归类的部分而已!!
#             if res1 == len(nums1):
#                 res += nums2[res2:]
#             else:
#                 res += nums1[res1:]
#             data[left:right+1] = res[:]
        def merge(data, left, mid, right):
            nums1 = data[left:mid+1]
            nums2 = data[mid+1:right+1]
            res1 = 0
            res2 = 0
            for i in range(left, right+1):
                if res1 < mid+1-left and res2 < right-mid:
                    if nums1[res1] <= nums2[res2]:
                        data[i] = nums1[res1]
                        res1 += 1
                    else:
                        data[i] = nums2[res2]
                        res2 += 1
                        self.count += mid+1-left-res1 # 难点~~~只是加上当前还没归类的部分而已!
                elif res1 < mid+1-left:
                    data[i] = nums1[res1]
                    res1 += 1
                else:
                    data[i] = nums2[res2]
                    res2 += 1
            
        def merge_sort(data, left, right): # 这里的right应该是可以取到的
            if left < right:
                mid = (left + right)//2
                merge_sort(data, left, mid)
                merge_sort(data, mid+1, right)
                merge(data, left, mid, right)
        merge_sort(data, 0, len(data)-1)
        return self.count%1000000007

剪绳子

在这里插入图片描述
这题可以使用DP,也可以通过数学推导得到尽量分到2/3
*DP就是dp[n]代表n长度可以得到的最大乘积,转移就是dp[n] = dp[n-i]dp[i]–需要注意的一点是大于4的肯定是拆了更好,但是2/3不拆更好,所以先排除这两种情况.

# -*- coding:utf-8 -*-
class Solution:
    def cutRope(self, number):
        # write code here
        # 其实至少剪两段
        if number == 2:
            return 1
        if number == 3:
            return 2
        if number <= 1:
            return 
        dp = [0]*(1+number)
        for i in range(1, number+1):
            if i == 1:
                dp[i] = 1
            elif i == 2:
                dp[i] = 2
            elif i == 3:
                dp[i] = 3
            else:
                for j in range(2, i-1): #不会剪成1的.
                    dp[i] = max(dp[i], dp[i-j]*dp[j])
        return dp[-1]
    # 这里会有个问题,不一定要拆开,可能不拆更大~其实只有2/3会有顾虑,其他肯定是拆开更大.

数值的整数次方

在这里插入图片描述

# -*- coding:utf-8 -*-
# bug_free的关键是先考虑好所有的边界情况,一般来说边界满足了,都可以满足.
class Solution:
    def Power(self, base, exponent):
        # write code here
        if base == 0:
            return 0
        if exponent == 0:
            return 1
        if exponent < 0:
            reverse = 1
            exponent = abs(exponent)
        else:
            reverse = 0
        out = 1 # 注意初始值
        if exponent % 2 == 0:
            while exponent > 1:
                out = base * base
                exponent = exponent // 2
                base = out
        else:
            exponent -= 1
            res = base
            while exponent > 1:
                out = base * base
                exponent = exponent // 2
                base = out
            out *= res
        if reverse:
            return 1/out
        return out
    

打印从1到最大的n位数

在这里插入图片描述

虽然python不需要考虑大数问题,但是还是为了面试谨慎.—掌握大数问题,解决办法就是通过字符串来实现,也就是得到n位数的全排列,然后把字符后转化成数.

    
class Solution:
    def printNumbers(self, n: int) -> List[int]:
        if n < 1:
            return []
        out = []
        def backtrack(n, num):
            if len(num) == n:
                for j in range(len(num)):
                    if num[j] != "0":
                        out.append(int(num[j:]))
                        return 
                return
            for i in range(10):
                add = str(i)
                backtrack(n, num+add)
        backtrack(n, "")
        return out

正则表达式的匹配

在这里插入图片描述

class Solution:
    def match(self , str , pattern ):
        if not str and not pattern:
            return True
        elif not pattern:
            return False
        elif not str and pattern[-1] != "*":
            return False
        
        def is_match(str, pattern):
            while pattern and pattern[0] == "*":
                pattern = pattern[1:]
            if not str and not pattern:
                return True
            elif not pattern:
                return False
            elif not str:
                if pattern[-1] != "*":   # 关键直接破除~~
                    return False
                else:
                    return True
            if len(pattern) > 1 and pattern[1] == "*":
                # 本次递归,重复一次 或者 重复0次
                if pattern[0] in {
    
    str[0], "."}: ### 这次出错地方
                    return is_match(str[1:], pattern) or is_match(str, pattern[2:])
                else:
                    return is_match(str, pattern[2:])
            else:
                if pattern[0] in {
    
    str[0], "."}:
                    return is_match(str[1:], pattern[1:])
                else:
                    return False
        return is_match(str, pattern)
    

表示数值的字符串

技巧,分成e前和e后两个子串进行判断比较简单~~
标题

class Solution:
    def isNumeric(self , str ):
        # write code here
        if not str:
            return True
        if str == "+" or str == "-" or str=="E" or str == "e":
            return False
        if str[-1] == "e" or str[-1] == "E":
            return False
        done_o = 0
        res1 = ""
        res2 = ""
        for i in range(len(str)):
            if str[i] == "e" or str[i] == "E":
                res1 = str[0:i]
                res2 = str[i+1:]
                break
        if not res1 and not res2:
            res1 = str
        
        for i in range(len(res1)):
            if res1[i] == "+" or res1[i] == "-":
                if i != 0:
                    return False
                
            if res1[i] not in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "+", "-"]:
                return False
            
            if done_o:
                if res1[i] == ".":
                    return False
            if res1[i] == ".":
                if i == 0:
                    return False
                done_o = 1
        for i in range(len(res2)):
            if res2[i] == ".":
                return False
            if res2[i] == "+" or res2[i] == "-":
                if i != 0:
                    return False
                
            if res2[i] not in ["+", "-", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]:
                return False
        return True
        

两道回溯法—解决

套框架还是很轻松的

矩阵中的路径

在这里插入图片描述

class Solution:
    def hasPath(self , matrix , rows , cols , str ):
        # write code here
        res = []
        for i in range(cols, len(matrix)+1, cols):
            res.append(matrix[i-cols:i])
        matrix = res
        if not str:
            return True
        if rows <= 0 or cols <= 0:
            return False
        if rows == 1 and cols == 1:
            return matrix[0][0] == str[0]
        dp = []
        for i in range(rows):
            dp.append([0]*cols)
        self.rows = rows
        self.cols = cols
        
        def backtrack(row, col, str):
            if not str:
                return True
            if col >= self.cols or col < 0 or row >= self.rows or row < 0:
                return False
            if dp[row][col]:
                return False
            if matrix[row][col] == str[0]:
                if len(str) == 1:  ####### 要判断[1:]是否有效~~
                    return True
                dp[row][col] = 1
                if backtrack(row+1, col, str[1:]) or backtrack(row, col+1, str[1:]) \
            or  backtrack(row-1, col, str[1:]) or backtrack(row, col-1, str[1:]):
                    dp[row][col] = 0
                    return True
                else:
                    dp[row][col] = 0
                    return False
            else:
                return False
            
        for i in range(rows):
            for j in range(cols):
                if backtrack(i, j, str):
                    return True
        return False

机器人的运动范围

在这里插入图片描述
#这题有个注意点:是可以返回的,所以下面设置的self.maximum是每次走过一个格子就加一次.
#也就是每个方向能走最远的路程相加,不是说走一条路,就不能回头的那种~~~
# dp[i][j] = 0 #如果是挑选最长的某一条路,才需要!!!

解法一:

class Solution:
    def movingCount(self, threshold, rows, cols):
        # write code here
        # 回溯
        if not rows or not cols:
            return 0
        self.threshold = threshold
        dp = []
        self.maximum = 0
        for i in range(rows):
            dp.append([0] * cols)

        def get_sum(num):
            out = 0
            while num > 0:
                out += num % 10
                num //= 10
            return out

        def backtrack(i, j, out):
            if i < 0 or i >= rows or j < 0 or j >= cols:
                return
            if not dp[i][j]:
                sum = get_sum(i) + get_sum(j)
                if sum > self.threshold:
                    dp[i][j] = 1
                    return
                else:
                    # if out + 1 > self.maximum:  #应该是求和的关系~~不是求最大,可以返回走过的地方
                    self.maximum += 1
                    dp[i][j] = 1  #走过了不用再走
                    backtrack(i + 1, j, out + 1)
                    backtrack(i - 1, j, out + 1)
                    backtrack(i, j + 1, out + 1)
                    backtrack(i, j - 1, out + 1)
                    # dp[i][j] = 0 #如果是挑选最长的某一条路,才需要!!!!!!!!!
            else:
                return

        backtrack(0, 0, 0)
        return self.maximum

解法二:直接返回每个方向能走多远,并相加

class Solution:
    def movingCount(self, threshold, rows, cols):
        # write code here
        # 回溯
        if not rows or not cols:
            return 0
        self.threshold = threshold
        dp = []
        for i in range(rows):
            dp.append([0] * cols)

        def get_sum(num):
            out = 0
            while num > 0:
                out += num % 10
                num //= 10
            return out

        def backtrack(i, j, out):
            if i < 0 or i >= rows or j < 0 or j >= cols:
                return out
            if not dp[i][j]:
                sum = get_sum(i) + get_sum(j)
                if sum > self.threshold:
                    dp[i][j] = 1
                    return out
                else:
                    dp[i][j] = 1
                    out += backtrack(i + 1, j, 0) + \
                    backtrack(i - 1, j, 0) + \
                    backtrack(i, j + 1, 0) + \
                    backtrack(i, j - 1, 0) + 1
                    return out
            else:
                return out
        return backtrack(0, 0, 0)

旋转数组的最小元素

在这里插入图片描述
三种情况, (mid>right)mid属于左数组,那么最小的数肯定在[mid+1, right];
(mid<right),mid属于右数组,最小的数肯定在[left, mid];
mid==right,则无法确认属于左数组还是右,故right-1
—必须保证范围缩小.
错误答案:case:
其实这里应该考虑:[100, 1000, 8, 9, 9, 9, 9, 9, 9]:mid属于左数组—错了!!
#100 100 100 100 100 100 100 9 9 9 9 9 9 100 : mid属于右数组
这两种情况

# -*- coding:utf-8 -*-
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        # write code here
        if not rotateArray:
            return 0
        def half_search(nums, left, right):
            while left <= right:
                mid = (left + right)//2
                if nums[mid] >= nums[right]:
                    left = mid + 1   # 这种情况没有考虑可能出现在左数组中:[100, 1000, 8, 9, 9, 9, 9, 9, 9]
                else:
                    right = mid
            return right
        return rotateArray[half_search(rotateArray, 0, len(rotateArray) - 1)]

正确答案


class Solution:
    def minArray(self, rotateArray):
        # write code here
        if not rotateArray:
            return 0
        def half_search(nums, left, right):
            while left < right:
                mid = (left + right)//2
                if nums[mid] > nums[right]:
                    left = mid + 1
                elif nums[mid] < nums[right]:
                    right = mid
                else:
                    right -= 1
            return right
        return rotateArray[half_search(rotateArray, 0, len(rotateArray) - 1)]

二分搜搜:找最左边,找最右边

最简单就是通过[8, 8]两个相邻元素来判断了.

def binary_search_left(nums, left, right, target):

    while left < right:
        mid = (left + right) // 2
        if nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid - 1
        else:
            right = mid
    return right

def binary_search_right(nums, left, right, target):
    while left < right:
        mid = (left + right) // 2
        if nums[mid] <= target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid - 1
        else:
            left = mid + 1
    return left

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

在这里插入图片描述
这题很像已知前序遍历和中序遍历重建二叉树,都是从根节点出发的~~

# -*- coding:utf-8 -*-
class Solution:
    def VerifySquenceOfBST(self, sequence):
        # write code here
        # 本题是从根节点入手的~~
        if not sequence:
            return False
        def is_BST(sequence):
            done = 0
            res = 0
            if len(sequence) == 1:
                return True
            for i in range(0, len(sequence)-1):
                if not done and sequence[i] > sequence[-1]: 一旦比根节点大,那么后续必然都比根节点大.
                    res = i
                    done = 1
                if done and sequence[i] < sequence[-1]:
                    return False
            if not res: # 全部比根节点小,是对的
                return True
            return is_BST(sequence[0:res]) and is_BST(sequence[res:len(sequence)-1])
        return is_BST(sequence)

复杂链表复制

在这里插入图片描述

class Solution:
    # 返回 RandomListNode
    def Clone(self, pHead):
        # write code here
        if not pHead:
            return 
        record = {
    
    }
        head1 = pHead
        num = 0
        while head1:
            if head1 in record:
                head = record[head1]
            else:
                head = RandomListNode(head1.label)
            if num == 0:
                first = head
                num += 1
            record[head1] = head
            if not head1.random:
                head.random = None
            elif head1.random in record:
                head.random = record[head1.random]
            else:
                head.random = RandomListNode(head1.random.label)
                record[head1.random] = head.random
            
            if not head1.next:
                head.next = None
            elif head1.next in record:
                head.next = record[head1.next]
            else:
                head.next = RandomListNode(head1.next.label)
                record[head1.next] = head.next
            head1 = head1.next
        return first

把数组排成最小的数

在这里插入图片描述
在这里插入图片描述

这道题说难不难,也不简单,刚开始尝试使用回溯法得到所有的组合,然后进行比较得到最小的,这样的时间复杂度太高,并且出现一个错误就是比较的时候转换为了int,这个题目有隐含的大数问题,最好是使用字符串进行比较。

若拼接字符串 x + y > y + xx+y>y+x ,则 xx “大于” yy ;

反之,若 x + y < y + xx+y<y+x ,则 xx “小于” yy ;

如果转换为int会出现说明里出现的情况无法正确对待,int(“0123445”) = 123445,所以不能直接转换为int。
下面是直接结合某一种排序方法进行,结合对应的判断规则即可。

# 若拼接字符串 x + y > y + xx+y>y+x ,则 xx “大于” yy ;
# 反之,若 x + y < y + xx+y<y+x ,则 xx “小于” yy ;
# 使用快排
class Solution:
    def minNumber(self, nums: List[int]) -> str:
        if not nums:
            return ""

        def partition(nums, left, right):
            res1 = left - 1
            res2 = left - 1
            key = nums[right]
            for i in range(left, right):
                if nums[i] + key <= key + nums[i]:
                    res1 += 1
                    res2 += 1
                    nums[res1], nums[res2] = nums[res2], nums[res1]
                else:
                    res2 += 1
            #!!!!!!! nums[res1+1], nums[-1] = nums[-1], nums[res1+1]
            nums[res1+1], nums[right] = nums[right], nums[res1+1]
            return res1+1

        def quick_sort(nums, left, right):
            if left < right:
                res = partition(nums, left, right)
                quick_sort(nums, left, res-1)
                quick_sort(nums, res+1, right)

        for i in range(len(nums)):
            nums[i] = str(nums[i])
        quick_sort(nums, 0, len(nums) - 1)
        return "".join(nums)

把数字翻译成字符串

在这里插入图片描述
说到底也就是dp[i] = dp[i-1] + dp[i-2]

class Solution:
    def translateNum(self, num: int) -> int:
        # 递归解决
        record = {
    
    }
        char = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", \
        "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
        for i in range(26):
            record["%s"%i] = char[i]
        num = str(num)
        self.out = 0
        def translate(num, sub):
            if not num:
                self.out += 1
                return             
            translate(num[1:], sub+record[num[0]])
            if len(num) > 1 and num[0:2] in record:
                translate(num[2:], sub+record[num[0:2]])

        translate(num, "")
        return self.out

# 上述递归办法有重叠子问题。12258为例: 12 258,  1 2 258


## 动态规划
# dp[i] = dp[i-1] + dp[i-2]
class Solution:
    def translateNum(self, num: int) -> int:
        # 递归解决
        record = {
    
    }
        char = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", \
        "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
        for i in range(26):
            record["%s"%i] = char[i]
        num = str(num)
        self.out = 0
        dp = [0]*(len(num)+1)
        dp[0] = 1
        dp[1] = 1
        for i in range(2, len(num)+1):
            if num[i-2:i] in record:
                dp[i] = dp[i-1] + dp[i-2]
            else:
                dp[i] = dp[i-1]
        return dp[-1]

# 初始状态: dp[0] = dp[1] = dp[0] = dp[1] = 1 ,即 “无数字” 和 “第 11 位数字” 的翻译方法数量均为 11 ;

# 返回值: dp[n]dp[n] ,即此数字的翻译方案数量。

# Q: 无数字情况 dp[0] = 1dp[0]=1 从何而来?
# A: 当 numnum 第 1, 21,2 位的组成的数字 \in [10,25]∈[10,25] 时,显然应有 22 种翻译方法,即 dp[2] = dp[1] + dp[0] = 2dp[2]=dp[1]+dp[0]=2 ,而显然 dp[1] = 1dp[1]=1 ,因此推出 dp[0] = 1dp[0]=1 。

礼物的最大价值

在这里插入图片描述

class Solution:
    def maxValue(self, grid: List[List[int]]) -> int:
        
        if not grid:
            return 0
        # 动态规划
        m = len(grid)
        n = len(grid[0])

        dp = []
        for i in range(m):
            dp.append([0]*n)

        for i in range(m):
            for j in range(n):
                if i == 0 and j == 0:
                    dp[i][j] = grid[i][j]
                elif i == 0:
                    dp[i][j] = dp[i][j-1] + grid[i][j]
                elif j == 0:
                    dp[i][j] = dp[i-1][j] + grid[i][j]
                else:    
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j]
        return dp[-1][-1]

最长不含重复字符的子字符串

在这里插入图片描述
最容易的想是字典的办法,但是问题是老的保存会影响现在的情况。所以关键是能否使用额外的条件判别。不可以自认为替换掉了,到那时除了替换掉的那一个之外,前面的其实没有被替换

class Solution:
    def lengthOfLongestSubstring(self , arr ):
        # write code here
        # 这道题开始出现的问题在于没有意识到,如果是一个新的开始,那么其实前面进入字典的键不再生效,从上一个重复的键之前的那些键,所以这种做法是通过OrderedDict的有序性来解除这种危机。

        # length  = end - start + 1
        ########################################################## 致命错误。
        # "abba"  ,  当走到第三个的时候,start变成了b,但是a仍保留在了dict里面~~
        ########################################################## 致命错误。
        # 解决通过引入判断条件,是否大于length来判断~
        if not len(arr):
            return 0
        record = {
    
    }
        lengths = []
        length = 0
        longest = 0
        start = 0
        for i in range(len(arr)):
            if arr[i] not in record:
                record[arr[i]] = i
                length += 1
            else:

                #######################################
                if i - record[arr[i]] + 1 > length + 1:
                    record[arr[i]] = i
                    length += 1
                    continue
                ####################################### 有时候需要多开动脑筋想象办法
                if length > longest:
                    longest = length
                index = record[arr[i]]
                length = i - (index + 1) + 1
                start = index + 1
                record[arr[i]] = i
        if length > longest:
            longest = length
        return longest

猜你喜欢

转载自blog.csdn.net/caihuanqia/article/details/114634724