LeetCode算法笔记 【第一章:算法入门与数组篇】

3、数组排序

(1)冒泡排序

算法思想:

通过相邻元素之间的比较与交换,使值较小的元素逐步从后面移到前面,值较大的元素从前面移到后面。

算法步骤:

第 1 次:先将序列中第 1 个元素与第 2 个元素进行比较,较大者置于后面,然后将第 2 个元素与第 3 个元素比较,较大者置于后面。依次类推,直到第 n−1 个元素与第 n 个元素比较。

······
依次类推,进行多次冒泡排序,直到排出正确序列,排序结束。


代码实现:

排序 arr = [5, 2, 3, 6, 1, 4] :

arr = [5, 2, 3, 6, 1, 4]
l = len(arr)
for i in range(l-1):
    flag = False
    for j in range(l-i-1):
        if arr[j]>arr[j+1]:
            arr[j],arr[j+1]=arr[j+1],arr[j]
            flag = True
    if not flag:
        break
print(arr)
    

算法分析:

最佳时间复杂度:O(n)  初始时序列已经排列好,只需经过 1 次排序、 n 次元素之间的比较。

最坏时间复杂度:O(n^2) 需要进行 n次排序,总共进行 n(n−1)/2 次元素之间的比较。

空间复杂度:O(1) 冒泡排序只用常数项变量。

适用情况:冒泡排序比较适合数据量较小的情况,尤其是当序列的初始状态基本有序的情况。

排序稳定性:排序不会改变元素的相对顺序,是一种稳定排序算法。


(2)选择排序

算法思想:


每次从数组中选择出一个最小元素,放到数组未排序序列最左端,从而实现该元素左部分为已排序数组,右端为未排序数组,依次遍历直到排序完成


算法步骤:


1、遍历区间 [0,n−1], 记录区间中最小元的素下标。

2、将最小元素下标与下标为 0 处的元素交换位置。

3、遍历区间 [1,n−1],记录区间中最小元的素下标。

4、将最小元素下标与下标为 1 处的元素交换位置。

5、依次类推,对剩余区间排序,直到所有元素都完成排序。


图解:


代码实现:

排序 arr = [5, 2, 3, 6, 1, 4]:

arr = [5, 2, 3, 6, 1, 4]
l=len(arr)
for i in range(l):
    min=i
    for j in range(i,l):
        if arr[j]<arr[min]:
            min=j
    arr[i],arr[min]=arr[min],arr[i]
print(arr)
    
            

算法分析:

时间复杂度:O(n^2)。每次排序都要进行n-i次元素之间的比较,共为 n(n−1)/2 次。
空间复杂度:O(1)。只用到常数项变量
适用情况:数据量较小,空间复杂度要求较低时,可以考虑选择排序。
稳定性:可能会改变相等元素的相对顺序,因此选择排序法是一种不稳定排序算法。


(3)插入排序

算法思想:

将数组分为左侧有序区间和右侧无序区间,每次从无序区间取出一个元素,插入到有序区间的适当位置。

算法步骤:

1、取出无序区间 [1,n−1] 中的第 1 个元素,nums[1]。

2、从右到左遍历有序区间中的元素,将比 nums[1] 大的元素向后移动 1 位

3、遇到小于或等于 nums[1] 的元素,将 nums[1] 插入到该位置

4、取出无序区间 [2,n−1] 中的第 1 个元素。

5、从右到左遍历有序区间中的元素,将比 nums[2] 大的元素向后移动 1 位

6、遇到小于或等于 nums[1] 的元素,将 nums[2] 插入到该位置

7、依次类推,直到所有元素都插入到有序区间中。

图解:

代码实现:

排序 arr = [5, 2, 3, 6, 1, 4] :

arr = [5, 2, 3, 6, 1, 4]
l = len(arr)
for i in range(1,l):
    temp = arr[i]
    j = i
    while j>0 and arr[j-1]>temp:
        arr[j]=arr[j-1]
        j-=1
    arr[j]=temp
print(arr)
        

算法分析:

最佳时间复杂度:O(n) : 每个元素只进行一次比较,共n-1次

最差时间复杂度:O(n^2) : 每个元素都要进行 i−1 次比较,总次数为n(n-1)/2

