剑指offer 调整数组顺序使奇数位于偶数前面

使用双向链表优化数组排序问题
本文讨论了一个关于调整数组中奇偶数顺序的问题,通过双向链表实现了时间复杂度为O(n)且空间复杂度为O(1)的解决方案。详细介绍了问题的思考过程、算法设计和代码实现,并通过实例分析解决死循环问题。

题目描述:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

思路:
其实这道题我的第一想法就是利用归并排序的思想来实现,但是这样的话时间复杂度会是O(nlogn),空间复杂度是O(n),不可取。

既然是移动操作,在数组上必然很费时间,因此我想到了用双向链表来实现,这样的话时间复杂度是O(n),空间复杂度是O(1),可以。

具体实现:

首先遍历链表,将所有奇数的节点插入到第一个偶数节点之前,这样遍历完成后一定满足题意。但是写完提交上去后返回TLE,这令我不得其解,难道链表的效率还不如数组?感觉因该是插入操作写搓了,死循环导致,出了几组样例,果然发现了其中的问题,1 1 0这组数据,按照我的思路,第一个1会插入到0的前面,然后是第二个1,此时第一个1已经跑到第二个1的后面,插入完第二个1,又回到了原来状态,这样会死循环,因此我们一定要从第一个偶数节点的后面遍历,而不是从头遍历,这样问题就解决了。

#include <cstdio>
using namespace std;

typedef struct Node{
    int val;
    Node *pre;
    Node *next;
    Node(int val = -1) : val(val), pre(NULL), next(NULL){}
} *pNode;

class List{
private:
    pNode Head;//头指针
    pNode Tail;//尾指针
    pNode First_Even;//第一个偶数节点指针
    pNode First_Odd;//第一个奇数节点指针
    void Insert_Node(pNode);//插入一个节点在第一个偶数节点之前
    void Traverse_List(pNode);
    void Reverse_Travese_List(pNode);
    void Delete_List(pNode);//删除链表
    bool is_Odd(pNode);
public:
    void Insert_Node(int);//在尾部添加节点
    void Traverse_List();//遍历链表
    void Reverse_Travese_List();//反向遍历链表
    void Algorithm();//满足题目算法的具体实现
    List();
    ~List();
};

void List::Insert_Node(int x){
    pNode node = new Node(x);
    if(this->First_Even == NULL && !is_Odd(node))//指向第一个偶数节点的地址
        this->First_Even = node;
    if(this->First_Odd == NULL && this->First_Even != NULL && is_Odd(node))
        this->First_Odd = node;//注意这里一定要加上 this->First_Even != NULL 这句!!
    this->Tail->next = node;
    node->pre = this->Tail;
    this->Tail = node;
}

void List::Insert_Node(pNode node){
    if(node == this->Tail)
        this->Tail = node->pre;
    node->pre->next = node->next;
    if(node->next != NULL)
        node->next->pre = node->pre;
    node->next = this->First_Even;
    node->pre = this->First_Even->pre;
    this->First_Even->pre->next = node;
    this->First_Even->pre = node;
}

void List::Algorithm(){
    if(this->First_Even == NULL || this->First_Odd == NULL)
        return;//只存在一种数字,则直接输出
    pNode p = this->First_Odd;
    pNode psave = NULL;
    while(p){
        psave = p->next;
        if(is_Odd(p)){
            Insert_Node(p);//遍历链表寻找所有奇数节点插入到第一个偶数节点之前
        }
        p = psave;
    }
}

void List::Reverse_Travese_List(){
    Reverse_Travese_List(this->Tail);
}

void List::Reverse_Travese_List(pNode node){
    if(node != this->Head){
        printf("%d ", node->val);
        Reverse_Travese_List(node->pre);
    }
}

void List::Traverse_List(){
    Traverse_List(this->Head->next);
}

void List::Traverse_List(pNode node){
    if(node){
        printf("%d", node->val);
        if(node != this->Tail)
            printf(" ");
        Traverse_List(node->next);
    }
}

bool List::is_Odd(pNode node){
    int x = node->val > 0 ? node->val : -node->val;
    return x % 2 == 1;
}

