任务:

  1. 学习周期:8.17 — 8.21 号,共计 5 天。
  2. 学习专题:数组。主要题目类型主要有查找、排序。其中数组需要的技巧相对多一些。比如二分法、子数组、双指针、滑动窗口、快排变形等等,其中的快排变形、双指针是最最常考的点,一定要掌握。

  3. 训练题目:标红的重点再多巩固巩固

  • 21: 调整数组顺序,使奇数位于偶数前面

  • 3:数组中重复的数字

  • 4 : 二维数组中的查找

  • 11 :旋转数组的最小数字

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

  • 51: 数组中的逆序对

  1. 链表复习:
  • 52 两个链表的第一个公共节点

  • 35 复杂链表的复制

21. 调整数组顺序,使奇数位于偶数前面(8.17)

image.png

1. 思路

  • 首先要遍历数组,判断是奇数还是偶数
  • 辅助数组法
    • 申请两个空数组,一个放奇数,一个放偶数,最后合并一下
    • 如果使用一个数组,把奇数放在前面,把偶数放到最后
  • 交换位置
    • 本质也是双指针
  • 双指针
    1. 指针 i 从左向右寻找偶数
    2. 指针 j 从右向左寻找奇数
    3. 将 偶数 nums[i]和 奇数 nums[j] 交换

2. 辅助数组法

  1. class Solution:
  2. def exchange(self, nums: List[int]) -> List[int]:
  3. num1=[]
  4. num2=[]
  5. for i in nums:
  6. if i%2!=0:
  7. num1.append(i)
  8. else:
  9. num2.append(i)
  10. return num1+num2
  • 换成一个数组
    1. class Solution:
    2. def exchange(self, nums: List[int]) -> List[int]:
    3. num1=[]
    4. for i in nums:
    5. if i%2!=0:
    6. num1.insert(0,i)
    7. else:
    8. num1.append(i)
    9. return num1

3. 交换位置

  1. class Solution:
  2. def exchange(self, nums: List[int]) -> List[int]:
  3. i = 0
  4. for j in range(len(nums)):
  5. if nums[j]%2 == 1:
  6. nums[i],nums[j] = nums[j],nums[i]
  7. i+=1
  8. return nums

4. 双指针

  1. class Solution:
  2. def exchange(self, nums: List[int]) -> List[int]:
  3. m,n = 0,len(nums)-1
  4. while m<n:
  5. if nums[m]%2 == 0:
  6. nums[m],nums[n] = nums[n],nums[m]
  7. n -= 1
  8. else:
  9. m += 1
  10. return nums

5. 总结

这道题可以使用辅助数组和双指针,第二种交换方法本质也是双指针,只不过一个双指针在俩端,一个在一端,需要注意在list末尾添加元素时不可以使用insert(-1,i),只能使用append(i)

3. 数组中重复的数字(8.18)

image.png

1. 思路

  • set/字典
    • 创建一个set,遍历一遍数组,遍历时判断,是否在set中,
    • 如果在,返回,不在,添加到set中,如果遍历一遍没有,返回-1
    • dict也可以,原理一样
  • 排序
    • 进行排序
    • 然后看是否有重复
  • 原地置换

    • 题目说:在一个长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范围内
    • 可遍历数组并通过交换操作,使元素的 索引 一一对应
    • 如果出现了第二次nums[x]=x,就代表出现了重复

      2. set/字典

      1. class Solution:
      2. def findRepeatNumber(self, nums):
      3. dic = set()
      4. for i in nums:
      5. if i in dic:
      6. return i
      7. dic.add(i)
      8. return -1

      3. 排序

      1. class Solution:
      2. def findRepeatNumber(self, nums):
      3. nums.sort()
      4. pre = nums[0]
      5. for i in nums:
      6. if pre == nums[i]:
      7. return i
      8. pre = nums[i]
      9. return -1

      4. 原地置换

      1. class Solution:
      2. def findRepeatNumber(self, nums):
      3. i = 0
      4. while i < len(nums):
      5. if nums[i] == i:
      6. i += 1
      7. continue
      8. if nums[nums[i]] == nums[i]:
      9. return nums[i]
      10. nums[nums[i]], nums[i] = nums[i], nums[nums[i]]
      11. return -1

      5. 总结

  • 比较容易想到set这个解法,在判断链表是否有环也用到了set这种方法

  • 排序也比较好
  • 觉得面试官应该会比较喜欢最后原地置换的方法,看题解才想到
  • 使用原地置换充分使用了题目给出的条件,其他方法是不需要这个条件的

