首页 > 试题广场 >

完美洗牌问题(2)

[编程题]完美洗牌问题(2)
  • 热度指数:832 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
给定一个数组arr,请将数组调整为依次相邻的数字,总是先<=、再>=的关系,并交替下去。比如数组中有五个数字,调整成[a,b,c,d,e],使之满足a<=b>=c<=d>=e。

输入描述:
输入包含两行,第一行一个整数n,代表数组的长度,接下来一行n个整数,代表数组arr


输出描述:
输出一行,代表调整后的数组。
示例1

输入

6
1 2 3 4 5 6

输出

1 3 2 5 4 6
示例2

输入

3
1 2 3

输出

1 3 2

备注:
时间复杂度,空间复杂度
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    scanf("%d", &n);
    int a[n];
    for(int i=0;i<n;i++)
        scanf("%d", &a[i]);
    sort(a, a+n);
    for(int i=0;i<n;i++){
        if(i&1 && i!=n-1)
            swap(a[i], a[i+1]);
        printf("%d ", a[i]);
    }
    return 0;
}

发表于 2020-05-04 00:45:40 回复(0)
#include <stdio.h>
#include <stdlib.h>

int modifyIndex(int i, int len);

void reverse(int *arr, int l, int r);

void rotate(int *arr, int l, int m, int r);

void shuffle(int *arr, int l, int r);

void cycles(int *arr, int l, int len, int k);

void heapSort(int *arr, int n);

void downAdjust(int *arr, int i, int n);

void swap(int *arr, int i, int j);

int main(void) {
    int n, *arr;
    scanf("%d", &n);
    arr = (int *) malloc(sizeof(int) * n);
    for (int i = 0; i < n; i++) {
        scanf("%d", arr + i);
    }
    heapSort(arr, n);
    if (n % 2 == 0) {
        shuffle(arr, 0, n - 1);
        for (int i = 0; i < n; i += 2) {
            swap(arr, i, i + 1);
        }
    } else {
        shuffle(arr, 1, n - 1);
    }
    for (int i = 0; i < n; i++) {
        if (i != 0) printf(" ");
        printf("%d", arr[i]);
    }
    printf("\n");
    free(arr);
    return 0;
}

int modifyIndex(int i, int len) {
    return (i * 2) % (len + 1);
}

void reverse(int *arr, int l, int r) {
    while (l < r)
        swap(arr, l++, r--);
}

void rotate(int *arr, int l, int m, int r) {
    reverse(arr, l, m);
    reverse(arr, m + 1, r);
    reverse(arr, l, r);
}

void shuffle(int *arr, int l, int r) {
    int len, base, k, n, mid;
    while (r - l + 1 > 0) {
        len = r - l + 1;
        base = 1;
        k = 0;
        // 得出不超过len且满足3^k-1的最长长度
        while (base * 3 <= len + 1) {
            base *= 3;
            k++;
        }
        n = (base - 1) >> 1;
        mid = l + ((r - l) >> 1);
        rotate(arr, l + n, mid, mid + n);
        cycles(arr, l, base - 1, k);
        l += base - 1;
    }
}

void cycles(int *arr, int l, int len, int k) {
    int pre, cur, tmp;
    for (int i = 0, s = 1; i < k; i++, s *= 3) {
        pre = arr[l + s - 1];
        cur = modifyIndex(s, len);
        while (cur != s) {
            tmp = arr[l + cur - 1];
            arr[l + cur - 1] = pre;
            pre = tmp;
            cur = modifyIndex(cur, len);
        }
        arr[l + cur - 1] = pre;
    }
}

void heapSort(int *arr, int n) {
    for (int i = (n - 2) >> 1; i >= 0; i--) {
        downAdjust(arr, i, n);
    }
    while (n > 1) {
        swap(arr, 0, n - 1);
        downAdjust(arr, 0, n - 1);
        n--;
    }
}

void downAdjust(int *arr, int i, int n) {
    int left = i << 1 | 1;
    while (left < n) {
        int better = (left + 1 < n && arr[left + 1] > arr[left]) ?
                     left + 1 : left;
        if (arr[i] >= arr[better]) break;
        swap(arr, i, better);
        i = better;
        left = i << 1 | 1;
    }
}

void swap(int *arr, int i, int j) {
    int t = arr[i];
    arr[i] = arr[j];
    arr[j] = t;
}