空间复杂度 : O(1) :插入排序只用到常数项变量

排序稳定性:插入排序每次都插入到相等元素的右侧,不会改变相等元素的顺序,是稳定排序算法

(4)归并排序

算法思想:

先递将当前数组平均分成两半,然后将有序数组两两合并,最终合并成一个有序数组。

算法步骤:

1、分解:找到数组中心位置,将数组分为两部分,再对子数组进行分解,直到子数组长度均为1

2、归并:从长度为1的子数组开始,进行两两排序、合并直到数组末尾

代码实现:

arr = [0,5,7,3,1,6,8,4]

def merge_sort(nums):
    l = len(nums)

    if l<= 1:
        return nums

    mid = l // 2
    left_l= merge_sort(nums[:mid])
    right_l= merge_sort(nums[mid:])
    p, q = 0, 0
    temp = []
    len_left, len_right = len(left_l), len(right_l)
    while len_left > p and len_right > q:
        if left_l[p] <= right_l[q]:
            temp.append(left_l[p])
            p += 1
        else:
            temp.append(right_l[q])
            q += 1
    temp += left_l[p:]
    temp += right_l[q:]

    return temp
nums=[0, 5, 7, 3, 1, 6, 8, 4]
print(merge_sort(nums))

算法分析:

时间复杂度:O(n×log⁡n) 等于归并次数与每一次归并复杂度的乘积

空间复杂度:O(n×log⁡n)  归并排序方法需要用到与参加排序的数组同样大小的辅助空间

排序稳定性:稳定排序

(5)希尔排序

算法思想:

将整个数组按照一定的间隔划分为若干个子数组,每个子数组分别进行插入排序。然后逐渐缩小间隔进行下一轮操作。直至最后一轮排序间隔为 1,对整个数组进行插入排序。

算法步骤:

1、确定一个间隔数

2、将数组按照间隔分成若干个子数组

3、对各个子数组进行排序

4、减少间隔数,并重新分组,再分别对各个子数组进行排序。

5、依次类推,直到间隔数为 1,对仅有的一个数组进行排序。

图解:

代码实现:

排序 arr  =  [5, 7, 2, 3, 6, 8, 1, 4]

arr = [5, 7, 2, 3, 6, 8, 1, 4]
print(arr)
l = len(arr)
gap = l//2
while gap > 0:
    for i in range(gap,l):
        temp = arr[i]
        j = i
        while j>=gap and arr[j-gap]>temp:
            arr[j] = arr[j-gap]
            j-=gap
        arr[j] = temp
    gap = gap//2
print(arr)

算法分析:

时间复杂度:介于 O(n×log2⁡n) 与 O(n^2) 之间

空间复杂度:O(1) 只使用了常数项变量

排序稳定性:在不同的插入排序中,相等元素能在各自的排序中移动,因此,希尔排序不稳定

(6)快速排序

算法思想:

选择一个数组中的元素作为参照,通过排序将数组中小于参照的元素放入一个数组中,大于参照的放入另一个数组中,按同样的方式对两个子数组再进行快速排序。

算法步骤:

1、选定参照数

2、使用指针i,j 分别指向数组头部和尾部

3、向右移动指针i,直到 i 遇到比参照大的元素,i停止

4、向左移动指针j,直到 j 遇到比参照小的元素,j停止

5、交换i,j指针元素位置

6、重复操作,直到i,j指针相遇停止

7、对划分好的子数组进行上述排序,直到每个子数组元素个数为一。

图示:

代码实现:

arr = [5, 2, 3, 6, 1, 4]
def quicksort(arr, start, end):
    if start >= end:
        return
    p, i, j = arr[start], start, end
    while i < j:
        while arr[j] >= p and i < j:
            j -= 1
        arr[i] = arr[j]
        while arr[i] < p and i < j:
            i += 1
        arr[j] = arr[i]
    arr[i] = p
    quicksort(arr, start, i - 1)
    quicksort(arr, i + 1, end)
quicksort(arr, 0, len(arr) - 1)
print(arr)

算法分析:

最佳时间复杂度:O(n×log⁡n) 每一次选择参照数都是数组的中位数

最坏时间复杂度:O(n2) 每一次选择参照数都是数组的末端元素

