《剑指 Offer》(第 2 版) 题解(Python 语言实现)第 11-20 题


第 11 题:旋转数组中的最小数字

传送门:AcWing:旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

输入一个升序的数组的一个旋转,输出旋转数组的最小元素。

例如数组 [3,4,5,1,2][1,2,3,4,5] 的一个旋转,该数组的最小值为 1 1 1

数组可能包含重复项。

注意:数组内所含元素非负,若数组大小为0,请返回-1。

样例:

输入:nums=[2, 2, 2, 0, 1]

输出:0

思路1 :这是典型的可以使用二分法解决的问题,应用二分法的模板。特别注意,数组可能包含重复项,因此中间项如果等于末尾项,例如:[1, 1, 1, 1, 1, 0, 1] ,不能砍掉一半,只能把末尾项排除掉。

Python 代码:

class Solution:
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        size = len(nums)
        if size == 0:
            return -1
        l = 0
        r = size - 1
        while l < r:
            mid = l + (r - l) // 2

            if nums[mid] < nums[r]:
                # mid 有可能是最小值
                # [7,8,1,2,3]
                r = mid
            elif nums[mid] > nums[r]:
                # mid 肯定不是最小值
                # [7,8,9,10,11,1,2,3]
                l = mid + 1
            else:
                # 都有可能,所以就把 r 排除了
                # [1,1,1,1,1,0,1]
                assert nums[mid] == nums[r]
                r = r - 1
        return nums[l]

思路2 :还可以使用“分治法”,“分治法”就不用在乎有没有重复项了。但是“分治法”无异于把整个数组都看一遍,时间复杂度为 O ( n ) O(n) O(n)

Python 代码:

class Solution:
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        size = len(nums)
        if size == 0:
            return -1
        if size == 1:
            return nums[0]
        return self.__findMin(nums, 0, size - 1)

    def __findMin(self, nums, left, right):
        if left == right:
            return nums[left]
        if left + 1 == right:
            return min(nums[left], nums[right])
        mid = left + (right - left) // 2
        return min(self.__findMin(nums, left, mid), self.__findMin(nums, mid + 1, right))

第 12 题:矩阵中的路径

传送门:AcWing:矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。

路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。

如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。

注意:

  • 输入的路径不为空;
  • 所有出现的字符均为大写英文字母;

样例:

matrix=
[
       ["A","B","C","E"],
       ["S","F","C","S"],
       ["A","D","E","E"]
]

str="BCCE" , return "true" 

str="ASAE" , return "false"

思路:典型的 floodfill 解法,本质上是递归回溯算法。

Python 代码:

class Solution(object):
    directions = [(-1, 0), (1, 0), (0, 1), (0, -1)]

    def hasPath(self, matrix, string):
        """
        :type matrix: List[List[str]]
        :type string: str
        :rtype: bool
        """
        rows = len(matrix)
        if rows == 0:
            return False
        cols = len(matrix[0])

        marked = [[False for _ in range(cols)] for _ in range(rows)]

        for i in range(rows):
            for j in range(cols):
                if self.__has_path(matrix, string, 0, i, j, marked, rows, cols):
                    return True
        return False

    def __has_path(self, matrix, word, index, start_x, start_y, marked, m, n):
        # 注意:首先判断极端情况
        if index == len(word) - 1:
            return matrix[start_x][start_y] == word[-1]
        if matrix[start_x][start_y] == word[index]:
            # 先占住这个位置,搜索不成功的话,要释放掉
            marked[start_x][start_y] = True
            for direction in self.directions:
                new_x = start_x + direction[0]
                new_y = start_y + direction[1]
                if 0 <= new_x < m and 0 <= new_y < n and not marked[new_x][new_y]:
                    if self.__has_path(matrix, word, index + 1, new_x, new_y, marked, m, n):
                        return True
            marked[start_x][start_y] = False
        return False


if __name__ == '__main__':
    matrix = [
        ["A", "B", "C", "E"],
        ["S", "F", "E", "S"],
        ["A", "D", "E", "E"]
    ]

    str = "ABCEFSADEESE"

    solution = Solution()
    result = solution.hasPath(matrix, str)
    print(result)

