【剑指offer学习记录】前57题思路记录

3-数组中重复的数字

通过将值为i的元素调整到第i个位置上,如果对应位置已经有值为i的数字,则i值重复

3-题目二-不修改数组找出重复的数字

使用二分法变体。通过统计数组中i-m范围数字个数是否为m-i+1判断该范围是否有重复数字。

时间复杂度O(nlogn),空间复杂度O(1)

实现注意事项

  • 二分法的使用,因为是i-m的范围,是前后闭合的

4-二维数组中的数字查找

从右上角或者左下角开始找。

右上角特征:所在列的其他元素大于该数,所在行的元素小于该数。

左下角特征:所在列的其他元素小于该数,所在行的元素大于该数。

通过右上角或左下角的对比可以每次比较删除一行或者一列。

实现注意事项

二维数组的元素表示,分清比较的是右上角还是左下角

5-替换空格

计算字符串长度+双指针+从后向前替换

  • 注意python实现可以将字符串转换为数组

  • python可以使用s += [None]*count来为s列表扩容

6- 从尾到头打印链表

  • 栈的先进后出逻辑思想

    因为遍历的顺序和打印顺序相反所以可以想到利用栈实现。

    栈的逻辑和递归的逻辑相同,因此实现也可以用递归实现

  • python中的栈可以用列表实现

7- 重建二叉树

  • 前序遍历的第一个元素为根节点

  • 以前序遍历根节点对中序遍历序列进行拆分为左右子树

  • 使用递归对左右子树再分别拆分+重建节点

  • 代码实现需注意前序序列中左右子树的范围

8-二叉树的下一个节点

分情况讨论:

  • 节点有右子树,下一节点为右子树的最左节点

  • 节点没有右子树,且为父节点的左子节点,下一节点为父节点

  • 节点没有右子树,且为父节点的右子节点,下一节点为沿着指向父节点的指针一直向上遍历,找到一个未父节点的左子节点的节点。

一点想法:

因为是中序遍历,顺序为左-中-右,所以用是否有右子树进行分类?

9-两个栈实现队列

使用stackA作为入队列容器,stackB作为出队列容器。

入队列时直接将元素加入stackA;

出队列时如果stackB为空,则将stackA元素压入stackB,此时stackB栈顶为出队列元素

出队列时如果stackB不为空,stackB本身存储顺序为入栈的反序即入队列的正序,直接取出stackB的栈顶元素即可

  • python中list的append和pop函数使得list本身逻辑是一个stack

9- 题目拓展-用两个队列实现一个栈

  • push

    直接将元素加入queueA

  • pop

    如果queueA只有一个元素,则直接出队列;否则将queueA中元素放入queueB中,直到queueA中只有一个元素,取出queueA元素。然后交换queueA和queueB

  • 注意事项

    • 将queueA元素放入queueB中,queueB的元素顺序与queueA是相同的

    • 交换queueA和queueB后queueA中的元素顺序和取出元素前顺序是相同的,只是少了第一个元素

10-斐波那契数列

  • 斐波那契数列是最基本的递归思想的问题,经典解法为递归实现。

  • 但是由此题也要学习到递归方法会产生很多的重复计算项,而根据递归树,进行自下而上的计算,则可以排除重复计算项。

10-扩展题目1-跳台阶

  • 递归思想:同样对某个状态分解,可以发现该状态是由两个子状态构成;而其子状态也是由两个子状态构成。

  • 注意使用循环求解时,循环的边界和斐波那契数列求和不一样

10-扩展题目2-跳台阶进阶

  • 此题的特点是状态很多,无法一下像上面的递归对问题分解找到循环的状态

  • 因此可以尝试列出两个邻接状态的公式,并对公式变换得到两个邻接状态的关系

10-扩展题目3-矩形覆盖

11-旋转数组的最小数字

  • 此题也是一道经典的二分法查找的变体题目

  • 看到题目中的排序可以联想到大概率使用二分法进行查找

  • 利用旋转得到两个排序的子数组的规律,查找旋转点,即为最小数字

  • 注意二分法的结束条件、返回条件、变换边界的条件

