算法入门(十九):链表(下)

82 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

单向链表区域划分问题

image.png

乍一看其实有一些快速排序的感觉,同样要做一个类似partation的过程。

笔试情况

把单链表每一个Node放到数组里面去,然后像之前快速排序一样基于pivot玩partation就可以了,最后再把分割好的数组按照链表串起来就可以了:

image.png

public static Node listPartition1(Node head, int pivot){

    if(head ==null){

        return head;

    }

    Node cur = head;

    int i = 0;

    // 计算链表长度

    while(cur != null){

        i++;

        cur = cur.next;

    }

    Node[] nodeArr = new Node[i];

    i = 0;

    cur = head;

    // 链表存入数组

    for(i = 0; i != nodeArr.length;i++){

        nodeArr[i] = cur;

        cur = cur.next;

    }

    arrPartition(nodeArr,pivot);

    // 重新生成链表

    for (i = 1; i != nodeArr.length; i++){

        nodeArr[i - 1].next = nodeArr[i];

    }

    // 返回head

    return nodeArr[0];

}



public static void arrPartition(Node[] nodeArr,int pivot){

    int small = -1;

    int big = nodeArr.length;

    int index = 0;

    // partition过程,遍历数组根据和基准值比较结果决定当前index是和左侧区域交换还是右侧区域

    while(index != big){

        if(nodeArr[index].value < pivot){

            swap(nodeArr, ++small, index++);

        }else if(nodeArr[index].value == pivot){

            index++;

        }else {

            swap(nodeArr, --big, index);

        }

    }

}

面试情况

这里就需要考虑到链表相对于数组来说,插入和移动的成本比较低,改前面next就行了,所以这里我们基于6个变量可以实现:

  1. 小于区域的头和尾指针

  2. 等于区域的头和尾指针

  3. 大于区域的头和尾指针

来看一下思路:

image.png

image.png

image.png

image.png

首先6个变量初始都为null,遍历链表

第一个是4,<基准5,SH和ST都为null,让SH和ST指向4节点

第二个是6,>基准5,BH和BT都为null,让指向6节点

第三个是3,<基准5,SH和ST不为null,让之前方向的节点的next指向3节点,然后让3变成此时的尾巴ST

第四个是5...

...

第7个是2,<基准5,SH和ST不为null,让之前节点3的next指针指向2,然后让2变成此时的尾巴ST

最后让ST连EH,ET连BH,结束。

说起来简单,但是有很多边界条件需要考虑,尤其是最后三块连接时候,例如如果没有==5的区域呢?

image.png

image.png

关键是最下面的考虑有没有三种区域的方法

复制含有随机指针节点的链表

image.png

image.png

每个节点附带一个random指针指向任意其他节点或者null,怎么拷贝一个和原来一模一样的链表,包括random也一样。

笔试情况

如果允许用额外空间,那么基于哈希表就很简单了,key表示原始Node,value表示克隆Node:

image.png

首先第一轮根据原始链表遍历,只复制node的value,然后生成哈希表。

第二轮遍历原始链表给新链表生成next数据,得到1->next的节点2,然后把这个节点作为key去查哈希表,得到2',然后设置1'的next为2' ...

第三轮遍历原始链表给新链表生成rand数据

public static Node copyListWithRand1(Node head){

    HashMap(Node, Node) map = new HashMap<Node, Node>();

    Node cur = head;

    // 作出克隆节点放入哈希表 - 此时克隆链表的next和rand都是指向原始链表

    while(cur != null){

        map.put(cur, new Node(cur.value));

        cur = cur.next

    }

    cur = head;

    while(cur != null){

        // cur老

        // map.get(cur) 新

        map.get(cur).next = map.get(cur.next);

        map.get(cur).rand = map.get(cur.rand);

        cur = cur.next;

    }

    // 新结构的head

    return map.get(head);

}

面试情况

不用哈希表怎么做呢?这里技术有些秀,简单来说克隆节点直接插入在原始节点的next上:

image.png

这样rand节点非常好找,1的rand是3,那么1'的rand就是3的next 即 3'

image.png

最后在next方向上把新老节点分离开来就可以了,rand节点之前已经保证正确了。

这里就是通过分析思考原先的位置关系来把哈希表的额外空间省掉了,直接在原始链表上面操纵



public static Node copyListWithRand2(Node head){

    if(head == null){

        return null;

    }

    Node cur = head;

    Node next = null;

    // 克隆所有节点和链接关系

    // 1 -> 2  改成 1 -> 1' -> 2

    while(cur != null){

        next = cur.next;

        cur.next = new Node(cur.value);

        cur.next.next = next;

        cur = next;

    }

    cur =head;

    Node curCopy = null;

    // 设置克隆节点的random指针

    // 1->1' ->2 ->2'

    while(cur != null){

        next = cur.next.next;

        curCopy = cur.next;

        curCopy.rand = cur.rand != null ? cur.rand.next : null;

        cur = next;

    }

    Node res = head.next;

    cur = head;

    // 分离克隆节点们

    while(cur != null){

        next = cur.next.next;

        curCopy = cur.next;

        cur.next = next;

        curCopy.next = next != null ? next.next:null;

        cur = next;

    }

    return res;

}