发表于 2022-02-08 16:16:24 回复(0)
按照这个题对空间复杂度O(1)的要求,其实应该先用堆排序对数组进行排序,然后分数组长度为偶数和奇数两种情况分别进行处理:
  1. 如果数组长度为偶数,就先对数组进行完美洗牌,然后对洗牌后的数组构建一个长度为2的滚动窗口,每次交换窗口中的两个数就能达成题目的要求;
  2. 如果数组长度为奇数,则第一个元素不动,从第二个到最后一个元素进行完美洗牌就可以了。
而完美洗牌要达到空间复杂度O(1),需要用到下标连环怼的技巧,时间复杂度为O(nlog3n)<O(nlog2n)。从而整个过程的时间复杂度瓶颈主要在于排序,整体时间复杂度为O(nlog2n)。但是……我懒得手撸堆排序了
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        String[] strArr = br.readLine().split(" ");
        int[] arr = new int[n];
        for(int i = 0; i < n; i++) arr[i] = Integer.parseInt(strArr[i]);
        // 先排序,对于基础数据类型,java中数组长度不超过60时使用的是插入排序,超过之后使用的是快排。因此这一步严格来说要手撸堆排
        Arrays.sort(arr);
        if((n & 1) == 0){
            // 数组长度为偶数,完美洗牌后两个一组逆序
            perfectShuffle(arr, 0, n - 1);
            for(int i = 0; i < n - 1; i += 2){
                System.out.print(arr[i + 1] + " " + arr[i] + " ");
            }
        }else{
            // 数组长度为奇数,第一个数不动,后面的数进行一次完美洗牌
            perfectShuffle(arr, 1, n - 1);
            for(int i = 0; i < n; i ++){
                System.out.print(arr[i] + " ");
            }
        }
    }
    
    private static void perfectShuffle(int[] arr, int left, int right){
        while(right - left + 1 > 0){
            // 在left~right上将数组中的元素按3的次幂长度进行完美洗牌
            int len = right - left + 1;
            int base = 3, k = 1;
            while(base <= (len + 1) / 3){
                base *= 3;
                k ++;
            }
            int halfLen = (base - 1) >> 1;
            int mid = left + ((right - left) >> 1);
            rotate(arr, left + halfLen, mid, mid + halfLen);
            cycles(arr, left, base - 1, k);
            left = left + base - 1;
        }
    }
    
    private static void cycles(int[] arr, int start, int len, int k) {
        // 以1为第一个触发点,每次乘以3,触发k次推下标的操作
        for(int i = 0, trigger = 1; i < k; i++, trigger *= 3) {
            int preValue = arr[trigger + start - 1];
            int cur = modifyIndex(trigger, len);
            while(cur != trigger){
                int temp = arr[cur + start - 1];
                arr[cur + start - 1] = preValue;
                preValue = temp;
                cur = modifyIndex(cur, len);
            }
            arr[cur + start - 1] = preValue;
        }
    }
 
    private static int modifyIndex(int i, int n) {
        return (2 * i) % (n + 1);
    }
 
    private static void rotate(int[] arr, int left, int mid, int right) {
        reverse(arr, left, mid);
        reverse(arr, mid + 1, right);
        reverse(arr, left, right);
    }
 
    private static void reverse(int[] arr, int left, int right) {
        while(left < right) {
            int temp = arr[left];
            arr[left++] = arr[right];
            arr[right--] = temp;
        }
    }
}
再偷懒一点的话,可以用O(N)的时间和空间复杂度完成完美洗牌,这样代码写得更快
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        String[] strArr = br.readLine().split(" ");
        int[] arr = new int[n];
        for(int i = 0; i < n; i++) arr[i] = Integer.parseInt(strArr[i]);
        // 先排序
        Arrays.sort(arr);
        if((n & 1) == 0){
            // 数组长度为偶数,完美洗牌后两个一组逆序
            perfectShuffle(arr, 0, n - 1);
            for(int i = 0; i < n - 1; i += 2){
                System.out.print(arr[i + 1] + " " + arr[i] + " ");
            }
        }else{
            // 数组长度为奇数,第一个数不动,后面的数进行一次完美洗牌
            perfectShuffle(arr, 1, n - 1);
            for(int i = 0; i < n; i ++){
                System.out.print(arr[i] + " ");
            }
        }
    }
    
    private static void perfectShuffle(int[] arr, int start, int end){
        int[] temp = new int[end - start + 1];
        int left = start, mid = start + ((end - start) >> 1), right = mid + 1;
        int index = 0;
        while(left <= mid){
            if((index & 1) == 0){
                temp[index++] = arr[right++];
            }else{
                temp[index++] = arr[left++];
            }
        }
        for(int i = start; i <= end; i++){
            arr[i] = temp[i - start];
        }
    }
}