空间复杂度:O(n) 块速排序过程中用到堆栈或其他空间来存放当前数组的首、尾位置

排序稳定性:进行哨兵划分时,参照数可能会换到相等元素右边,是一种不稳定排序

(7)堆排序

堆结构:

1、堆的定义

堆:一种完全二叉树,有大顶堆、小顶堆两种形式

大顶堆:任意节点值 ≥ 其子节点值                                        小顶堆:任意节点值 ≤ 其子节点值

2、堆的存储结构

对于完全二叉树,采用顺序存储结构。

节点下标:若某节点下标为i,则其左子节点下标为:2*i+1;右子节点下标为:2*i+2

父根节点下标为:i-/2(向下取整)


3、访问堆顶元素

堆顶元素位于根节点,使用顺序结构表示堆时,堆顶元素位于数组队首位置


4、向堆中插入元素

步骤:

1、将新元素插入到堆的末尾

2、从该元素父节点开始比较,若大于父节点就交换位置,若小于则位置不变

3、重复上述步骤,直至新元素小于父节点或达到根节点


5、删除堆顶元素

步骤:

1、将堆顶元素与堆尾元素互换

2、移除末尾元素

3、从堆顶元素开始,逐步与较大的子节点比较,调整至合适位置

4、重复上述步骤,直到新的堆顶元素不再小于其子节点或者达到了堆的底部


堆排序:

1、算法思想

借助堆结构,将数组转化为大顶堆,重复从大顶堆中取出最大的节点

2、算法步骤

1、定义堆结构的数组,将原数组的元素存入该数组中,通过排序形成大顶堆

2、交换堆顶元素与最后一个元素位置,将堆顶元素放在最后,堆长度减一

3、从根节点开始对新结构排序,使其形成大顶堆

4、重复上述步骤、直至堆的大小为1

3、代码实现
arr =[10, 25, 6, 8, 7, 1, 20, 23, 16, 19, 17, 3, 18, 14]
def heap(arr, index, end):
    left = index * 2 + 1
    right = left + 1
    while left <= end:
        max_index = index
        if arr[left] > arr[max_index]:
            max_index = left
        if right <= end and arr[right] > arr[max_index]:
            max_index = right
        if index == max_index:
            break
        arr[index], arr[max_index] = arr[max_index], arr[index]
        index = max_index
        left = index * 2 + 1
        right = left + 1