12-矩阵中的路径

  • 回溯法

  • 注意点:回溯的状态回退,需要回退状态记录的辅助数组与目标值的已访问状态。

13-机器人的运动范围

  • 回溯法

    回溯法的代码实现逻辑体现为递归方法

  • 注意不同的回溯法检查状态是否可以用的函数都是不一样的,但是回溯的基本代码逻辑框架是一致的

14-剪绳子

  • 方法1:动态规划

    • 首先找到要求解的问题

      绳子任意切分的乘积最大值。绳子任意切分的最大值,和绳子切分成两段,分别求乘积最大值再相乘的结果是相同的,因此f(n)可以分解为求max(f(i)xf(n-i))的子问题。

    • 问题的初始状态

      问题的初始状态非常重要,当n=1时,乘积为0;当n=2时,乘积为1;当n=3时,乘积为2;当n=4时,才可以切分为2x2,与1x3,此时才可以归为最优化问题。

      因此在实现时,需要设置一个初始值的数组。

  • 方法2:贪心算法

    • 概念

      贪心算法在问题求解时,总是做出当前开了最好的选择。也就是说,不从整体最优上考虑,因此得到的结果是某种意义的局部最优解

    • 一般步骤

      建模描述问题

      分解问题为若干子问题

      对子问题求解,得到子问题最优解

      把子问题的局部最优解合成原理解问题的一个解

15-二进制中1的个数

16-数值的整数次方

  • 特殊值的检查

    注意要检查底数为0的情况,以及对指数为负的情况进行特殊处理,以及底数为0指数为负的情况是错误情况。

  • 位移实现高效计算

    基于以下的公式,可以通过位移算法实现对指数除二,然后通过递归,进行n次的循环,从而计算出结果

    为偶数为奇数
  • python实现的注意事项

    python的浮点数相等不能直接使用,要与0.0相减判断差值

17-打印从1到最大的n位数

  • 解法1

    使用字符串表示n位数。

    通过判断最高位是否产生进位来判断是否到达最大数

  • 解法2

    将该问题想成一个n位数的全排列问题。

    每个数位有0-9个选项可以选择,从最高位到最低位依次进行选择。

    这一过程可以抽象为一种递归的调用状态。

    即最高位选择0-9一种状态=》次高位选择0-9一种状态=》.....最低位选择0-9一种状态,当最低位选择状态时,调用输出数字函数,并返回到最低位再进行状态选择,如果最低位状态都选择完,则返回次低位再选择次低位的下一状态,然后再选择最低位的状态,然后再调用输出数字函数....

    由高到低的选择顺序是因为要从0到999999输出。

  • 效率问题

    递归方法代码简单,但是效率实在堪忧。

18-删除链表中的节点

  • 基于下一节点的O(1)删除方法

    因为删除此节点需要知道前驱节点,但是删除下一节点就之一知道此节点即可。

    因此可以通过将下一节点的值赋给此节点,然后删除下一节点的方法达到目的

18-题目二删除链表中重复的节点

  • 当遇到相同节点时,把该节点的前一个节点与后面值和当前值不同的下一个节点相连。

  • 这道题实现比较麻烦,有一些特殊的情况需要考虑,后面再看看

19-正则表达式

  • 分情况讨论

    存在"**"多次匹配的情况,pattern不变,可以使用递归实现

  • python实现的问题

    在python中迭代是使用了切片。是否需要判断切片是否可切?

20-表示数值的字符串

  • 根据指数e进行分割判断

    • 按照e分割成为两部分

      • 第二部分检查是否有小数点,有则不是数字

      • 然后对第一部分和第二部分同一用一个函数检查是否是正常的数字,其中小数点个数不能大于1个,且小数点前后要是数字

    • 重点是小数点判断时不能是前一部分最后一个且小数点前是数字且小数点不能是第一个

21-调整数组顺序使奇数位于偶数前面

  • 头尾指针

    前指针遇到偶数停止,后指针遇到奇数停止,然后交换再继续遍历

