归并排序
归并排序,是创建在归并操作上的一种有效的排序算法。算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。归并排序思路简单,速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列。
按照分治三步法,对归并排序算法介绍如下:
划分问题:把序列分成元素个数尽量相等的两半。
递归求解:把两个元素分别排序。
合并问题:把两个有序表合并成为一个。
前两部分是很容易完成的,关键在于如何把两个有序表合并成为一个。合并的过程为每次都把两个有序表中最小的元素加以比较,删除其中的较小元素并加入合并后的新表中。由于需要一个新表来存放结果,所以附加空间为 n 。
习题
题目一:2020.09.04 [009] 数组中的逆序对
1.题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:0 <= 数组长度 <= 50000
2.解题思路
这道题给定了数组长度的范围,这表示对时间还是有要求的,用暴力求解肯定行不通,因此这里使用归并排序。
问题的关键在于合并这一步,假设当前有两个有序数组,这里需要一个辅助数组。需要三个指针 i , j 和 k,分别指向辅助数组中的两个有序数组以及合并后的数组。
在合并时比较辅助数组中两个有序表,比较指针i,j当前指向的最小值。将两者之中最小的放入原始数组中,这里分成两种情况:
tmp[i] <= tmp[j]:由于tmp[i]之前并无比它还要大的数,因此对逆序数没有影响。此时只需要令nums[k] = tmp[i],并是i,k指针分别加一即可。
注意这里使用小于等于号的目的是保持归并排序的稳定性(排序算法的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,而在排序后的序列中,r[i] 仍在 r[j] 之前,则称这种排序算法是稳定的;否则称为不稳定的。)
tmp[i] > tmp[j]:此时 tmp[j] 前面的所有数字都比它大,因此逆序数会增加,增加的数值等于tmp[j] 前面的数字个数(即 mid - i + 1)。
class Solution:
def merge_sort(self, nums, tmp, left, right):
if(left >= right):
return 0
mid = (left + right) // 2
sum = self.merge_sort(nums, tmp, left, mid) + self.merge_sort(nums, tmp, mid + 1, right)
i, j, k = left, mid + 1, left
tmp[left:right + 1] = nums[left:right + 1]
while(i <= mid and j <= right):
if(tmp[i] <= tmp[j]):
nums[k] = tmp[i]
i += 1
k += 1
elif(tmp[i] > tmp[j]):
nums[k] = tmp[j]
sum += mid - i + 1
k += 1
j += 1
while(i <= mid):
nums[k] = tmp[i]
k += 1
i += 1
while(j <= right):
nums[k] = tmp[j]
k += 1
j += 1
return sum
def reversePairs(self, nums: List[int]) -> int:
length = len(nums)
tmp = [0] * length
return self.merge_sort(nums, tmp, 0, length - 1)
题目二:2020.09.05 [010] 合并两个排序的链表
1.题目描述
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4限制:
0 <= 链表长度 <= 1000
2.解题思路
利用两个列表有序的性质,这道题只涉及到归并排序中的合并的那一步。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
cur = dum = ListNode(0)
while(l1 and l2):
if(l1.val <= l2.val):
cur.next, l1 = ListNode(l1.val), l1.next
else:
cur.next, l2 = ListNode(l2.val), l2.next
cur = cur.next
cur.next = l1 if l1 else l2
return dum.next
题目三:2020.09.23 [011] 字符串的排列
1.题目描述
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。示例:
输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]限制:
1 <= s 的长度 <= 8
2.解题思路
一直一个长度为N的字符串s,若s中没有重复字符串,那么这个字符串共有N!中排列方式。这个排列可以由深度优先算法dfs求出,从第0位一直到第 N-1 位开始固定,通过将当前固定位x与[x, N-1]上的字符进行交换来变换当前固定位x上的字符。
但是当字符串中出现重复字符时,我们就要进行剪枝。用一个集合记录当前位置出现的字符,若刚交换的字符出现在集合中,那么直接跳过,如果没出现过,则交换后进行下一位的固定,继续执行dfs(x+1)。
class Solution:
def permutation(self, s: str) -> List[str]:
c, res = list(s), []
def dfs(x):
if x == len(c) - 1:
res.append(''.join(c))
return
dict = set()
for i in range(x, len(c)):
if c[i] in dict:
continue
dict.add(c[i])
c[x], c[i] = c[i],c[x]
dfs(x+1)
c[x], c[i] = c[i],c[x]
dfs(0)
return res