完美洗牌

目录

给定一棵二叉树的头节点 head,已知所有节点的值都不一样,返回其中最大的且符合搜索二叉树的最大拓扑结构的大小。

拓扑结构:不是子树,只要能连起来的结构都算。

分析:

假设数组下标从 1 开始,我们可以根据数据的原始下标,通过公式推导出调整后的下标。

$f(i)=\begin{cases} 2i & i <=N & 左半区 \ 2(i-N)-1 & i>N & 右半区 \end{cases}$

def modify_index(i, length):
    if i <= int(length / 2): return 2 * i
    return 2 * (i - int(length / 2)) - 1

def modify_index2(i, length):
    return (2 * i) % (length + 1)

如果我们找到一个数据开始,如下图一样可以循环处理下去,就能完成调整。

一个数组可能由很多环,我们需要处理完毕所有环。如果能找到所有环的触发点,问题就迎刃而解了。

下图的两个环,触发点分别是:a 和 c

**结论:当 $N=3^k-1$ (k > 0)时,不同环的所有的触发点是:$3^0,3^1,3^2,...,3^k$**

例如:

  • 当 N = 2 时,触发点:1
  • 当 N = 8 时,触发点:1,3
  • 当 N = 26 时,触发点:1,3,9

对于arr 的长度为: $N=3^k-1$ ,通过以上方法解决了。

# 从 start 位置开始,向右 length 长度这一段,做下标续连推
# 出发位置(trigger):1,,3,9...
def cycles(arr, start,length, k):
    # 找到每一个出发位置 trigger,一共 k 个
    # 每个 trigger 都进行下标连续推
    # 出发位置是从 1 开始计算的,而数组下标是 0 开始计算的
    i = 0
    trigger = 1
    while i < k:
        pre_value = arr[trigger + start - 1]
        cur = modify_index2(trigger, length)
        while cur != trigger:
            tmp = arr[cur + start - 1]
            arr[cur + start - 1] = pre_value
            pre_value = tmp
            cur = modify_index2(cur, length)

        arr[cur + start - 1] = pre_value
        trigger *= 3
        i += 1

arr 的 长度为普通长度怎么办?

要处理普通长度数组的调整,我们需要先了解一种调整结构。

将数组【a,b,c,d,e,甲,乙】调整为 【甲,乙,a,b,c,d,e】步骤如下图。

# arr[left:mid+1] 为左部分,arr[mid+1:right+1] 为右部分,左右部分互换
def rotate(arr, left, mid, right):
    reverse(arr, left, mid)
    reverse(arr, mid + 1, right)
    reverse(arr, left, right)


def reverse(arr, left, right):
    while left < right:
        arr[left], arr[right] = arr[right], arr[left]
        left += 1
        right -= 1

开始处理通常偶数

假设 N = 14, 距离 \(N=3^k-1\) 的数是 8,那么我们先处理前八位。我们需要将【L1,L2,L3,L4】和 【R1,R2,R3,R4】挨着。也就是:

原始数组:【L1,L2,L3,L4,L5,L6,L7,R1,R2,R3,R4,R5,R6,R7】

调整为: 【L1,L2,L3,L4,R1,R2,R3,R4,L5,L6,L7,R5,R6,R7】

此时我们按照: $N=3^k-1=8$ 处理, 当 N = 8 时,触发点:1,3。

处理完毕前8位,再处理剩余后六位:【L5,L6,L7,R5,R6,R7】

N = 6 距离 \(N=3^k-1\) 的数是 2,依次递归处理完毕。

def shuffle(arr):
    if not arr or len(arr) % 2 != 0: return
    proces(arr, 0, len(arr) - 1)


# 在 arr[left:right+1] 上做完美洗牌的调整
def proces(arr, left, right):
    # 切成一块一块的解决,每一块的长度满足 3^k-1
    while right > left - 1:
        length = right - left + 1
        base = 3
        k = 1

        # 计算小于等于 length 并且离 length 最近的,满足 3^k-1 的数
        # 也就是找到最大的 k,满足 3^k <= length
        while base <= int((length + 1) / 3):
            base *= 3
            k += 1

        # 当前要解决长度为 base - 1 的块,一半就是再除2
        half = int((base - 1) / 2)
        # left 和 right 的中点位置
        mid = int((left + right) / 2)
        # 要旋转的左部分为[L + half...mid], 右部分为arr[mid + 1..mid + half]
        # 注意在这里,arr下标是从 0 开始的
        rotate(arr, left + half, mid, mid + half)
        # 旋转完成后,从L开始算起,长度为base-1的部分进行下标连续推
        cycles(arr, left, base - 1, k)
        # 解决了前base - 1 的部分,剩下的部分继续处理
        left = left + base - 1

对数器

import random

def wiggle_sort2(arr):
    if not arr: return
    # 假设这里是堆排序
    arr.sort()
    if len(arr) & 1 == 1:
        proces(arr, 1, len(arr) - 1)

    shuffle(arr)
    for i in range(0, len(arr), 2):
        tmp = arr[i]
        arr[i] = arr[i + 1]
        arr[i + 1] = tmp

def check():
    for i in range(100):
        arr = [int(random.random() * 100) for _ in range(int(random.random() * 10) + 1)]
        arr1 = arr[:]
        arr2 = arr[:]
        shuffle(arr1)
        shuffle2(arr2)
        if arr1 != arr2:
            print("ERROR", arr, arr1, arr2)
    print("OVER")

wiggle sort

给出一个无序的数组,在原地将数组排列成符合以下规律:nums[0] <= nums[1] >= nums[2] <= nums[3]….

Given nums = [3, 5, 2, 1, 6, 4], one possible answer is [1, 6, 2, 5, 3, 4].

空间复杂度要求:O(1)

第一步先排序:

​ 使用堆排序(三大排序算法:快排,堆排序,归并排序中只有堆排序的空间复杂度为:O(1))

第二步:

​ 如果 N 是偶数:进行完美洗牌,完美洗牌后大数在第一位,需要将相邻两数两两交换顺序。

如果 N 是奇数:对arr[1:] 进行完美洗牌。

def wiggle_sort(arr):
    if not arr: return
    # 假设这里是堆排序
    arr.sort()
    if len(arr) % 2 == 0:
        return shuffle(arr)
    return proces(arr, 1, len(arr) - 1)