同 LeetCode 第 79 题,传送门:79. 单词搜索

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例:

board =
[
       ['A','B','C','E'],
       ['S','F','C','S'],
       ['A','D','E','E']
]

给定 word = “ABCCED”, 返回 true.
给定 word = “SEE”, 返回 true.
给定 word = “ABCB”, 返回 false.

思路:其实就是 floodfill 算法,这是一个非常基础的算法,一定要掌握。特别要弄清楚,marked 数组的作用,一开始要占住这个位置,发现此路不通的时候,要释放掉。

Python 代码:

class Solution:
    #         (x-1,y)
    # (x,y-1) (x,y) (x,y+1)
    #         (x+1,y)

    directions = [(0, -1), (-1, 0), (0, 1), (1, 0)]

    def exist(self, board, word):
        """
        :type board: List[List[str]]
        :type word: str
        :rtype: bool
        """

        m = len(board)
        n = len(board[0])

        marked = [[False for _ in range(n)] for _ in range(m)]
        for i in range(m):
            for j in range(n):
                # 对每一个格子都从头开始搜索
                if self.__search_word(board, word, 0, i, j, marked, m, n):
                    return True
        return False

    def __search_word(self, board, word, index, start_x, start_y, marked, m, n):
        # 先写递归终止条件
        if index == len(word) - 1:
            return board[start_x][start_y] == word[index]

        # 中间匹配了,再继续搜索
        if board[start_x][start_y] == word[index]:
            # 先占住这个位置,搜索不成功的话,要释放掉
            marked[start_x][start_y] = True
            for direction in self.directions:
                new_x = start_x + direction[0]
                new_y = start_y + direction[1]
                if 0 <= new_x < m and 0 <= new_y < n and \
                        not marked[new_x][new_y] and \
                        self.__search_word(board, word,
                                           index + 1,
                                           new_x, new_y,
                                           marked, m, n):
                    return True
            marked[start_x][start_y] = False
        return False

Java 代码:

public class Solution {

    /**
     *       x-1,y
     * x,y-1   x,y    x,y+1
     *       x+1,y
     */
    private int[][] direct = new int[][]{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};

    public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
        int len = matrix.length;
        if (len == 0) {
            return false;
        }
        boolean[] marked = new boolean[len];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (dfs(matrix, rows, cols, str, str.length, marked, i, j, 0)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean dfs(char[] matrix, int rows, int cols, char[] str, int len, boolean[] marked, int i, int j, int start) {
        // 匹配到最后,说明找到一条路径
        int index = getIndex(i, j, cols);
        if (start == len - 1) {
            return matrix[index] == str[start];
        }
        // 要特别小心!
        marked[index] = true;
        if (matrix[index] == str[start]) {
            // 当前匹配了,才开始尝试走后面的路
            for (int k = 0; k < 4; k++) {
                // 特别小心,一定是一个初始化的新的变量
                int newi = i + direct[k][0];
                int newj = j + direct[k][1];
                int nextIndex = getIndex(newi, newj, cols);
                if (inArea(newi, newj, rows, cols) && !marked[nextIndex]) {
                    // marked[nextIndex] = true; 不在这里设置
                    if (dfs(matrix, rows, cols, str, len, marked, newi, newj, start + 1)) {
                        return true;
                    }
                    // marked[nextIndex] = false; 不在这里设置
                }
            }
        }
        // 要特别小心!
        marked[index] = false;
        return false;
    }

    private int getIndex(int x, int y, int cols) {
        return x * cols + y;
    }

    private boolean inArea(int x, int y, int rows, int cols) {
        return x >= 0 && x < rows && y >= 0 && y < cols;
    }

    public static void main(String[] args) {
        char[] matrix = new char[]{'a', 'b', 't', 'g',
                'c', 'f', 'c', 's',
                'j', 'd', 'e', 'h'};
        int rows = 3;
        int cols = 4;
        Solution solution = new Solution();
        char[] str = "hscfdeh".toCharArray();
        boolean hasPath = solution.hasPath(matrix, rows, cols, str);
        System.out.println(hasPath);
    }
}

第 13 题:机器人的运动范围

传送门:AcWing:机器人的运动范围

地上有一个 m m m 行和 n n n 列的方格,横纵坐标范围分别是 0 ∼ m − 1 0∼m−1 0m1 0 ∼ n − 1 0∼n−1 0n1

一个机器人从坐标 ( 0 , 0 ) (0,0) (0,0)的格子开始移动,每一次只能向左,右,上,下四个方向移动一格。

但是不能进入行坐标和列坐标的数位之和大于 k k k 的格子。

请问该机器人能够达到多少个格子?

样例1:

输入:k=7, m=4, n=5

输出:20

样例2:

输入:k=18, m=40, n=40

输出:1484

解释:当 k 为 18 时,机器人能够进入方格(35,37),因为 3+5+3+7 = 18。
但是,它不能进入方格(35,38),因为 3+5+3+8 = 19。

注意:

