nowcoder basic algorithm chapter 2

Posted jiaxin359

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nowcoder basic algorithm chapter 2相关的知识,希望对你有一定的参考价值。

划分问题和快排

普通划分

给定一个数组arr, 和一个数num, 请把小于等于num的数放在数组的左边, 大于num的数放在数组的右边。要求额外空间复杂度O(1), 时间复杂度O(N)

想法是这样的,维持一个index,表示在index以及以前的数字都是小于等于num的。

def split_array(arr, num):
    """维持一个小于等于区域的index,
    如果一个数小于等于num,则和index位置交换
    如果一个数大于num,则直接跳下一个
    """
    i = -1
    for j in range(len(arr)):
        if arr[j] > num:
            pass
        else:
            i += 1
            arr[j], arr[i] = arr[i], arr[j]
    return arr

荷兰国旗问题

给定一个数组arr, 和一个数num, 请把小于num的数放在数组的左边, 等于num的数放在数组的中间, 大于num的数放在数组的右边。 要求额外空间复杂度O(1), 时间复杂度O(N)

思路是这样的,维持两个index,一个less,一个more

def split_array(arr, num):
    """总体的策略是,遇到小的数字,那么把它往前面移动,遇到大的数字,把它让后面移动,遇到相等的数字,不动
    维持两个三个变量来进行交换 less  , more , current
    其中more的值应该为len(arr),这样在判断的时候多判断一次,并且j-=1写在后面

    more要等于len(arr),并且运算的时候先要减一,否则会出现这种情况:
    [7,6,4,1,2,5,4] 变为[2, 4, 4, 1, 5, 6, 7],因为这种情况下面并没有对中间的1进行排序,少了一次排序的次数
    """
    current = 0
    less = -1
    more = len(arr)
    while(current < more):
        if arr[current] < num:      # 如果小于,那么less和current要进行交换,并且都推进一位
            less += 1
            arr[less], arr[current] = arr[current], arr[less]
            current += 1
        elif arr[current] == num:   # 如果等于,那么直接向下进行
            current += 1
        else:
            more -= 1
            arr[current], arr[more] = arr[more], arr[current]
    return arr

快排算法

由划分问题引出来的快排算法,用一个数将数组划分为两部分,然后剩下的两部分继续划分

import random
def quick_sort(arr):
    """快排散发
    
    时间复杂度:随机快排是一种随机算法,使用期望来求解,时间复杂度为O(NlogN)
    空间复杂度: O(logN)
    
    稳定性: 非稳定的,由于是随机选择, 比如有两个相等的数,可能选择左边的,然后右边的会放到它的左边
    """
    sort_range(arr, 0, len(arr)-1)


def sort_range(arr, left, right):
    if left < right:
        position = split_array(arr, left, right)
        sort_range(arr, left, position[0]-1)
        sort_range(arr, position[1]+1, right)

def split_array(arr, left, right):
    """遇到小与num的数,那么把它往前面移动,遇到大的数字,把它让后面移动,遇到相等的数字,继续向后走。

    less = left-1 与 more = right +1
    在less下标左边的数字(包含less)都是比num要小的,
    在more右边的数字(包含more)都是比more要大的


    随机快排的时间复杂度: O(nlogn)
            空间复杂度: O(logn)   使用随机算法,这个复杂度是求随机的期望得到的
    Args:
        left 表示需要排序的左区间,包含left
        right  需要排序的由区间 包含right
    Returns:
        [less+1, more-1] 一个含有两个数的里列表,表示划分的num的坐下标和右下标 如排序后的[2,1,4,4,5,6,7]返回[2,3]
    """
    num = arr[random.randint(left, right)]
    current = left
    less = left -1
    more = right + 1
    while(current < more):
        if arr[current] < num:      # 如果小于,那么less和current要进行交换,并且都推进一位
            less += 1
            arr[less], arr[current] = arr[current], arr[less]
            current += 1
        elif arr[current] == num:   # 如果等于,那么直接向下进行
            current += 1
        else:
            more -= 1
            arr[current], arr[more] = arr[more], arr[current]
    return [less+1 , more-1]

 

堆结构

一个数组可以看做是一颗完全二叉树。 节点标号是从0开始的,其中父节点和子节点的关系:如果父节点为i,那么左子节点为 2*i+1, 右子节点为2*i+2。 若果子节点为i,那么父节点为(i-1)/2,对于0也使用,前提是除法是向0舍入。

大根堆和小根堆:大根堆是一个节点的值总是比它下面节点所有的值都大,这样根节点的值为最大值。小根堆则刚好相反。

如何将数组转换为大根堆:

思路是这样的,对于数组节点都做一次调整,如果它比父节点大,那么和父节点交换,直到交换到根节点。

def heap_insert(arr, current_index):
    father_index = int((current_index - 1) / 2)
    while(arr[father_index] < arr[current_index]):
        arr[father_index], arr[current_index] = arr[current_index], arr[father_index]
        current_index = father_index
        father_index = int((current_index - 1) / 2)
x = [2, 1, 3, 6, 0, 4]
for i in range(len(x)):
    heap_insert(x, i)

 