22-链表中倒数第k个节点

  • 先后指针

    第一个指针先走k-1步,第二个指针再开始和第一个指针一起走,当第一个指针到链表尾部后第二个指针到到倒数第k个节点。

    倒数的第k个节点,该节点是正数的第n-k+1个,其右边有k-1个,所以该数是从右往左数的第k个。

23-链表中环的入口节点

  • 快慢指针判断是否有环

    快慢指针值一个走的快,一个走的慢。快的走2步,慢的走1步,如果会相遇则说明存在环

  • 判断环中节点个数

    当有环相遇时,再继续走,如果又到相遇节点,则到环中节点个数

  • 先后指针找入口点

    先后指针指一个指针先走,一个指针后走,两个指针步伐一致。

    假设环有n个节点,环入口点之前有m个节点。则可以想象为该链表为一个m+n的无环链表,环的入口点为倒数第n个节点。所以可以先让前一个指针从头结点先走n步,此时前指针位于倒数第m个节点,即再走m步到达入口点。此时后指针也从第一个节点开始走,因为环入口点前有m个节点,因此第一个节点走m步会和后指针相遇。

  • 注意事项

    注意使用双指针时要注意:

    • 先后距离差

    • 步伐

    • 目的位置和走的步数的关系

24-反转链表

  • 三指针

    分别指向 前、当前、后三个指针

25-合并两个排序的链表

  • 代码简洁

    使用递归,将“取两个链表中小值的节点加入要返回链表,并为返回链表下一节点找两个链表后续节点的小值节点”这一过程看为子问题,递归获取结果。

26-树的子结构

  • 第一步:检测A中树是否有和B中树相同的节点

  • 第二步:检测B中树其余节点是否和A的以B为根节点的子树的节点相同

  • 本质是树的遍历过程,可以使用递归遍历

  • 注意如何判断两个树相等,如果是浮点型不能直接判断为相等

27-二叉树的镜像

28-对称的二叉树

  • 对称遍历算法

29-顺时针打印矩阵

  • 将问题具体分析为不同的子问题,并各自抽象为一类问题,并查找问题的状态

  • 实现时,注意不同遍历的顺序和坐标

  • 类似题目是leetcode54题

30-包含min函数的栈

  • 双栈

    一个栈用来正常的push和pop,另一个栈用来存储当前的最小值。

    push时,如果min栈为空则直接push,如果min不为空则和mintop比较,如果比mintop小则放在min栈中,如果大于min则对min再push一个mintop

    pop时,直接pop,同时pop一个min栈的top元素,保持两个栈的元素个数是相同的。

    查询min时,直接popmin栈的元素

31-栈的压入、弹出序列

  • 辅助栈

    模拟入栈出栈顺序

  • 关键点

    栈顶元素是否为弹出序列的下一个元素,如果不是则继续向栈中压入压栈序列元素

32-从上到下打印二叉树

  • 层序遍历

  • 使用队列实现

32-题目二分行从上到下打印二叉树

  • 层序遍历

  • 使用队列实现

  • 因为要分行,所以要知道要打印的该行有多少节点,还剩多少节点要打印

32-题目三之字形打印二叉树

  • 使用双栈的思想,分奇偶层进行翻转顺序的打印

  • 注意节点的左右节点的插入顺序

    因为后面要进行顺序翻转,所以插入的时候是右1左1右2左2,才能翻转以后变为左2右2左1右1。如果还是按照左1右1左2右2的顺序插入,得到的翻转顺序是右2左2右1左1

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

  • 二叉搜索树的性质

    根结点大于左子树,小于右子树

  • 后续遍历的性质

    左中右顺序

  • 代码实现

    递归检查左右子树的序列与根结点大小的关系

  • python中 sequence[index:-1] 如果index=0,则序列为空

34-二叉树中和为某一值的路径

  • 递归方法

  • 栈存储路径节点,路径和不成立则回退