  1. 0<=m<=50
  2. 0<=n<=50
  3. 0<=k<=100

思路:使用广度优先搜索,注意不是深度优先搜索。

Python 代码:特别注意,mark 的时候,一定是放入队列的时候就 mark,不是等到出队的时候 mark,否则会出现很多重复

class Solution(object):

    def __count_bit_sum(self, num):
        res = 0
        while num:
            res += num % 10
            num //= 10
        return res

    def __in_area(self, x, y, rows, cols):
        return 0 <= x < rows and 0 <= y < cols

    def movingCount(self, threshold, rows, cols):
        """
        :type threshold: int
        :type rows: int
        :type cols: int
        :rtype: int
        """
        if threshold < 0 or rows == 0 or cols == 0:
            return 0

        if threshold == 0:
            return 1

        directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
        marked = [[False for _ in range(cols)] for _ in range(rows)]

        queue = [(0, 0)]
        res = 0

        while queue:
            top_x, top_y = queue.pop(0)

            for direction in directions:
                new_x = top_x + direction[0]
                new_y = top_y + direction[1]

                if self.__in_area(new_x, new_y, rows, cols) \
                        and not marked[new_x][new_y] \
                        and self.__count_bit_sum(new_x) + self.__count_bit_sum(new_y) <= threshold:
                    queue.append((new_x, new_y))
                    # 注意:应该写在这里,而不是 pop 出队列的时候
                    marked[new_x][new_y] = True
                    res += 1
        return res


if __name__ == '__main__':
    k = 18
    m = 40
    n = 40
    solution = Solution()
    result = solution.movingCount(k, m, n)
    print(result)

第 14 题:剪绳子

说明:同 LeetCode 343 题。

传送门:AcWing 25. 剪绳子

给你一根长度为 n n n 的绳子,请把绳子剪成 m m m 段( m m m n n n 都是整数, 2 ≤ n ≤ 5 8 2 2 \le n \le 58^2 2n582 并且 m ≥ 2 m \ge2 m2)。

每段的绳子的长度记为 k[0]、k[1]、……、k[m]k[0]k[1] … k[m] 可能的最大乘积是多少?

例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。

样例:

输入:8

输出:18

分析:动态规划。关键在于画出树形结构图。

状态:dp[i],这个状态就是题目中要我们求的。把整数 i 至少分割成 2 2 2 个部分,各个部分都大于 0 0 0,它们的乘积。

状态转移方程:用 j 遍历 1,2,…, i -1 ,要么分割成两部分:i - jj,要么是 jdp[i-j],取最大者。

Python 代码:dp[0] 这个位置没有使用

class Solution(object):
    def maxProductAfterCutting(self, length):
        """
        :type length: int
        :rtype: int
        """

        assert length > 1

        dp = [0 for _ in range(length + 1)]

        dp[1] = 1