4. 二维数组中的查找(8.19)

1. 思路

  • 暴力
    • 直接遍历,没有难度,但是负责度高,没有充分利用条件
  • 二分查找
    • 每一行进行二分查找,复杂度为O(nlogn)
  • 线性查找
    • 从二维数组的右上角开始查找。如果当前元素等于目标值,则返回 true。如果当前元素大于目标值,则移到左边一列。如果当前元素小于目标值,则移到下边一行。
    • image.png
    • 降低为O(m+n)的复杂度

      2. 暴力

      1. class Solution:
      2. def findNumberIn2DArray(self, matrix, target):
      3. for i in matrix:
      4. for j in i:
      5. if j == target:
      6. return True
      7. return False

      3. 二分查找

      ```python class Solution: def findNumberIn2DArray(self, matrix, target): n = len(matrix) if n == 0:
      1. return False
      else:
      1. for i in range(n):
      2. left, right = 0, len(matrix[0]) - 1
      3. while left <= right:
      4. mid = (left + right + 1) // 2
      5. if matrix[i][mid] > target:
      6. right = mid - 1
      7. elif matrix[i][mid] == target:
      8. return True
      9. else:
      10. left = mid + 1
      11. return False
  1. <a name="TdlAb"></a>
  2. ### 4. 线性查找
  3. ```python
  4. class Solution:
  5. def findNumberIn2DArray(self, matrix, target):
  6. if len(matrix) == 0 or len(matrix[0]) == 0:
  7. return False
  8. height = len(matrix)
  9. width = len(matrix[0])
  10. row = 0
  11. col = width - 1
  12. while row < height and col >= 0:
  13. if matrix[row][col] > target:
  14. col = col - 1
  15. elif matrix[row][col] < target:
  16. row = row + 1
  17. else:
  18. return True
  19. return False

5. 总结

从暴力,二分查找到线性查找,复杂度从mn,nlogm,n+m逐渐减少,最优的是使用线性查找,可以类比二叉查找树,不容易想到

11. 旋转数组的最小数字

image.png

1. 思路

  • 暴力
    • 直接返回数组的min
    • 将数组排序,返回nums[0]
    • 不是很好
  • 找规律
    • 一个数组在一个位置进行反转
    • 这个列表分为两个升序子列表,那在这两个子列表的边界肯定会出现numbers[i]<numbers[i-1]numbers[i]<numbers[i−1],这样返回numbers[i]numbers[i]
    • 遍历完没有返回值,那就说明这个列表是升序列表,只要返回第一个元素即可
    • image.png
  • 二分查找

    • 设置 ii, jj 指针分别指向 numbers 数组左右两端,m = (i + j) // 2m=(i+j)//2 为每次二分的中点( “//“ 代表向下取整除法,因此恒有 i \leq m < ji≤m<j ),可分为以下三种情况:
    • 当 numbers[m] > numbers[j]时: mm 一定在 左排序数组 中,即旋转点 xx 一定在 [m + 1, j][m+1,j] 闭区间内,因此执行 i = m + 1i=m+1;
    • 当 numbers[m] < numbers[j] 时: mm 一定在 右排序数组 中,即旋转点 xx 一定在[i, m][i,m] 闭区间内,因此执行 j = mj=m;
    • 当 numbers[m] == numbers[j] 时: 无法判断 mm 在哪个排序数组中,即无法判断旋转点 xx 在 [i, m][i,m] 还是 [m + 1, j][m+1,j] 区间中。解决方案: 执行 j = j - 1j=j−1 缩小判断范围

      2. 暴力

      1. class Solution:
      2. def minArray(self, numbers: List[int]) -> int:
      3. return min(numbers)
      1. class Solution:
      2. def minArray(self, numbers: List[int]) -> int:
      3. numbers.sort()
      4. return numbers[0]

      3. 找规律

      1. class Solution:
      2. def minArray(self, numbers: List[int]) -> int:
      3. for i in range(1,len(numbers)):
      4. if numbers[i]<numbers[i-1]:
      5. return numbers[i]
      6. return numbers[0]

      4. 二分查找

      1. class Solution:
      2. def minArray(self, numbers: [int]) -> int:
      3. i, j = 0, len(numbers) - 1
      4. while i < j:
      5. m = (i + j) // 2
      6. if numbers[m] > numbers[j]: i = m + 1
      7. elif numbers[m] < numbers[j]: j = m
      8. else: j -= 1
      9. return numbers[i]

      5. 总结

  • 这道题比较好一点的方法是找规律和二分查找

  • 这道题说明当数组旋转后也可以使用二分查找,但是当左右相同是需要做进一步的处理,不可以随便舍弃一边

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