void List::Delete_List(pNode node){
    if(node){
        Delete_List(node->next);
        delete node;
    }
}

List::List(){
    this->Head = new Node();//表头结点,不参与计算
    this->Tail = this->Head;
    this->First_Even = NULL;
    this->First_Odd = NULL;
}

List::~List(){
    Delete_List(this->Head);
}

int main()
{
    List *list = new List;
    int n;
    scanf("%d", &n);
    while(n--){
        int x;
        scanf("%d", &x);
        list->Insert_Node(x);
    }
    list->Algorithm();
    list->Traverse_List();
    printf("\n");
    // list->Reverse_Travese_List();
    // printf("\n");
    delete list;
    return 0;
}
调整数组顺序使奇数位于偶数前面有多种方法,以下为你介绍几种常见的实现方式: ### 方法一:双指针交换法 使用两个指针,一个从左向右移动,一个从右向左移动。左指针找到偶数,右指针找到奇数时,交换两个指针所指的元素,直到两个指针相遇。以下是 Java 代码示例: ```java public class changeNum { public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5, 6}; rearrange(arr); for (int num : arr) { System.out.print(num + " "); } } public static void rearrange(int[] arr) { int n = arr.length; int left = 0, right = n - 1; while (left < right) { // 找到左边是偶数,右边是奇数时,交换 if (arr[left] % 2 == 0 && arr[right] % 2 != 0) { int temp = arr[left]; arr[left] = arr[right]; arr[right] = temp; left++; right--; } // 移动左指针 if (arr[left] % 2 != 0) { left++; } // 移动右指针 if (arr[right] % 2 == 0) { right--; } } } } ``` 这种方法的时间复杂度是 $O(n)$,空间复杂度是 $O(1)$,但它不会保证奇数偶数内部的相对顺序不变 [^3]。 ### 方法二:双指针遍历法 通过遍历数组,记录奇数的个数,将奇数依次向前移动。以下是 Java 代码示例: ```java public class Solution { public void reOrderArray(int [] array) { int oddNum = 0; for(int i = 0; i < array.length; i++){ if(array[i] % 2 == 1){ int index = i; int tmp = array[i]; while(index > oddNum){ array[index] = array[index - 1]; index--; } array[oddNum] = tmp; oddNum++; } } } } ``` 这种方法可以保证奇数偶数内部的相对顺序不变,时间复杂度是 $O(n^2)$,空间复杂度是 $O(1)$ [^1]。 ### 方法三:使用链表辅助 将奇数偶数分别存放在两个链表中,然后将链表中的元素依次放回原数组。以下是 Java 代码示例: ```java import java.util.LinkedList; public class ReorderArray { public static void reOrderArray(int[] array) { LinkedList<Integer> oddList = new LinkedList<>(); LinkedList<Integer> evenList = new LinkedList<>(); for (int num : array) { if (num % 2 != 0) { oddList.add(num); } else { evenList.add(num); } } int i = 0; for (int num : oddList) { array[i++] = num; } for (int num : evenList) { array[i++] = num; } } } ``` 这种方法的时间复杂度是 $O(n)$,空间复杂度是 $O(n)$,可以保证奇数偶数内部的相对顺序不变 [^2]。 ### 方法四:LeetCode 剑指 Offer 21 解法 使用双指针,一个指针从左向右找偶数,另一个指针从右向左找奇数,找到后交换,直到两个指针相遇。以下是 Java 代码示例: ```java class Solution { public int[] exchange(int[] nums) { if (nums.length == 0) return nums; int l = 0, r = nums.length - 1; do { while (l < nums.length && nums[l] % 2 != 0) l++; //l的边界条件 while (r >= 0 && nums[r] % 2 == 0) r--; //r的边界条件 if (l <= r) { //到这一步代表左右指针对应的值该换了 int t = nums[l]; nums[l] = nums[r]; nums[r] = t; } } while (l <= r); return nums; } } ``` 这种方法的时间复杂度是 $O(n)$,空间复杂度是 $O(1)$,但不保证奇数偶数内部的相对顺序不变 [^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值