        for i in range(2, length + 1):
            for j in range(1, i):
                dp[i] = max(dp[i], j * (i - j), j * dp[i - j])
        return dp[length]

LeetCode 343 题:整数拆分

传送门:343. 整数拆分

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例 1:

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

示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

说明: 你可以假设 n 不小于 2 且不大于 58。

分析:这是一个很经典的问题。可以使用贪心算法。

贪心算法: 2 ( n − 2 ) &gt; n 2(n-2)&gt;n 2(n2)>n 得到 n &gt; 4 n &gt; 4 n>4 3 ( n − 3 ) &gt; n 3(n-3)&gt;n 3(n3)>n ,得到 n &gt; 4.5 n&gt;4.5 n>4.5,即 n n n 大于等于 5 5 5 的时候。

结论:不能包含 1 1 1 ,所有的加法因子只能有 2 2 2 3 3 3,最多只有 2 2 2 2 2 2,因此加法因子里没有 4 ​ 4​ 4

image-20190108000201883

Java 代码:

class Solution2 {
    public int integerBreak(int n) {
        if (n <= 2) {
            return 1;
        }
        if (n == 3) {
            return 2;
        }
        if (n == 4) {
            return 4;
        }
        // 接下来就是 n >= 5 的时候的逻辑了
        int res = 1;
        while (n > 4) {
            res *= 3;
            n -= 3;
        }
        res *= n;
        return res;
    }

    public static void main(String[] args) {
        Solution2 solution2 = new Solution2();
        int integerBreak = solution2.integerBreak(8);
        System.out.println(integerBreak);
    }
}

C++ 代码:

image-20190108000417713

第 15 题:二进制中 1 1 1 的个数

传送门:二进制中 1 1 1 的个数

输入一个 32 位整数,输出该数二进制表示中1的个数。

注意

  • 负数在计算机中用其绝对值的补码来表示。

样例1:

输入:9
输出:2
解释:9 的二进制表示是 1001,一共有 2 个 1 。

样例2:

输入:-2
输出:31
解释:-2 在计算机里会被表示成 11111111111111111111111111111110,一共有 31 个 1 。

知识点:1、什么是补码补码就是一个数与另一个数相加,是一个进制表示下很整的数;

2、正数的补码就是它自己,负数在计算机中的表示是它的补码

3、数分为:“有符号整数”与“无符号整数”。

记住:1、n & (n - 1) 把最低位的 1 1 1 变成 0 0 0

2、Python 中的二进制有陷阱,参考资料:https://www.cnblogs.com/klchang/p/8017627.html。

笔记:

image-20190110145738153

image-20190110145826208

分析:位运算的问题,看答案做出来的,记住 n & (n-1) 能够消掉最低位的 1 1 1 即可。

Python 代码1:一位一位算就可以了,注意,做 32 32 32 次就可以了。

class Solution(object):
    def NumberOf1(self, n):
        """
        :type n: int
        :rtype: int
        """
        ans = 0
        for i in range(32):
            if n & 1:
                ans += 1
            n = n >> 1

        return ans

Python 代码2:Python 中的数是长整型,因此一开始做的时候,要把高于 32 32 32 位的全部砍掉

class Solution(object):
    def NumberOf1(self, n):
        """
        :type n: int
        :rtype: int
        """
        # print((-1 & (2**31-1)) >> 1)
        # print((-3) >> 1)
        n = n & (2 ** 32 - 1)
        # print(n)
        count = 0
        while n != 0:
            if n & 1 == 1:
                count += 1
            n = n >> 1
            # print(n)
        return count

Python 代码:以下写法等价

class Solution(object):
    def NumberOf1(self,n):
        """
        :type n: int
        :rtype: int
        """
        counter  = 0
        
        # Python 中的 32 位整数没有溢出这回事,所以要强行变成 32 位
        # 这一步相当于把这个数变成无符号整数,为了通过 judge 才这么做的
        
        n = n & 0xFFFFFFFF
        
        while n:
            n = n &(n-1)
            counter +=1
        return counter

C++ 写法:转成无符号数。

image-20190108001508697

image-20190120110929244

第 16 题:数值的整数次方(快速幂)

传送门:AcWing:数值的整数次方

实现函数double Power(double base, int exponent),求baseexponent次方。

不得使用库函数,同时不需要考虑大数问题。

注意:

  • 不会出现底数和指数同为 0 的情况

样例1:

输入:10 ,2

输出:100

样例2:

输入:10 ,-2

输出:0.01

分析:数值的整数次方,要处理一些细节问题,加法变成乘法。考虑底数为 0 0 0 的时候,指数不能为负数。

思路1:使用递归

Python 代码:

class Solution(object):
    def Power(self, base, exponent):
        """
        :type base: float
        :type exponent: int
        :rtype: float
        """

        if exponent == 0:
            return 1

        if exponent < 0:
            return 1 / self.Power(base, -exponent)

        # 如果是奇数
        if exponent & 1:
            return base * self.Power(base, exponent - 1)
        return self.Power(base * base, exponent >> 1)

思路2:非递归的写法,把 exponent 想象成二进制。

Python 代码:在理解的基础上记住这个写法

class Solution(object):
    def Power(self, base, exponent):
        """
        :type base: float
        :type exponent: int
        :rtype: float
        """

        if exponent < 0:
            base = 1 / base
            # 负数变成正数
            exponent = -exponent

        res = 1
        while exponent:
            if exponent & 1:
                res *= base
            base *= base
            exponent >>= 1
        return res

LeetCode 第 50 题: P o w ( x , n ) Pow(x, n) Pow(x,n)

传送门:50. Pow(x, n)

实现 pow(x, n) ,即计算 x 的 n 次幂函数。

示例 1:

输入: 2.00000, 10
输出: 1024.00000

示例 2:

输入: 2.10000, 3
输出: 9.26100

示例 3:

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

说明:

  • -100.0 < x < 100.0
  • n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。

思路1:使用循环,把指数 n n n 想成二进制

Python 代码:

class Solution:
    def myPow(self, x, n):
        """
        :type x: float
        :type n: int
        :rtype: float
        """
        
        
        if n < 0:
            x = 1 / x
            n = - n
        res = 1
        while n:
            if n & 1 == 1:
                res *= x
            # 注意:这里不要写成  res *= res
            x *= x 
            n >>= 1
        return res

思路2:将循环变成递归操作,每次折半求值,避免死板做循环,这种感觉像加法变乘法。(脑子里回忆公式)。注意细节:底数为 0 0 0 的时候,指数为负数是没有意义的。

Python 代码:递归写法:注意边界条件

class Solution:
    def myPow(self, x, n):
        """
        :type x: float
        :type n: int
        :rtype: float
        """
        # 对 x = 0 , n < 0 还要做特判
        if n == 0:
            return 1
        if n < 0:
            return 1 / self.myPow(x, -n)