def build(arr):
    size = len(arr)
    for i in range((size - 2) // 2, -1, -1):
        heap(arr, i, size - 1)
    return arr
def heapsort(arr):
    build(arr)
    size = len(arr)
    for i in range(size):
        arr[0], arr[size - i - 1] = arr[size - i - 1], arr[0]
        heap(arr, 0, size - i - 2)
    return arr
print(heapsort(arr))
4、算法分析

时间复杂度:O(n×log⁡n)。

空间复杂度:O(1),堆排序只使用了一个空间

排序稳定性:排序时相同元素位置会发生变化,是一种不稳定算法

(8)计数排序

算法思想:

统计数组中每个元素在数组中出现的次数,从而将数组元素有序的放置到正确位置。

算法步骤:

1、遍历数组,找出最大元素和最小元素,从而计算出排序的范围

2、定义一个大小为排序范围的数组 ,统计每个元素的出现次数。

3、遍历排序数组,在计数数组中统计每个元素出现次数

4、按计数数组元素显示的数量填充结果数组

代码实现:

arr = [3,  4, 2, 5, 1, 3, 1, 4, 5]
nums_min, nums_max = min(arr), max(arr)
size = nums_max - nums_min + 1
counts = [0 for _ in range(size)]
for i in arr:
       counts[i-nums_min]+=1
arr.clear()
for i in range(size):
        for j in range(counts[i]):
                arr.append(i+nums_min)
print(arr)

算法分析:

时间复杂度:O(n+k):k 代表待排序数组的值域。

空间复杂度:O(k)

适用情况:计数排序一般用于整数排序,不适用于按字母顺序、人名顺序排序。

(9)桶排序

算法思想:

将数组中的元素分到多个桶中,然后对每个桶中的元素再进行排序。

算法步骤:

1、根据数组元素大小,将数组分为 k 个桶。

2、将每个元素根据大小分配到对应的桶中。

3、对每个桶内的元素进行排序。

4、将排好序的各个桶中的元素按照区间顺序依次合并起来,形成一个完整的有序数组。

代码实现:

排序 arr = [39, 49, 8, 13, 22, 15, 10, 30, 5, 44]

arr=[39, 49, 8, 13, 22, 15, 10, 30, 5, 44]
arr_min, arr_max = min(arr), max(arr)
count = (arr_max - arr_min) // 5 + 1
buckets = [[] for _ in range(count)]
for i in arr:
    buckets[(i-arr_min) // 5].append(i)
new_arr=[]
for bucket in buckets:
    bucket.sort()
    new_arr.extend(bucket)
print(new_arr)

算法分析:

时间复杂度 :O(n) :当桶的个数 m 接近于数据个数 n 时,桶排序时间复杂度接近于 O(n)。

空间复杂度:O(n+m):桶排序使用了辅助空间,空间复杂度是 O(n+m)。

排序稳定性:桶排序的稳定性取决于桶内使用的排序算法

(10)基数排序

算法思想:

将整数各位数进行分解比较,从而达到排序的目的。

算法步骤:

从低位到高位比较:

1、求出位数:遍历数组元素,获取最大元素,求出最大位数。

2、定义一个长度为10的桶数组,每个桶代表数字0-9

3、按照每个元素当前位上的数字,将元素放入对应数字的桶

4、清空原始数组,按桶中的顺序重新将元素放入到原始数组中

5、前移一位,重复上述过程,直至最后一位

代码实现:

排序 arr=[692,924,969,503,871,704,542,436]:

arr = [692, 924, 969, 503, 871, 704, 542, 436]
size = len(str(max(arr)))
for i in range(size):
    buckets = [[] for _ in range(10)]
    for a in arr:
        buckets[a//(10**i)%10].append(a)
    arr.clear()
    for bucket in buckets:
        for a in bucket:
            arr.append(a)
print(arr)

算法分析:

时间复杂度:O(n×k) : n 是待排序元素的个数,k 是数字位数。

空间复杂度:O(n+k) 共调用k个桶空间

排序稳定性:内部采用的桶排序是稳定算法,所以基数排序也是一种稳定排序算法。

排序算法适用在排序元素位数较多时

(11)练习题目

把数组排成最小的数:

题目:给定一个非负整数数组 nums。将数组中的数字拼接起来排成一个数,打印能拼接出的所有数字中的最小的一个。

class Solution(object):
    def minNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: str
        """
        l = len(nums)
        for i in range(l):
            nums[i]=str(nums[i])
        for i in range(l):
            for j in range(i+1,l):
                if nums[i]+nums[j]>nums[j]+nums[i]:
                    nums[i],nums[j]=nums[j],nums[i]
        s = ''.join(nums)
        return s

移动零:

题目:给定一个数组 nums,将所有 0 移动到末尾,并保持原有的非 0 数字的相对顺序。

class Solution(object):
    def moveZeroes(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        l = len(nums)
        i = 0
        while i<l:
            if nums[i]==0:
                nums.pop(i)
                nums.append(0)
                l-=1
                i=0
            else : i+=1

排序数组:

题目:给定一个整数数组 nums。将该数组升序排列。

最简便方法:使用列表类方法sort()升序排列;sort(reverse=True)降序排列

class Solution(object):
    def sortArray(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        nums.sort()
        return nums

相对名次:

题目:给定一个长度为 n 的数组 score。其中 score[i] 表示第 i 名运动员在比赛中的成绩。所有成绩互不相同。找出他们的相对名次,并授予前三名对应的奖牌。前三名运动员将会被分别授予「金牌("Gold Medal")」,「银牌("Silver Medal")」和「铜牌("Bronze Medal")」。

class Solution(object):
    def findRelativeRanks(self, score):
        """
        :type score: List[int]
        :rtype: List[str]
        """
        new_score = []
        for i in score:
            new_score.append(i)
        score.sort(reverse= True)
        medal=["Gold Medal","Silver Medal","Bronze Medal"]+[str(i)  for i in range(1,len(score)+1) if i > 3]
        for i in range(len(score)):
            for j in range(len(score)):
                if new_score[j]==score[i]:
                    new_score[j] = medal[i]
        return new_score

合并两个有序数组:

题目:给定两个有序数组 nums1、nums2,将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

class Solution(object):
    def merge(self, nums1, m, nums2, n):
        """
        :type nums1: List[int]
        :type m: int
        :type nums2: List[int]
        :type n: int
        :rtype: None Do not return anything, modify nums1 in-place instead.
        """
        nums1[m:] = nums2
        nums1.sort()

数组中的逆序对:

题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

暴力算法:

class Solution(object):
    def reversePairs(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n = 0
        l = len(nums)
        for i in range(l-1):
            for j in range(i+1,l):
                if nums[i]>nums[j]:
                    n+=1
        return n

时间更短的算法:

class Solution(object):
    def reversePairs(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        def merge_sort(l, r):
            if l >= r:
                return 0
            m = (l + r)//2 
            i, j = l, m+1  
            res = merge_sort(l, m) + merge_sort(m+1, r) 
            tmp[l:r+1] = nums[l:r+1]    
            for k in range(l,r+1):
                if i == m+1:    
                    nums[k] = tmp[j]
                    j += 1
                elif j == r+1 or tmp[i] <= tmp[j]:  
                    nums[k] = tmp[i]
                    i += 1
                else:   
                    nums[k] = tmp[j]
                    j += 1
                    res += m - i + 1
            return res
        tmp = [0] * len(nums)
        return merge_sort(0, len(nums)-1)

颜色分类:

题目:给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。使用整数 0、 1 和 2 分别表示红色、白色和蓝色。不使用 sort 函数

class Solution(object):
    def sortColors(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        a=0
        num=[0,0,0]
        for i in nums:
            num[i]+=1
        for i in range(3):
            for j in range(num[i]):
                nums[a]=i
                a+=1

数组中的第k个最大元素:

题目:给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

class Solution(object):
    def findKthLargest(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        l = len(nums)
        nums.sort()
        return nums[l-k]

最小的k个数:

题目:输入整数数组 arr ,找出其中最小的 k 个数

class Solution(object):
    def getLeastNumbers(self, arr, k):
        """
        :type arr: List[int]
        :type k: int
        :rtype: List[int]
        """
        arr.sort()
        return arr[:k]

数组的相对排序:

题目:给你两个数组,arr1 和 arr2arr2 中的元素各不相同,arr2 中的每个元素都出现在 arr1 中。

对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。

class Solution(object):
    def relativeSortArray(self, arr1, arr2):
        """
        :type arr1: List[int]
        :type arr2: List[int]
        :rtype: List[int]
        """
        arr1_max = max(arr1)
        nums=[0]*(arr1_max+1)
        for i in arr1:
            nums[i]+=1
        arraylist = []
        for i in arr2:
            arraylist.extend([i]*nums[i])
            nums[i]=0
        for i in range(arr1_max+1):
            if nums[i]>0:
                arraylist.extend([i]*nums[i])
        return arraylist

存在重复元素Ⅲ:

题目:给定一个整数数组 nums,以及两个整数 k、t。判断数组中是否存在两个不同下标的 i 和 j,其对应元素满足 abs(nums[i]−nums[j])≤t,同时满足 abs(i−j)≤k。如果满足条件则返回 True,不满足条件返回 False

class Solution(object):
    def containsNearbyAlmostDuplicate(self, nums, indexDiff, valueDiff):
        """
        :type nums: List[int]
        :type indexDiff: int
        :type valueDiff: int
        :rtype: bool
        """
        l = len(nums)
        for i in range(l):
            for j in range(i+1,l):
                if abs(i-j)<=indexDiff and abs(nums[i]-nums[j])<=valueDiff:
                    return True
        return False

最大间距:

题目:给定一个无序的数组 nums,返回 数组在排序之后,相邻元素之间最大的差值 。如果数组元素个数小于 2,则返回 0 。

class Solution(object):
    def maximumGap(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums)<2:
            return 0
        nums.sort()
        num=[]
        for i in range(1,len(nums)):
            num.append(nums[i]-nums[i-1])
        num.sort()
        l = len(num)-1
        return num[l]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值