大根堆的下沉操作:

假如有一个节点的值变小了,那么如何调整这个数组,使得它依然是一个堆,这个过程叫做下沉。思路是这样的:如果是左子树不存在,那么它是叶子节点,不做调整。判断右子树存在吗?然后找出左子树和右子树的最大值,如果当前值比最大值大,那么退出,否则进行交换,然后重复这样的操作。

def heapipy(arr, current_index, heap_size):
    """在大根堆的情况下,如果一个节点变小情况下的 向下调整过程

    Args:
        arr: 要调整的数组
        current_index: 要替换的下标
        heap_size: 数组的长度(堆的大小)
    """
    left_index = 2 * current_index + 1
    while(left_index < heap_size):
        right_index = left_index + 1
        if right_index < heap_size:
            largest =  left_index if arr[left_index] >= arr[right_index] else right_index
        else:
            largest = left_index

        largest = largest if arr[largest] > arr[current_index] else current_index
        if largest == current_index:
            break
        else:
            arr[largest], arr[current_index] = arr[current_index], arr[largest]
            current_index = largest
            left_index = 2*current_index + 1

 

堆排序:

  利用上面两个特性,首先来构造根,然后是将末尾的数和第一个数交换,然后第一个数下沉一下。

def heapipy(arr, current_index, heap_size):
    """在大根堆的情况下,如果一个节点变小情况下的 向下调整过程

    Args:
        arr: 要调整的数组
        current_index: 要替换的下标
        heap_size: 数组的长度(堆的大小)
    """
    left_index = 2 * current_index + 1
    while(left_index < heap_size):
        right_index = left_index + 1
        if right_index < heap_size:
            largest =  left_index if arr[left_index] >= arr[right_index] else right_index
        else:
            largest = left_index

        largest = largest if arr[largest] > arr[current_index] else current_index
        if largest == current_index:
            break
        else:
            arr[largest], arr[current_index] = arr[current_index], arr[largest]
            current_index = largest
            left_index = 2*current_index + 1

def heap_insert(arr, current_index):
    """在数组指定的位置插入值,一般是末尾值,这样能够不断的构建大堆"""
    father_index = int((current_index - 1) / 2)
    while(arr[father_index] < arr[current_index]):
        arr[father_index], arr[current_index] = arr[current_index], arr[father_index]
        current_index = father_index
        father_index = int((current_index - 1) / 2)

def heap_sort(arr):
    """堆排序

    时间复杂度:建堆的时间复杂度为O(n), 调整过程的时间复杂度为O(nlogn),所以总体复杂度为O(nlogn)
    空间复杂度: O(1) 只是交换的时候用了一下额外空间

    稳定性: 不稳定, 比如[4a,4b,4c,5]建堆的时候发生错误,变为【5,4a,4c,4b],排序后变为【5,4b,4c,4a】
            [9,5A,7,5B]在放入堆的时候会发生错误
    """
    # 建立堆栈的过程
    for i in range(len(arr)):
        heap_insert(arr, i)

    arr_length = len(arr)
    arr[0], arr[-1] = arr[-1], arr[0]
    arr_length -= 1
    while(arr_length > 0):
        heapipy(arr, 0, arr_length)
        arr[0], arr[arr_length-1] = arr[arr_length-1], arr[0]
        arr_length -= 1

 

堆的应用:求不断产生数的中位数

由一个数组产生器不断的产生一组数,在产生的过程当中求这些数的中位数。

暴力解法:每求一个数,然后将数组进行一些排序,求中位数,时间复杂度为O(NlogN)

使用堆结构:使用一个大根堆和一个小根堆,两个堆存放的数量相差不会超过1,这样大根堆所有的数小于小根堆所有的数,中位数在大根堆的堆顶或者小根堆的堆顶。

【关于如何弹出一个堆的最大值,或者最小值】,有一个很好的方法是,【1:第一个数和最后一个数交换,2弹出最后一个数,3:向下调整第一个数】

代码如下:

import random

def heapipy(arr, current_index, heap_size):
    """在大根堆的情况下,如果一个节点变小情况下的 向下调整过程
    Args:
        arr: 要调整的数组
        current_index: 要替换的下标
        heap_size: 数组的长度(堆的大小)
    """
    left_index = 2 * current_index + 1
    while(left_index < heap_size):
        right_index = left_index + 1
        if right_index < heap_size:
            largest =  left_index if arr[left_index] >= arr[right_index] else right_index
        else:
            largest = left_index

        largest = largest if arr[largest] > arr[current_index] else current_index
        if largest == current_index:
            break
        else:
            arr[largest], arr[current_index] = arr[current_index], arr[largest]
            current_index = largest
            left_index = 2*current_index + 1

