알고리즘 테마: Merge Sort
def merge_sort(lst):
"""Sortsthe input list using the merge sort algorithm.
# >>> lst = [4, 5, 1, 6, 3]
# >>> merge_sort(lst)
[1, 3, 4, 5, 6]
"""
if len(lst) <= 1:
return lst
mid = len(lst) // 2
left = merge_sort(lst[:mid])
right = merge_sort(lst[mid:])
return merge(left, right)
def merge(left, right):
"""Takestwo sorted lists and returns a single sorted list by comparing the
elements one at a time.
# >>> left = [1, 5, 6]
# >>> right = [2, 3, 4]
# >>> merge(left, right)
[1, 2, 3, 4, 5, 6]
"""
if not left:
return right
if not right:
return left
if left[0] < right[0]:
return [left[0]] + merge(left[1:], right)
return [right[0]] + merge(left, right[1:])
분명히 병합 정렬은 전형적인 분할 치료(Divide and Conquer, D&C) 알고리즘이다. 사상은 먼저 두 개의 데이터를 각각 정렬한 다음에 병합하는 것이다.이렇게 하면 T(n) = 2T(n/2) + O(n), Master Theorem에서 얻을 수 있는 시간 복잡도는 O(nlogn)이다.구체적인 실현을 다시 보다.정렬 주체 함수는 귀속을 사용하는데 귀합 알고리즘은 일반적으로 이렇다.merge 부분도 사실 교체로 완성할 수 있다.
def merge_2(left, right):
p1 = p2 = 0
temp = []
while p1 < len(left) and p2 < len(right):
if left[p1] <= right[p2]:
temp.append(left[p1])
p1 += 1
else:
temp.append(right[p2])
p2 += 1
while p1 < len(left):
temp.append(left[p1])
p1 += 1
while p2 < len(right):
temp.append(right[p2])
p2 += 1
return temp
단순히 이 상황에 대해 말하자면, 교체된merge는 지루하고 효율이 향상되지 않았다.그러나 그 장점은 적용성이 넓다는 것이다. 왜냐하면mergesort의 변형이 많아서merge 함수를 귀속적으로 호출하기 불편하기 때문이다.변형:mergesort는 tweak의 응용이 많은데 대부분이 수조의 전후 관계를 고려해야 한다.
예1.하나의 수조nums를 제시하려면 모든 수의 index 다음에 그것보다 큰 수의 개수에 대해 화합을 구해야 합니다.예를 들어 [7, 4, 5, 2, 8, 9, 0, 1]을 제시하고 11을 되돌려줍니다. 7 뒤에 8, 9 두 개가 그보다 크기 때문입니다. 4는 3개, 5는 2개, 2는 2개, 8은 1개, 0은 1개로 총 2+3+2+11=11입니다.
[해] Method1: 하나의naive 방법은 모든 수에 대해 그 뒤에 있는 것보다 큰 수를 두루 검색하는 것이다. 분명히 시간 복잡도는 O(n^2)이다.
Method 2: 또 하나의 방법은 Segment Tree를 고려하여 min에서 max까지의 라인 트리, O(max(n)-min(n)을 먼저 구축하는 것입니다. 초기count는 모두 0입니다.그리고 반대방향으로 생각해서 앞에 작은 것이 몇 개인지 생각해 보세요.즉, 어떤 n[i]에 대해 [min(n), n[i]-1] 구간 안에 몇 개의 count가 있는지 고려한다.끝나면 n[i]의count++.이 예를 들어 먼저 7을 넣고 4가 들어올 때 [0,3] 구간을 검색한다. 4보다 작기 때문에 4의count를 1로 설정한다.이렇게 하면 5가 들어올 때 4의 존재를 검색할 수 있다.이 알고리즘의 다음 단계는 O(nlogn)이지만 라인 트리를 만들어야 합니다. 만약max가 매우 크다면 적합하지 않습니다.물론 argue가 말하기를 나는 아예 가장 작고 가장 큰 32개의 int를 포괄하는 라인 트리를 구성할 수 있다. 이것은 역시 O(1)일까 XD일까.
Method 3: 이 해법은mergesort의 tweak를 사용하는 것을 고려합니다.n[i] 다음에 그것보다 큰 수를 요구하기 때문에 나누어 다스릴 수 있는 사상을 사용할 수 있다. 즉, 앞쪽이 몇 개인지, 뒤쪽이 몇 개인지를 고려한 다음에 그 사이에 몇 개가 있는지 고려할 수 있다.이 예로 말하자면 마지막 메르지 이전의 모습을 고려한다:[2,4,5,7][0,1,8,9]. 이때 양쪽 모두 계산이 끝났고merge에서 발생한 결과만 계산하면 된다.분명히 결과는 8이었다. 왜냐하면 앞부분의 네 수가 모두 8과 9보다 적기 때문이다.그런데 어떻게 계산하나요?후반이 이미 순서를 정했음을 감안하면, 후반에 대해binarysearch를 사용한다면, 당연히 첫 번째로 앞반의 어느 수보다 큰 index를 얻을 수 있고, 이 수보다 큰 모든 수를 얻을 수 있다.그러니까 이merge가 O(nlogn)라는 거야.그러면 전체적으로 T(n)=T(n/2)+O(nlogn)이고 Master Theorem에서 알 수 있는 복잡도는 O(n(logn)^2)이다.
Method4: 단, 위의 이merge 방법은 앞부분도 순서를 정하는 조건이 없기 때문에 더 잘할 수 있습니다.두 개의 지침 p1과 p2를 고려하여 각각 앞반과 뒤반을 가리킨다.p1은 처음에 2, p2는 0이었다. 왜냐하면 이때 n[p2]
# count number that larger than it and after it
def dc2(self, n, s, e):
if s >= e:
return 0
m = (s + e) // 2
ans = self.dc2(n, s, m) + self.dc2(n, m + 1, e)
p1 = s
for q in range(m + 1, e + 1):
while p1 <= m and n[q] > n[p1]:
p1 += 1
ans += p1 - s
# merge
temp = []
p1, p2 = s, m + 1
while p1 <= m and p2 <= e:
if n[p1] <= n[p2]:
temp.append(n[p1])
p1 += 1
else:
temp.append(n[p2])
p2 += 1
while p1 <= m:
temp.append(n[p1])
p1 += 1
while p2 <= m:
temp.append(n[p2])
p2 += 1
for i in range(len(temp)):
n[i + s] = temp[i]
return ans
예2.하나의 수조 n과 하나의 범위 [a, b]를 제시하고 n이 몇 개의 하위 구간의 합이 [a, b] 안에 있는지 구한다.가령 수조 n의 원소와 a, b는 모두 정수이다.예를 들어[2,3,4,1]과 범위[3,5]를 제시하면 자구간[3][4][2,3][4,1]은 모두 조건을 만족시키고 4로 돌아간다.
[해] Method1:naive 방법은 모든 하위 구간을 찾아서 몇 개의 만족 조건이 있는지 보는 것이다.복잡도가 매우 높다.
Method2: 하위 구간의 합을 보면 prefixsum이 생각납니다.즉, 하나의 수조s를 만들 수 있다는 것이다. 모든 원소 s[i]=n[0]+...+n[i].그러면 모든 하위 구역은 n[0]을 제외하고 s의 다음 원소로 이전 원소를 빼서 얻을 수 있다.즉, 문제는 하나의 수조 s를 제시하고 몇 쌍의 ij를 계산하여 s[i]-s[j] in[a,b] 그리고 i
Method 4: Method 3을 기반으로 개선예1과 유사하게 Method 3의 문제는 여전히 절반의 순서를 정하는 조건을 이용하지 않았다는 데 있다.세 가지 지침을 고려하면 p1p2와 q, p1p2는 모두 반쪽을 가리키고 p2는 반쪽을 가리킨다.반정렬의 조건을 이용해야 하기 때문에 고정적으로 점차적으로 증가한다.s[q]에 대해서는 s[q]-s[i]가 구간[a,b]에 있어야 한다.즉, s[q]-s[i]>=a, s[i]<=s[q]-a;s[q] - s[i] <= b, s[i] >= s[q] - b. 따라서 두 개의 지침 p1p2, p1은 s[p1]<=s[q]-a, p2는 s[p2]
class Solution:
# count numbers of subarray sum in range of [a,b]
def countSubarraySum(self, nums, a, b):
if not nums:
return 0
n = [0] * len(nums)
for i in range(len(nums)):
if i != 0:
n[i] = nums[i] + n[i - 1]
else:
n[i] = nums[i]
return self.dc(n, a, b, 0, len(n) - 1)
# count number of prefix sum that x[i] - x[j] in [a, b] and i > j plus itself in [a, b]
def dc(self, n, a, b, s, e):
if s > e:
return 0
if s == e:
return a <= n[s] <= b
m = (s + e) // 2
ans = self.dc(n, a, b, s, m) + self.dc(n, a, b, m + 1, e)
p1 = p2 = s
for q in range(m + 1, e + 1):
while p1 <= m and n[q] - n[p1] >= a:
p1 += 1
while p2 <= m and n[q] - n[p2] > b:
p2 += 1
if p2 <= p1:
ans += p1 - p2
# merge
temp = []
p1, p2 = s, m + 1
while p1 <= m and p2 <= e:
if n[p1] <= n[p2]:
temp.append(n[p1])
p1 += 1
else:
temp.append(n[p2])
p2 += 1
while p1 <= m:
temp.append(n[p1])
p1 += 1
while p2 <= m:
temp.append(n[p2])
p2 += 1
for i in range(len(temp)):
n[i + s] = temp[i]
return ans
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.