image.png

1. 思路

  • 字典统计
    • 遍历数组,添加到字典,重复元素加一
    • 判断这个值是否大于数组的一半,返回
  • 数组排序
    • 对数组进行排序
    • 中位数一定是出现的次数超过一半的数字的值
  • 摩尔投票

    • 正负抵消
    • 这个方法有的巧,假设第一个为众数,如果是众数就+1,不是就-1,如果为0就正负相肖,让后一个为众数,继续执行
    • image.png

      2. 字典统计

      1. class Solution(object):
      2. def majorityElement(self, nums):
      3. if not nums:
      4. return None
      5. if len(nums) == 1:
      6. return nums[0]
      7. write = {}
      8. for i in nums:
      9. if i not in write:
      10. write[i] = 1
      11. else:
      12. write[i] += 1
      13. if write[i] > (len(nums) / 2):
      14. return i
      15. return None

      3. 数组排序

      1. class Solution(object):
      2. def majorityElement(self, nums):
      3. nums.sort()
      4. return nums[len(nums) // 2]

      4. 摩尔投票

      1. class Solution:
      2. def majorityElement(self, nums: List[int]) -> int:
      3. votes, count = 0, 0
      4. for num in nums:
      5. if votes == 0: x = num
      6. votes += 1 if num == x else -1
      7. for num in nums:
      8. if num == x: count += 1
      9. return x if count > len(nums) // 2 else 0

      5. 总结

  • 在字典统计是要注意数组为一个元素的情况

  • 摩尔投票有的巧,也是这个题的最佳解法,时间复杂度为O(n),空间复杂度为O(1)

51. 数组中的逆序对

1. 思路

  • 暴力
    • 直接暴力,但是超时了
  • 分治

    • 逆序对是归并排序的副产物
    • 归并排序实际上会把数组分成两个有序部分,我们不妨称其为左和右,归并排序的过程中会将左右两部分合并成一个有序的部分,对于每一个左右部分,我们分别计算其逆序数,然后全部加起来就是我们要求的逆序数。
    • image.png
    • image.png

      2. 暴力

      1. class Solution:
      2. def reversePairs(self, nums: List[int]) -> int:
      3. size = len(nums)
      4. if size < 2:
      5. return 0
      6. res = 0
      7. for i in range(0, size - 1):
      8. for j in range(i + 1, size):
      9. if nums[i] > nums[j]:
      10. res += 1
      11. return res

      3. 分治

      ```python class Solution: def reversePairs(self, nums: List[int]) -> int: self.cnt = 0 def merge(nums, start, mid, end, temp):

      1. i, j = start, mid + 1
      2. while i <= mid and j <= end:
      3. if nums[i] <= nums[j]:
      4. temp.append(nums[i])
      5. i += 1
      6. else:
      7. self.cnt += mid - i + 1
      8. temp.append(nums[j])
      9. j += 1
      10. while i <= mid:
      11. temp.append(nums[i])
      12. i += 1
      13. while j <= end:
      14. temp.append(nums[j])
      15. j += 1
      16. for i in range(len(temp)):
      17. nums[start + i] = temp[i]
      18. temp.clear()
  1. def mergeSort(nums, start, end, temp):
  2. if start >= end: return
  3. mid = (start + end) >> 1
  4. mergeSort(nums, start, mid, temp)
  5. mergeSort(nums, mid + 1, end, temp)
  6. merge(nums, start, mid, end, temp)
  7. mergeSort(nums, 0, len(nums) - 1, [])
  8. return self.cnt

```

4. 总结

  • 还不是很理解分治和归并排序
  • 这道题一上来的感觉很蒙,只想到暴力,看题解分治和树状数组,还是有些不是很明白