def small_heapipy(arr, current_index, heap_size):
    """在大根堆的情况下,如果一个节点变小情况下的 向下调整过程
    Args:
        arr: 要调整的数组
        current_index: 要替换的下标
        heap_size: 数组的长度(堆的大小)
    """
    left_index = 2 * current_index + 1
    while(left_index < heap_size):
        right_index = left_index + 1
        if right_index < heap_size:
            small =  left_index if arr[left_index] <= arr[right_index] else right_index
        else:
            small = left_index

        small = small if arr[small] < arr[current_index] else current_index
        if small == current_index:
            break
        else:
            arr[small], arr[current_index] = arr[current_index], arr[small]
            current_index = small
            left_index = 2*current_index + 1


def big_heap_insert(arr, current_index):
    father_index = int((current_index - 1) / 2)
    while(arr[father_index] < arr[current_index]):
        arr[father_index], arr[current_index] = arr[current_index], arr[father_index]
        current_index = father_index
        father_index = int((current_index - 1) / 2)
def small_heap_insert(arr, current_index):
    father_index = int((current_index - 1) / 2)
    while(arr[father_index] > arr[current_index]):
        arr[father_index], arr[current_index] = arr[current_index], arr[father_index]
        current_index = father_index
        father_index = int((current_index - 1) / 2)


small_heap = []
big_heap = []
for i in range(100):
    x = random.randint(1,100)
    if not big_heap:
        big_heap.append(x)
    else:
        if x <= big_heap[0]:
            big_heap.append(x)
            big_heap_insert(big_heap,len(big_heap)-1)
        else:
            small_heap.append(x)
            small_heap_insert(small_heap, len(small_heap)-1)
        if len(big_heap) - len(small_heap) == 2:

            big_heap[0], big_heap[-1] = big_heap[-1], big_heap[0]
            tmp = big_heap.pop()
            heapipy(big_heap, 0, len(big_heap))
            small_heap.append(tmp)  # 有代码重复的意思在里面
            small_heap_insert(small_heap, len(small_heap)-1)
        elif len(small_heap) - len(big_heap) == 2:

            small_heap[0], small_heap[-1] = small_heap[-1], small_heap[0]
            tmp = small_heap.pop()
            small_heapipy(small_heap,0, len(small_heap))

            big_heap.append(tmp)
            big_heap_insert(big_heap,len(big_heap)-1)

 

桶排序:

 

计数排序

def counting_sort(arr, arr_size):
    """计数排序

    它是非比较的排序算法 N=len(arr) K = arr_size

    时间复杂度: O(N+K)
    空间复杂度: O(N+K)

    稳定性: 稳定性排序
    """
    buck_count = [0] * arr_size
    for i in arr:
        buck_count[i-1] += 1

    result = []
    for i in range(arr_size):
        result += [i+1] * buck_count[i]
    return result

 

基数排序

def radix_sort(arr, digit):
    """基数排序

    时间复杂度: O(N)  其实是O(digit*n)一般digit很小,所以为O(N)
    空间复杂度: O(N)  用了两个数组,bucket和result(下面程序当中arr=[],直接将值赋给了arr,而不是result)

    稳定性:稳定性算法
    """
    for i in range(digit):
        bucket = [[] for i in range(10)]
        for x in arr:
            number_of_digit = (x % (10**(i+1))) // (10 ** i)  # 得到第i位的数字
            bucket[number_of_digit].append(x)
        arr = []
        for i in range(10):
            arr += bucket[i]
    return arr

求相邻数的最大差值,时间复杂度O(N)

给定一个数组, 求如果排序之后, 相邻两数的最大差值, 要求时间复杂度O(N), 且要求不能用非基于比较的排序

思路是利用桶的思想来进行解决,将相差范围内的数字放入一个桶当中

def get_index_by_num(num, arr_length, max_value, min_value):
    """将一个数划分到桶中
    将arr_length 切分成 (max_value - min_value)份
    然后在 min_value开始,分配这些份
    """
    return int( arr_length / (max_value - min_value) * (num-min_value)  )


def get_max_sub_min(arr):

    max_value = float(-inf)
    min_value = float(inf)

    for i in arr:
        max_value = i if i > max_value else max_value
        min_value = i if i < min_value else min_value
    if max_value == min_value:
        return 0

    arr_length = len(arr)
    max = [float(-inf)] * (arr_length + 1)
    min = [float(inf)] * (arr_length + 1)
    has_num = [False] * (arr_length + 1)

    for i in arr:
        index = get_index_by_num(i, arr_length, max_value, min_value)
        has_num[index] = True
        min[index] = i if i < min[index] else min[index]
        max[index] = i if i > max[index] else max[index]
    print(min)
    print(max)
    print(has_num)

    #最大值不会出现在一个桶的内部,只会出现在两个桶之间,但并不一定是空桶之间
    res = 0
    last_max = max[0]

    for index in range(1,arr_length + 1):
        if has_num[index]:
            res = min[index] - last_max if (min[index] - last_max) > res else res
            last_max = max[index]
    return res

以上是关于nowcoder basic algorithm chapter 2的主要内容,如果未能解决你的问题,请参考以下文章

PAT乙级(Basic Level)练习题-NowCoder数列总结

[Algorithm] A* Search Algorithm Basic

Basics of Algorithmic Trading: Concepts and Examples

Basic Sort Algorithms

NowCoder数列

nowcoder 寻找(LCA)