本博客题目来源b站左程云的视频。使用python手敲。并记录了一些思路,后续会持续更新
ch1 时间复杂度和简单排序算法
1.1 选择排序
题目描述:
使用选择排序
的方法对数组进行排序
思路:
每轮从i到len(arr)中选择一个最小值,放在i位置,直到i为len(arr)-1。所以需要两层循环:
- i从0到len(arr),表示每次确定i位置的数,这样从0-i都是排好的;
- 内层循环需要从i到len(arr)选择出这里面最小的数,用来和i位置原来的数做交换。
代码:
from typing import List # 函数注解
class SelectionSort:
def select_sort(self, arr: List[int]):
"""
选择排序算法思路:
每轮从i到len(arr)中选择一个最小值,放在i位置,然后i++,直到i为len(arr)。
所以需要两层循环:1.i从0到len(arr),表示每次确定i位置的数,这样从0-i都是排好的;
2. 内层循环需要从i到len(arr)选择出这里面最小的数,用来和i位置原来的数做交换。
:param arr:
:return:
"""
if arr is None or len(arr) < 2:
return
for i in range(len(arr)): # 每次确定i位置的数
min_index = i
for j in range(i+1, len(arr)): # 从i+1到len(arr)中找出最小数的index
if arr[j] < arr[min_index]:
min_index = j
arr[i], arr[min_index] = arr[min_index], arr[i] # 进行交换
s = SelectionSort() # 类的实例化
unsorted_arr = [3, 4, 1, 2, 5]
s.select_sort(unsorted_arr) # 调用方法
print(unsorted_arr) #[1, 2, 3, 4, 5]
1.2 奇数次的数I(其他数偶数次)
题目描述:
给你一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
你必须设计并实现线性时间复杂度的算法且仅使用常量额外空间来解决此问题。
思路:
假设两个元素为a和b,则把数组所有元素都异或起来的结果即为a^b。
接下来需要把a从ab中拿出来。由于ab一定不为0,所以最右某位一定为1,这代表,在这一位上,a和b一个是0,一个是1。所以我们只需要将数组中,所有该位为0的数异或到一起,就一定为a(因为其他数都是偶数次,都抵消了)。b = a ^ (a^b)
代码:
class OddTimes:
def print_odd_times_num2(self, nums: List[int]) -> List[int]:
"""
:param nums: 源数组
:return: List[int]
"""
eor = 0
for num in nums:
eor ^= num # eor = a ^ b
right_one = eor & (-eor) # 拿到...10...0, -eor即为补码,等于取反加一 ~eor+1
a = 0
for num in nums:
if num & right_one == 0:
a ^= num # 所有该位为0的
b = eor ^ a
return [a, b]
o = OddTimes()
arr = [1, 1, 3, 2, 3, 4, 5, 5]
print(o.print_odd_times_num2(arr)) # [4, 2]
1.3 奇数次的数II(其他数奇数次)
题目描述:
给你一个整数数组 nums
,除某个元素仅出现 一次外,其余每个元素都恰出现 三次 。
请你找出并返回那个只出现了一次的元素。
思路:
不考虑某个元素,其他元素的每一位的结果加起来mod3应该是0,如果不为0则来自于某个元素的。把32位中,所有不为0的位置的数累加起来,最后就是结果。注意,由于python没有无符号右移,所以需要考虑最高位为1的情况(负数)
代码:
class Solution:
def singleNumber(self, nums: List[int]) -> int:
res = 0 # 结果
for i in range(32):
cnt = 0 # 记录每一位的数量
for num in nums: # 每一位统计所有数字
cnt += ((num >> i) & 1) # 将num右移i位,然后与1
if cnt % 3 != 0:# 如果该位模3不为0,则说明是单独那个数的位置不为1。
if i == 31: #python最高位要特殊处理
# 这代表单独的数是负数,例如-2:111...10,需要减掉(1<<31)
res -= (1 << i)
else:
# 结果加上该位为1,也可以写作res |= ((cnt % 3) << i)
res += (1 << i)
return res
1.4 插入排序
题目描述:
使用插入排序
的方法对数组进行排序
思路:
插入排序就像打牌,把牌插入到现有已经有序的序列中。现在0
到j
位置已经有序了,对于nums[j+1]
,需要和nums[j]
进行比较,如果比nums[j]
大,则需要一直往左看,直到满足有序;如果不比nums[j]
小,则不需要排序,直接处理下一个数 。直到遍历完数组。
代码:
class InsertSort:
def insert_sort(self, nums: List[int]):
if nums is None or len(nums) < 2:
return
for i in range(1, len(nums)):
for j in range(i-1, -1, -1): # 从i-1到0遍历
if nums[j+1] < nums[j]: # 前一个数,就是i位置,但是需要该数需要一直往后,所以写成j+1方便j--
nums[j+1], nums[j] = nums[j], nums[j+1]
print(nums)
i = InsertSort()
i.insert_sort([2, 3, 1, 4, 5])
1.4 二分查找(非递归/递归)
题目描述:
使用二分查找的方法,在有序数组nums
中找到target
思路:
有序数组最先想到使用二分查找,二分查找的思想就是将中点的数和target
进行比较,减少一半的数据,一直往下,直到找到或者没找到。
代码:
# 递归
class BinarySearchUnRecu:
def binary_search(self, nums: List[int], target: int) -> Union[int, None]:
if nums is None:
return None
left = 0
right = len(nums) - 1
while left <= right:
mid = (right - left) // 2 + left
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
# 非递归
class BinarySearchRecu:
def binary_search(self, nums:List[int], left: int, right: int, target: int) -> int:
if nums is None:
return -1
if left <= right:
mid = (right - left) // 2 + left
if nums[mid] == target:
return mid
elif nums[mid] < target:
return self.binary_search(nums, mid+1, right, target)
else:
return self.binary_search(nums, left, mid-1, target)
return -1
1.5 找到不小于target的最左的数
题目描述:
在有序数组nums
中找到不小于target
的最左的数的索引。例如[1, 2, 2, 2, 3, 4, 5]
,不小于target
的最左的数索引为1
思路:
有序数组最先想到使用二分查找,但是这里找到了target
这个数之后,还需要继续找,直到找到最左的数。如果当前数比target
小,则说明该边界肯定在右边,则left = mid+1
。如果大于等于target
则当前的数可能就是该边界,或者是在左边,此时先用idx
记录该边界,然后right = mid-1
直到left>right
代码:
class FindLeftNum:
def find_left_num(self, nums: List[int], target: int) -> int:
idx = -1
if nums is None:
return idx
left, right = 0, len(nums)-1
while left <= right:
mid = (right - left) // 2 + left
if nums[mid] < target: # 二分查找,<>=三种情况讨论,合并即可
left = mid + 1
else:
idx = mid
right = mid - 1
return idx
1.6 局部最小值问题
题目描述:
在无序数组nums
中。相邻的数一定不相等,找到任一一个局部最小值。局部最小值概念:中间的数比它左右两边的数都要小。边界的数比边界的数小。
要求:时间复杂度好于O(N)
思路:
有序数组最先想到使用二分查找,但是这里找到了target
这个数之后,还需要继续找,直到找到最左的数。如果当前数比target
小,则说明该边界肯定在右边,则left = mid+1
。如果大于等于target
则当前的数可能就是该边界,或者是在左边,此时先用idx
记录该边界,然后right = mid-1
直到left>right
代码:
class LocalMinima:
def print_local_minima_idx(self, nums: List[int]) -> int:
# 左右边界条件
if nums[0] < nums[1]:
return 0
n = len(nums)
if nums[n-1] < nums[n-2]:
return n-1
idx = -1
# 中间情况,由于0 -> 1 一定是升序,n-1 -> n-2 一定是降序,所以一定存在局部最小值。
# 使用二分法,还是找两边升降序不一样的序列,一定存在局部最小值。
left, right = 0, n-1
# 画一下左右的趋势即可理解
while left <= right:
mid = ((right - left) >> 2) + left # 一定要加括号!!
idx = mid
if nums[mid] > nums[mid-1]:
right = mid -1
elif nums[mid] < nums[mid+1]:
return idx
else:
left = mid + 1
return idx
l = LocalMinima()
print(l.print_local_minima_idx([6, 5, 4, 7, 5, 6, 7])) # 2