35-复杂链表的复制

  • 先克隆后拆分

    只用遍历三遍,节省查找random关系的时间

  • 方法2:递归方法

36-二叉搜索树与双向链表

  • 搜索二叉树的中序遍历是一个排序的序列

37-序列化二叉树

  • 特殊值表示空节点+前序遍历

38-字符串的排列

39-数组中出现次数超过一半的数字

  • 如果一次数字出现的次数比其他所有数字出现的次数都要多,那么进行一次遍历,记录数字出现的次数,如果相同则+1,如果不同则次数-1,如果次数为0,则替换为该不同数字继续进行比较。因为超过一半的数字的数量大于其他数字个数总和。所以最后被记录的数字为要找的数字。

40-最小的k个数

  • 海量数据处理问题

    这道题是典型的海量数据处理问题。

    适用于内存被限制不能一次载入所有内存的情况。

  • 最大堆实现查找最小的k个数

  • 堆的处理

     MAX_HEAPIFY(A, i)
      L = left(i)
        R = right(i)
        if L<=A.size and A[L] > A[i]
        largest = L
        else
        largest = i
        if R<=A.size and A[R] > A[largest]
        largest = R
        if largest != i
        exchange A[i] and A[largest]
        MAX-HEAPIFY(A, largest)
  • 堆的建立

     BUILD_MAX_HEAP(A)
      for i = A.length/2 to 1
        MAX-HEAPIFY(A, i)

41-数据流中的中位数

  • 最大堆存储中位数左边,最小堆存储中位数右边

  • 使用heapq实现

注意python中的heapq默认实现的是最小堆,如果数字都是正数的话,可以通过将输入数字取负的方式生成最大堆

42-连续子数组的最大和

  • 数学规律法

  • 动态规划法

43-1~n整数中1出现的次数

  • 直接累加1-n的数字,挨个计算出现1的次数

    时间复杂度为O(nlogn)

  • 基于数学规律的递归

    将数字分为最高位和其余位两部分进行1的统计,通过递归一次得到n范围内1出现的次数。

    因为只用对数字n进行递归,其有logn位数字,因此时间复杂度为O(logn)

44-数字序列中某一位的数字

  • 统计出不同位数的数字的位数总和确定该数字范围

  • 求得该数位的余数与商得到该数字的数位

  • 注意python实现整除是//

45-把数组排成最小的数

  • 解法2——使用sorted函数,自定义比较函数方法

    规定比较函数为取x+y和y+x两个字符表示的整数小的那一个

  • 解法1——求全排列中的最小数

    使用itertools求可迭代对象的全排列

46-把数字翻译成字符串

  • 自顶向下分析迭代情况

  • 自下而上使用动态规划去除子问题

47-礼物的最大价值

  • 动态规划查找

  • 数据存储优化

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

  • 动态规划

    • 查找两个相邻状态的关系

    • 进行分类讨论

49-丑数

  • 以空间换实现

  • 对比前的数乘以2、3、5比目前最大数大的最小值即为下一个数

50-第一个只出现一次的字符

  • 哈希表两次查找

50-题目二 数据流中第一个只出现过一次的字符

  • 哈希表记录出现过几次

51-数组中的逆序对

52-两个链表的第一个公共节点

  • 结构特点

    在公共节点以后到尾部,都是公共的

  • 快慢指针

53-在排序数组中查找数字

  • 注意到是排序数组

  • 考虑使用二分法的变体

  • 先找到第一次出现的位置

  • 再找到最后一次出现的位置

54-二叉搜索树的第K大节点

  • 二叉搜索树的中序遍历是二叉搜索树的节点的递减序列

  • 中序遍历

55-二叉树的深度

  • 递归计算左右子树的深度,取最大值

55-题目二 判断树是否为二叉树

  • 递归计算左右子树深度并计算左右子树的深度差是否小于1

56-数组中数字出现的次数

  • 数位异或法

  • python counter容器法

57-和为S的数字

  • 头尾指针

posted @ 2020-07-15 20:32  szxyx  阅读(79)  评论(0编辑  收藏  举报