编辑于 2021-12-07 09:55:56 回复(0)
// 思路:观察规律就是,如果是有序的数组,那么只要每隔两个数进行交换,那就可以了
// 如果数组长度<=2,那么就不改变
function mysort(a,b){
    if(a>b){
        return 1;
    }else if(a<b){
        return -1
    }else{
        return 0
    }
}
var len=parseInt(readline())
var arr=readline().split(' ').map(Number).sort(mysort);
if(len<=2){
    console.log(arr.join(' '))
}else{
    for(var i=1;i<len;i+=2){
        var tem=arr[i];
        arr[i]=arr[i+1];
        arr[i+1]=tem;
        // 遍历到倒数第二个的时候就可以停了,也有可能到达最后一个
        if(i+1>=len-2){
            break;
        }
    }
    console.log(arr.join(' '))
}

发表于 2020-04-01 23:35:46 回复(0)
#include <algorithm>
#include <iostream>

using namespace std;

//给定一个数组arr,请将数组调整为依次相邻的数字,总是先<=、再>=的关系,并交替下去。比如数组中有五个数字,调整成[a,b,c,d,e],使之满足a<=b>=c<=d>=e。
//
//输入描述:
//输入包含两行,第一行一个整数n(1 \leq n \leq 10^5)(1≤n≤10 
//5
// ),代表数组的长度,接下来一行n个整数,代表数组arr(1 \leq arr_i \leq 10^9)(1≤arr 
//i
//	
// ≤10 
//9
// )。
//
//
//输出描述:
//输出一行,代表调整后的数组。
//示例1
//输入
//6
//1 2 3 4 5 6
//输出
//1 3 2 5 4 6
int main() {
    int n;
    cin >> n;
    int right = n / 2;
    int left = n - right;
    int arr[n];
    for (int i = 0; i < n; ++i) {
        cin >> arr[i];
    }
    sort(arr, arr + n);
    for (int i = 0; i < right; ++i) {
        cout << arr[i] << " " << arr[left + i] << " ";
    }
    if (left != right) {
        cout << arr[left - 1];
    }
    return 0;
}

发表于 2019-10-08 14:35:50 回复(0)
#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    cin>>n;
    vector<int>num(n);
    for(int i=0;i<n;i++)
        cin>>num[i];
    sort(num.begin(),num.end());
    for(int i = 1;i<n-1;i+=2)
        swap(num[i],num[i+1]);
    for(int i=0;i<n;i++)
        cout<<num[i]<<" ";
    return 0;
}

发表于 2019-09-20 18:01:06 回复(0)
这样有Special Judge,样例过不了不影响提交测评。以下是书中代码
import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] arr = new int[n];
        for (int i = 0; i < n; ++i) {
            arr[i] = sc.nextInt();
        }
        shuffle(arr);
        StringBuilder buf = new StringBuilder();
        if (n > 0) {
            buf.append(arr[0]);
            for (int i= 1; i < n; ++i) {
                buf.append(" ").append(arr[i]);
            }
        }
        System.out.println(buf);
    }

    private static void shuffle(int[] arr) {
        if (arr != null && arr.length != 0) {
            Arrays.sort(arr);
            if ((arr.length & 1) == 1) {
                shuffle(arr, 1, arr.length - 1);
            } else {
                shuffle(arr, 0, arr.length -1);
                for (int i= 0;i < arr.length; i += 2) {
                    int tmp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = tmp;
                }
            }
        }
    }

    private static void shuffle(int[] arr, int L, int R) {
        while(R - L + 1 > 0) {
            int len = R - L + 1;
            int base = 3;
            int k = 1;
            while(base <= (len + 1) / 3) {
                base *= 3;
                k++;
            }
            int half = (base - 1) / 2;
            int mid = (L + R) / 2;
            rotate(arr, L + half, mid, mid + half);
            cycles(arr, L, base -1, k);
            L = L + base - 1;
        }
    }

    private static void cycles(int[] arr, int start, int len, int k) {
        for(int i = 0, trigger = 1; i < k; ++i, trigger *= 3) {
            int preValue = arr[trigger + start - 1];
            int cur = modifyIndex(trigger, len);
            while (cur != trigger) {
                int tmp = arr[cur + start - 1];
                arr[cur + start - 1] = preValue;
                preValue = tmp;
                cur = modifyIndex(cur, len);
            }
            arr[cur + start - 1] = preValue;
        }
    }

    private static int modifyIndex(int i, int len) {
        return (2 * i) % (len + 1);
    }

    private static void rotate(int[] arr, int L, int M, int R) {
        reverse(arr, L, M);
        reverse(arr, M + 1, R);
        reverse(arr, L, R);
    }

    private static void reverse(int[] arr, int l, int r) {
        while(l < r) {
            int tmp = arr[l];
            arr[l++] = arr[r];
            arr[r--] = tmp;
        }
    }

}



