最初と2番目の検索
はじめに:バイナリ検索はバイナリ検索とも呼ばれ、データ構造を最初に並べ替える必要があり、検索がデータスケールの対数時間の複雑さの範囲内で完了できる場合に、より効率的な検索方法です。ただし、バイナリ検索では、線形テーブルがランダムアクセス(配列など)の特性を持っている必要があります。また、問題の規模を小さくするために、線形テーブルが中央の要素の特性に従って両側の要素の性質を推測できる必要があります。
コードテンプレート:
#常规
def bsearch(l, r, target):
while l <= r:
mid = (l + r) // 2
if nums[mid] > target:
right = mid - 1
elif nums[mid] < target:
left = mid + 1
return mid
'''
特殊情况:由于部分题左侧区间保留中间值(因为我们计算中间值向下取整,
这是为什么左侧区间保留中间值,右侧不保留的原因),保留中间值就需要
考虑啊是否会出现死循环的问题。
'''
def bsearch(l, r, target):
while l <= r:
mid = (l + r) // 2
if nums[mid] > target:
right = mid #保留中间值
elif nums[mid] < target:
left = mid + 1
else: #防止出现死循环
break
return mid
例1(leetcode33)
昇順でソートされた配列が事前に不明な点で回転するとします。
(たとえば、配列[0,1,2,4,5,6,7]は[4,5,6,7,0,1,2]になる場合があります)。
指定されたターゲット値を検索し、ターゲット値が配列に存在する場合はそのインデックスを返し、それ以外の場合は-1を返します。
配列に重複する要素がないと想定できます。
アルゴリズムの時間の複雑さはO(log n)レベルでなければなりません。
样例:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
解法一:一边循环直接出结果
class Solution:
def search(self, nums: List[int], target: int) -> int:
#由于旋转导致一边符合升序,另一边有可能不符合升序
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target: return mid
#等号满足区间长度为2,左侧为正确答案
if nums[left] <= nums[mid]:
if nums[left] <= target <= nums[mid]:
right = mid - 1
else:
left = mid + 1
else:
if nums[mid] <= target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
解法二:更为常规的解法
思路:两次运用二分查找,第一次寻找旋转点,第二次寻找目标值。
其中第二次直接套用模板,第一次对于边界条件考虑比较麻烦。
```python
class Solution:
def search(self, nums, target):
#通过二分算法获取转折点
def bfind_rotate(left, right):
while left <= right:
mid = (left + right) // 2
if mid < len(nums) - 1 and nums[mid] > nums[mid + 1]:
return mid
#由于二分往往向下取整,所以当选左侧的时候可以加上右边界
elif nums[mid] < nums[left]:
right = mid
elif nums[right] < nums[mid]:
left = mid + 1
#如果左侧区间右边界保留中间值,就需要考虑是否出现死循环
else:
break
return -1
#通过二分算法获取目标值索引
def bfind(left, right, target):
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid - 1
#else: 由于左侧区间没有保留右边界,所以不需要考虑死循环
# break
return -1
#特殊情况处理
if not nums: return -1
if len(nums) == 1 and nums[0] == target: return 0
elif len(nums) == 1: return -1
left, right = 0, len(nums) - 1
if nums[right] > nums[left]: return bfind(left, right, target)
else: rotate = bfind_rotate(left, right)
#判断目标值在旋转前还是旋转后
if target >= nums[0]: return bfind(0, rotate, target)
return bfind(rotate + 1, right, target)
例2(74. 2次元マトリックスの検索)
は、mxnマトリックスにターゲット値があるかどうかを判別するための効率的なアルゴリズムを記述します。行列には次の特性があります。
各行の整数は左から右に昇順で配置されます。
各行の最初の整数は、前の行の最後の整数より大きい。
示例 1:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true
思路:本题先对于第一列使用二分查找寻找到目标值所在的行row,条件是target值大于当前行起始值,小于下一行起始值
然后对于row行进行第二次二分查找,寻找目标target.
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if not matrix or not matrix[0]: return False #特殊情况处理
#对行应用二分查找,由于二分查找过程中会忽略最后一行,所以直接初始化row为最后一行
row, bottom, top= len(matrix) - 1, 0, len(matrix) - 1
while bottom <= top:
mid = (bottom + top) // 2
if matrix[mid][0] == target: return True
elif mid < len(matrix) - 1 and matrix[mid][0] < target and matrix[mid + 1][0] > target:
row = mid
break
elif matrix[mid][0] < target: bottom = mid + 1
elif matrix[mid][0] > target: top = mid - 1
#判断当row是最后一行时,是否符合题意
if matrix[row][-1] < target : return False
#对第row行进行二分查找
left, right = 0, len(matrix[0]) - 1
while left <= right:
mid = (left + right) // 2
if matrix[row][mid] == target:
return True
if matrix[row][mid] < target:
left = mid + 1
elif matrix[row][mid] > target:
right = mid - 1
return False
2、分割統治アルゴリズム
はじめに:コンピュータサイエンスでは、分割統治法は多分岐再帰に基づく非常に重要なアルゴリズムパラダイムです。文字通りの解釈は「分割統治」であり、これは複雑な問題を同じまたは類似の2つ以上のサブ問題に分割することです。最後のサブ問題が直接解決できるまで、元の問題の解決策はサブ問題の組み合わせです。
この手法は、ソートアルゴリズム(クイックソート、マージソート)、フーリエ変換(高速フーリエ変換)など、多くの効率的なアルゴリズムの基礎です。
クイックソートの例
class Solution {
public int[] smallestK(int[] arr, int k) {
helper1(0, arr.length - 1, arr);
return Arrays.copyOf(arr, k);
}
public void quickSort(int start, int end, int[] nums){
//当排序数组长度小于等于1时直接中止排序
if(start >= end) return;
//在start处挖个坑, 保存起始位置
int temp = nums[start], t0 = start, t1 = end;
while(start < end){
//注:先向前遍历,当队尾的元素大于等于基准数据时,向前挪动high指针
//start > end 防止start越界end
while(start < end && nums[end] >= temp) end--;
//将队尾元素小于基准数据的值放到坑里,将坑变为end所在处
swap(start, end, nums);
//向后遍历,当队首的元素小于等于基准数据时,向后挪动start指针
//start < end 防止start越界end
while(start < end && nums[start] <= temp) start++;
//将队首元素小于基准数据的值放到坑里,将坑变为start所在处
swap(start, end, nums);
}
//对temp左右两侧继续排序
quickSort(t0, start - 1, nums);
quickSort(start + 1, t1, nums);
}
public void swap(int start, int end, int[] nums){
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
}
}
実施例II 美しい配列
:ように配列Aの配列は1の整数である場合、いくつかの固定されたNのために、2、...、組成物のN、
各I <jについて、Kを満たすは、I <K <J例えばA [Kその存在しません] * 2 = A [i] + A [j]。
次に、配列Aは美しい配列です。
Nを指定すると、きれいな配列Aが返されます(存在することが保証されます)。
示例 1:
输入:4
输出:[2,1,4,3]
思路:漂亮数组有以下性质:
(1)A是一个漂亮数组,如果对A中所有元素乘以一个常数,那么A还是一个漂亮数组。
(2)A是一个漂亮数组,如果删除一些A中所有元素,那么A还是一个漂亮数组。
(3)A是一个奇数构成的漂亮数组,B是一个偶数构成的漂亮数组,那么A+B也是一个漂亮数组
#我的做法,非分治
class Solution:
def beautifulArray(self, N: int) -> List[int]:
def DAC(memo):
if len(memo) >= N: return memo
left = [i * 2 - 1 for i in memo]
right = [i * 2 for i in memo]
memo = left + right
return DAC(memo)
memo = DAC([1])
length = len(memo)
for i in range(N, length):
memo.remove(i + 1)
return memo
#官方题解 上一种解法的性质依然继续沿用
class Solution:
def beautifulArray(self, N):
memo = {1: [1]}
def f(N):
if N not in memo:
#性质一
odds = f((N+1)//2) #奇数数组是由(N+1)/2个数字组成的漂亮数组放射而成
evens = f(N//2) #偶数数组是由 N/2个数字组成的漂亮数组放射而成
#性质三
memo[N] = [2*x-1 for x in odds] + [2*x for x in evens]
return memo[N]
return f(N)
3番目の質問のマージソート
整数NUMSの配列、並べ替えを考えると配列を昇順に。
class Solution {
int[] arr;
public int[] sortArray(int[] nums) {
arr = nums;
helper(0, nums.length - 1);
return arr;
}
//分治算法
public void helper(int start, int end){
if(start >= end) return;
int mid = (start + end) / 2;
//将整体分为左右两个子区间进行排序
helper(start, mid);
helper(mid + 1, end);
//合并子区间后重新排序
insertSort(start, end);
}
public void insertSort(int start, int end){
if(start >= end) return; //只有一个数字不需要排序
int mid = (start + end) / 2; int i, j; //由归并排序可知待排数组左右两区间均完成排序
//插入排序
for(i = mid; i<=end; i++){
int index = i - 1, temp = arr[i];
//注意index >= start而不是0,用于规范排序区间
//注意arr[index] >= temp 而不是 arr[index] >= arr[i] 因为arr[i]会发生变化
while(index >= start && arr[index] >= temp) {arr[index + 1] = arr[index]; index -= 1;}
arr[index + 1] = temp;
}
}
}