        if n & 1:
            return x * self.myPow(x, n - 1)
        return self.myPow(x * x, n // 2)

基本的写法:

https://blog.csdn.net/happyaaaaaaaaaaa/article/details/76552127

image-20190108001835979

模板写法1:

image-20190118001251329

模板写法2:

image-20190120133359593

第 17 题:打印从 1 到最大的 n 位数

第 18-1 题:在 O ( 1 ) O(1) O(1) 时间删除链表结点(多写几遍)

传送门:AcWing:在 O(1) 时间删除链表结点

给定单向链表的一个节点指针,定义一个函数在 O ( 1 ) O(1) O(1) 时间删除该结点。

假设链表一定存在,并且该节点一定不是尾节点。

样例:

输入:链表 1->4->6->8,删掉节点:第 2 个节点即 6(头节点为第 0 个节点)

输出:新链表 1->4->8

思路:待删除的结点是末尾结点的情况比较容易忽略,刚好题目中说“该节点一定不是尾节点”。

Python 代码:

# 28. 在O(1)时间删除链表结点
# 给定单向链表的一个节点指针,定义一个函数在O(1)时间删除该结点。
#
# 假设链表一定存在,并且该节点一定不是尾节点。
# Definition for singly-linked list.
class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None


class Solution(object):
    def deleteNode(self, node):
        """
        :type node: ListNode
        :rtype: void
        """
        next = node.next
        node.val = next.val
        node.next = next.next
        next.next = None

C++ 代码:

image-20190108002212753

第 18-2 题:删除链表中重复的结点

同 LeetCode 第82 题。

传送门:AcWing:删除链表中重复的节点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留。

样例1:

输入:1->2->3->3->4->4->5

输出:1->2->5

样例2:

输入:1->1->1->2->3

输出:2->3

思路:因为头结点可能被删,所以要设置一个虚拟头结点。

Python 写法:

class Solution(object):
    def deleteDuplication(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if head is None:
            return None

        dummy = ListNode(-1)
        dummy.next = head
        cur = dummy

        # 一下子要看两个,所以是
        while cur.next and cur.next.next:
            if cur.next.val == cur.next.next.val:
                # 删除的起点至少是 cur.next.next
                del_node = cur.next.next

                while del_node.next and del_node.val == del_node.next.val:
                    del_node = del_node.next
                # 来到了一个新的结点,值不同

                cur.next = del_node.next
                del_node.next = None
            else:
                cur = cur.next

        return dummy.next

LeetCode 第 82 题: 删除排序链表中的重复元素 II

传送门:82. 删除排序链表中的重复元素 II

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。

示例 1:

输入: 1->2->3->3->4->4->5
输出: 1->2->5

示例 2:

输入: 1->1->1->2->3
输出: 2->3

Java 代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
       if (head == null) {
            return null;
        }
        // 只要涉及头结点的操作,我们都设立虚拟头结点
        ListNode dummyNode = new ListNode(-1);
        dummyNode.next = head;
        ListNode curNode = dummyNode;
        while (curNode.next != null && curNode.next.next != null) {
            // 如果接连两个结点的 val 相等,至少要把它们都删掉
            if (curNode.next.val == curNode.next.next.val) {
                // 要删除的起点至少应该是当前判断相同的结点的第 2 个
                ListNode delNode = curNode.next.next;
                // 如果后面还有相同的结点,delNode 后移一位,即 delNode 应该是指向相同的结点的最后一个
                while (delNode.next != null && delNode.next.val == delNode.val) {
                    delNode = delNode.next;
                }
                curNode.next = delNode.next;
                delNode.next = null;
            } else {
                curNode = curNode.next;
            }
        }
        return dummyNode.next; 
    }
}

LeetCode 第 83 题: 删除排序链表中的重复元素

传送门:83. 删除排序链表中的重复元素

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例 1:

输入: 1->1->2
输出: 1->2

示例 2:

输入: 1->1->2->3->3
输出: 1->2->3

Java 代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) {
            return head;
        }
        ListNode curNode = head;
        while (curNode != null && curNode.next != null) {
            if (curNode.val == curNode.next.val) {
                ListNode delNode = curNode.next;
                // 继续向前找,看看,还有没有可以删除的结点
                while (delNode.next != null && delNode.next.val == delNode.val) {
                    delNode = delNode.next;
                }
                // 穿针引线
                curNode.next = delNode.next;
                delNode.next = null;
            } else {
                curNode = curNode.next;
            }
        }
        return head;
    }
}

第 19 题:正则表达式匹配

传送门:正则表达式匹配

请实现一个函数用来匹配包括'.''*'的正则表达式。

模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。

在本题中,匹配是指字符串的所有字符匹配整个模式。

例如,字符串"aaa"与模式"a.a""ab*ac*a"匹配,但是与"aa.a""ab*a"均不匹配。

样例:

输入:

s="aa"
p="a*"

输出:true

思路:这题考察的是动态规划。笔记我写在这里了:《剑指 Offer》(第 2 版)第 19 题:正则表达式匹配

Python 代码:

class Solution(object):

    # 状态:dp[i][j] 表示 s 中前 i 个字符与 p 的前 j 个字符组成的表示式是否匹配
    # i 和 j 表示个数
    # 代码中出现 i 均表示 s 中的索引或者个数
    # 代码中出现 j 均表示 p 中的索引或者个数
    # 出现 -1 都表示当前考虑的
    # 出现 -2 都表示当前再前一个

    # 参考资料:http://www.voidcn.com/article/p-zioiffqq-mm.html

    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        n = len(s)
        m = len(p)

        dp = [[False for _ in range(m + 1)] for _ in range(n + 1)]

        # 当 s 和 p 的长度都为 0 的时候,定义成匹配
        dp[0][0] = True

        # 特判
        for j in range(2, m + 1):
            if p[j - 1] == '*' and dp[0][j - 2]:
                dp[0][j] = True

        # 下面分别对字符串 s 和模式串 p 进行匹配
        for i in range(1, n + 1):
            for j in range(1, m + 1):

                if s[i - 1] == p[j - 1] or p[j - 1] == '.':
                    dp[i][j] = dp[i - 1][j - 1]
                elif p[j - 1] == '*':
                    # 这是最麻烦的情况

                    if p[j - 2] != s[i - 1] and p[j - 2] != '.':
                        # 例子:s a
                        #        j-1
                        #      p b    *
                        #        j-2  j-1
                        # 此时只能把 * 当成 0 次,即 * 和它之前的字母不出现,所以一下子要减去 2
                        # p[j - 2] != '.' 这一点别忘了
                        # 不能匹配
                        dp[i][j] = dp[i][j - 2]
                    else:
                        # 接下来是可以匹配
                        # 例子:s a
                        #        j-1
                        #      p .    *
                        #        j-2  j-1
                        # 此时把 * 当成 0 次,
                        # 此时把 * 当成 1 次,
                        # 此时把 * 当成 多 次,直接把 i - 1 ,这是最难的地方
                        dp[i][j] = dp[i][j - 2] or dp[i][j - 1] or dp[i - 1][j]
        return dp[n][m]

方法2:递归的写法。

参考资料:一个网红的解法:http://www.cnblogs.com/grandyang/p/4461713.html。有解法 1 还有解法2。

image-20190125154510545

网红写法:https://blog.csdn.net/hk2291976/article/details/51165010

说明:这个网红还写了 leetbook。

image-20190125154654023

参考资料:https://zhuanlan.zhihu.com/p/37647267。

采用递归的解题方法,递归的终止条件是:

1、如果 s s s p p p 都只有一个字符,相等的充要条件是,它们相等,或者 p p p'.'

其他递归情况:

1、如果 p p p 的第二个字符不是 '*',那么如果 s s s 是空,返回 false,如果 s [ 0 ] s[0] s[0] p [ 0 ] p[0] p[0] 能匹配上,那么递归 s.substr(1), p.substr(1)

坏就坏在,如果 p p p 的第二个字符是 '*'

2、如果 p p p 的第二个字符是 ‘*’,因为我们知道 ‘’ 可以代表 '’ 之前的元素个数是 0 0 0 个或者 1 1 1 个或者多个,所以如果 s s s 的前 k k k 个元素个 p [ 0 ] p[0] p[0] 一样,那么它们有可能都被匹配到,也有可能一个都不会被匹配上。

C++ 写法:

class Solution {
public:
    bool isMatch(string s, string p) {
        if(p.empty())return s.empty();
        if(p.size() == 1) {
             return(s.size() == 1 && (s[0] == p[0] || p[0] == '.'));
        }
       
        if(p[1] != '*')  {
            if(s.empty())return false;
            return (s[0] == p[0] || p[0] == '.')&& isMatch(s.substr(1), p.substr(1));
        }
        // 走到这里 p[1] == '*',下面的 4 行代表比较难理解
        while(!s.empty() && (s[0] == p[0] || p[0] == '.')) {
            if(isMatch(s, p.substr(2))) return true;
            s = s.substr(1);
        }
        return isMatch(s, p.substr(2));
    }
};

要求:请实现一个函数用来匹配包括 .* 的正则表达式。模式中的字符 . 表示任意一个字符,而 * 表示它前面的字符可以出现任意次,包括 0 0 0 次。

LeetCode 第 10 题,传送门:10. 正则表达式匹配,难度是:困难

使用动态规划:

image-20190108003842166

dp 函数这么写。

image-20190108004253180

思路:当字符串只有一个字符时,直接进行判断,否则进入下面两种递归。

两种递归情况:1、当模式中的第二个字符不是 * 时:

(1)如果字符串第一个字符和模式中的第一个字符相匹配或是字符 . 那么字符串和模式都后移一个字符,然后匹配剩余 的;

(2)如果字符串第一个字符和模式中的第一个字符相不匹配,直接返回 false

2、当模式中的第二个字符是 * 时:

如果“字符串”第一个字符跟“模式串”第一个字符不匹配,则模式后移 2 2 2 个字符,继续匹配;

如果“字符串”第一个字符跟“模式串”第一个字符匹配或是字符 . ,可以有 3 3 3 种匹配方式:

(1)模式后移 2 2 2 字符,相当于 x 被忽略;

(2)字符串后移 1 1 1 字符,模式后移 2 2 2 字符;

(3)字符串后移 1 1 1 字符,模式不变,即继续匹配字符下一位,因为可以匹配多位。

image-20190107154329317

image-20190107154342220

第 20 题:表示数值的字符串

传送门: 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。

例如,字符串"+100","5e2","-123","3.1416""-1E-16"都表示数值。

但是"12e","1a3.14","1.2.3","+-5""12e+4.3"都不是。

注意:

  1. 小数可以没有整数部分,例如.123等于0.123;
  2. 小数点后面可以没有数字,例如233.等于233.0;
  3. 小数点前面和后面可以有数字,例如233.666;
  4. 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
  5. 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4;

样例:

输入: “0”

输出: true

Python 代码:

class Solution(object):
    def isNumber(self, s):
        """
        :type s: str
        :rtype: bool
        """
        size = len(s)

        # 1、去掉多余的空格
        i = 0
        while i < size and s[i] == ' ':
            i += 1

        j = size - 1
        while j >= 0 and s[j] == ' ':
            j -= 1
        if i > j:
            return False

        s = s[i:j - i + 1]
        # 2、首字母可以是加号或者减号
        if s[0] == '+' or s[0] == '-':
            s = s[1:]
        
        if len(s) == 0:
            return False
        # 3、只有 1 个点,不行
        if len(s) == 1 and s[0] == '.':
            return False
		# 4、下面对点的个数和 e 的个数展开讨论
        
        # 点的个数
        dot_cnt = 0
        # e 的个数
        e_cnt = 0

        size = len(s)
        i = -1
        while i < size - 1:
            i += 1
            if '0' <= s[i] <= '9':
                continue
            elif s[i] == '.':
                dot_cnt += 1
                # 如果没有 e,并且点的数量大于 1,不符合要求
                if e_cnt or dot_cnt > 1:
                    return False
            elif s[i] == 'e' or s[i] == 'E':
                e_cnt += 1
                if i == 0 or i == size - 1 or e_cnt > 1:
                    return False
                # '.' 后面不能加上
                if i == 1 and s[0] == '.':
                    return False
                if s[i + 1] == '+' or s[i + 1] == '-':
                    if i + 2 == size:
                        return False
                    i += 1
            else:
                return False
        return True


if __name__ == '__main__':
    solution = Solution()
    s = '123.45e+6'
    result = solution.isNumber(s)
    print(result)

“大雪菜”的解法:https://www.acwing.com/solution/acwing/content/737/。

C++ 代码:

image-20190108005159319

C++ 代码:

class Solution {
public:
    bool isNumber(string s) {
        int i = 0;
        while (i < s.size() && s[i] == ' ') i ++ ;
        int j = s.size() - 1;
        while (j >= 0 && s[j] == ' ') j -- ;
        if (i > j) return false;
        s = s.substr(i, j - i + 1);

        if (s[0] == '-' || s[0] == '+') s = s.substr(1);
        if (s.empty() || s[0] == '.' && s.size() == 1) return false;

        int dot = 0, e = 0;
        for (int i = 0; i < s.size(); i ++ )
        {
            if (s[i] >= '0' && s[i] <= '9');
            else if (s[i] == '.')
            {
                dot ++ ;
                if (e || dot > 1) return false;
            }
            else if (s[i] == 'e' || s[i] == 'E')
            {
                e ++ ;
                if (i + 1 == s.size() || !i || e > 1 || i == 1 && s[0] == '.') return false;
                if (s[i + 1] == '+' || s[i + 1] == '-')
                {
                    if (i + 2 == s.size()) return false;
                    i ++ ;
                }
            }
            else return false;
        }
        return true;
    }
};


作者:yxc
链接:https://www.acwing.com/solution/acwing/content/737/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

(本节完)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值