发表于 2021-08-25 15:12:11 回复(0)
import java.util.*;

public class Main {
    public static void main (String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = sc.nextInt();
        }
        heapSort(arr);
        //奇数数量情况下:
        if((n & 1) != 0) {
            shuffle(arr, 1, arr.length-1);
        }
        //偶数数量情况下:
        else {
            shuffle(arr, 0, arr.length-1);
            for (int i = 0; i < arr.length - 1; i+=2) {
                swap(arr, i, i+1);
            }
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            sb.append(arr[i] + " ");
        }
        System.out.println(sb.toString().trim());
    }
    
    /*
    在arr[L...R]上做洗牌,整体策略为:
    假设共2*6=12个数,先在 L1, L2, ..., R3, R4 这8个数上做下标连推,
    然后分别在L5, R5;L6, R6 这两组数上做下标连推,目的是为了使每次做下标连推
    的子数组数量都满足3^k-1. 为了实现下标连推,则必须在之前实现翻转字符串
    比方说在第一次下标连推前,把L5, L6,..., R3, R4左部分设为L5, L6, 有部分设为
    R1..R4, 然后进行翻转
    */
    public static void shuffle(int[] arr, int L, int R) {
        while(R - L + 1 > 0) {
            int len = R-L+1;
            int base = 3;
            int k = 1;
            //计算<=len且距离len最近的,满足(3^k-1)的数
            //也就是找到最大的k,满足3^k <= len+1
            while(base <= (len+1) / 3) {
                base *= 3;
                k++;
            }
            //当前要解决base-1块,一半就是再除以2
            int half = (base-1) / 2;
            //[L...R]的中点位置
            int mid = (L+R) / 2;
            //要旋转的左部分为[L+half...mid], 右部分为arr[mid+1...mid+half]
            //注意,这里arr下标是从0开始
            reversePart(arr, L + half, mid, mid + half);
            //旋转完成后,从L开始算起,长度为base-1的部分进行下标连续推
            cycles(arr, L, base-1, k);
            L += base - 1;
        }
    }
    
    public static void reverse(int[] arr, int L, int R) {
        while (L < R) {
            int tmp = arr[L];
            arr[L++] = arr[R];
            arr[R--] = tmp;
        }
    }
    
    public static void reversePart(int[] arr, int L, int M, int R) {
        reverse(arr, L, M);
        reverse(arr, M+1, R);
        reverse(arr, L, R);
    }
    
    /*
    下标连推,即多米诺骨牌效应,从某一位开始,使其顶掉新位置上的数,再让被顶掉
    的数去顶它的新位置上的数
    若数组长度为2*N == 3^k - 1, 则共有k个多米诺骨牌效应,且出发位置为1,3,9...3^(k-1)
    */
    public static void cycles(int[] arr, int start, int len, int k) {
        for(int i = 0, trigger = 1; i < k; i++, trigger *= 3) {
            int preVal = arr[start + trigger - 1];
            int cur = getNewIndex(trigger, len);
            while (cur != trigger) {
                int tmp = arr[start + cur - 1];
                arr[start + cur - 1] = preVal;
                preVal = tmp;
                cur = getNewIndex(cur, len);
            }
            arr[start + cur - 1] = preVal;
        }
    }
    
    //获得的新位置在数组中第几位
    public static int getNewIndex(int i, int len) {
        if (i <= len / 2)
            return 2 * i;
        else 
            return 2 * (i - len/2) - 1;
    }
    
    public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
    
    public static void heapify(int[] arr, int i, int size) {
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        int maxIndex = 0;
        while (left < size) {
            maxIndex = left;
            if (right < size && arr[right] > arr[left])
                maxIndex = right;
            maxIndex = arr[maxIndex] > arr[i] ? maxIndex : i;
            if (maxIndex == i)
                break;
            swap(arr, i, maxIndex);
            i = maxIndex;
            left = 2 * i + 1;
            right = 2 * i + 2;
        }
    }
    
    public static void heapSort(int[] arr) {
        for (int i = arr.length-1; i > -1; i--) {
            heapify(arr, i, arr.length);
        }
        for (int i = arr.length-1; i > -1; i--) {
            swap(arr, 0, i);
            heapify(arr, 0, i);
        }
    }
}
满足时间与额外空间复杂度要求
发表于 2021-07-30 18:28:12 回复(0)

问题信息

上传者:小小
难度:
8条回答 3011浏览

热门推荐

通过挑战的用户

查看代码