Loading

判断一个链表是否为回文结构 & 将单向链表按某值划分成左边小、中间相等、右边大的形式

判断一个链表是否为回文结构

题目:判断一个链表是否为回文结构

进阶:判断一个链表是否为回文结构(进阶)

《程序员代码面试指南》第18题 P55 难度:士★☆☆☆(普通解法)|  尉★★☆☆(进阶解法)

普通解法很简单,我也秒想出来,用来解决。

书上有两种方法,一是将整个链表压入栈,然后再从头遍历,每个节点与弹出的栈顶作比较;

另一种方法是将链表的右半部分压入栈中,然后再从左半部分头结点开逐一始与弹出的栈顶作比较。

对于书上的方法2我有点不理解为什么要多此一举。我的方法是:

将链表的左半部分依次压入栈中,然后从右半部分第一个节点开始,逐一与弹出的栈顶作比较

全部相等,则是回文结构否则不是。这样的话只需要从头开始遍历一次。

(不过可能是因为书上方法2的前提条件是事先不知道总节点数

进阶解法的思路是,将右半部分节点反转,然后2个指针分别从头结点和尾结点同时向中间移动。如果节点值有不相等的就不是回文结构。

需要注意,判断完后还需要将链表恢复原样。(我一开始以为就算这样还是破坏过链表,就否定了这个方法,没想到还真是这样去做(⊙o⊙)…)

普通与进阶解法的题解代码如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Stack;

/**
 * @描述:判断一个链表是否为回文结构
 * @思路:具有两种解法
 * @链接:https://www.nowcoder.com/practice/4b13dff86de64f84ac284e31067b86e2
 */


public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        String n = in.readLine();
        String[] items = in.readLine().split(" ");
        Node head = Node.createNodeList(items);
        System.out.println(IsPalindrom.isPalindrom(head));

    }
}

class IsPalindrom {

    /**
     * 基础解法:
     * 1. 将链表的右半区入栈
     * 2.检查栈中的值和顺序是否与未入栈的一致
     *
     * @复杂度:时间O(N) 空间O(N)
     */
    public static boolean isPalindrom(Node head) {
        //检验
        if (head == null || head.getNext() == null) {
            return false;
        }
        //--找到右半区的头结点
        Node right = head.getNext(); //从第二个结点开始
        Node last = head;
        while (last.getNext() != null && last.getNext().getNext() != null) {
            right = right.getNext(); // right ->中部
            last = last.getNext().getNext(); //cur-->尾部
        }
        //--将链表的右半区入栈
        Stack<Integer> stack = new Stack<Integer>();
        while (right != null) {
            stack.push(right.getValue());
            right = right.getNext();
        }
        //--检查栈中的值和顺序是否与未入栈的一致
        Node node = head;
        while (!stack.isEmpty()) {
            if (stack.pop() != node.getValue()) {
                return false;
            }
            node = node.getNext();
        }
        return true;
    }


    /**
     * 进阶解法:时间O(n),空间O(1)
     * 举例:  1 -> 2 -> 3 -> 4 -> 5
     * 1.改变链表右半区结构:令反转右半区,最后指向链表中间的节点;
     * 即:1 -> 2 -> 3 <- 4 <- 5
     * 2.leftStart和rightStart同时向中间移动,比较相应结点的值,看是否满足回文结构;
     * 3.无论结果如何,都应该将链表恢复成原来的样子,然后在返回检查结果;
     */
    public boolean isPalindrome1(Node head) {
        //校验
        if (head == null || head.next == null) {
            return false;
        }

        //找到中间节点,循环结果:n1->中间节点;  n2->结尾
        Node n1 = head;
        Node n2 = head;
        while (n2.next != null && n2.next.next != null) {
            n1 = n1.next;
            n2 = n2.next.next;
        }

        //反转右半区,循环结果: n1->最后节点;  n2、n3->null
        n2 = n1.next; //右半区的头节点
        n1.next = null; //令mid节点 -> null
        Node n3;
        while (n2 != null) {
            n3 = n2.next;
            n2.next = n1;
            n1 = n2;
            n2 = n3;
        }

        //比较左右半区的值来检查是否符合回文结构,获得结果
        n2 = head;
        n3 = n1;
        boolean res = true;
        while (n1 != null && n2 != null) {
            if (n1.value != n2.value) {
                res = false;
                break;
            }
            n1 = n1.next;
            n2 = n2.next;
        }

        //恢复链表原本的结构
        n2 = n3.next;
        n3.next = null;
        while (n2 != null) {
            n1 = n2.next;
            n2.next = n3;
            n3 = n2;
            n2 = n1;
        }
        return res;
    }


    public static void main(String[] args) {
        Node head = Node.createNodeList(new Integer[]{1, 2, 3, 2, 5});
        System.out.println(IsPalindrom.isPalindrom(head));
    }


}



class Node {

    public Node next;

    public int value;

    public Node(int value) {
        this.value = value;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }


    public static Node createNodeList(Integer[] values) {
        Node head = new Node((values[0]));
        Node node = head;
        for (int i = 1; i < values.length; i++) {
            Node newNode = new Node(values[i]);
            node.next = newNode;
            node = newNode;
        }
        return head;
    }

    public static Node createLoopNodeList(Integer[] values) {
        Node head = new Node((values[0]));
        Node node = head;
        for (int i = 1; i < values.length; i++) {
            Node newNode = new Node(values[i]);
            node.next = newNode;
            node = newNode;
        }
        node.setNext(head);
        return head;
    }

    public static Node createNodeList(String[] values) {
        Node head = new Node(Integer.parseInt(values[0]));
        Node node = head;
        for (int i = 1; i < values.length; i++) {
            Node newNode = new Node(Integer.parseInt(values[i]));
            node.next = newNode;
            node = newNode;
        }
        return head;
    }


    public static void printNodeList(Node head) {
        StringBuilder sb = new StringBuilder();
        while (head != null) {
            sb.append(head.getValue()).append(" ");
            head = head.getNext();
        }
        System.out.println(sb.toString());
    }

    public static void printLoopNodeList(Node head) {
        if (head == head.getNext()) { //只有一个节点
            System.out.println(head.getValue());
        } else {
            StringBuilder sb = new StringBuilder();
            Node last = head;
            while (last.getNext() != head) {
                sb.append(last.getValue()).append(" ");
                last = last.getNext();
            }
            System.out.println(sb.toString());
        }
    }


}

需要学习一下这个确定中间节点的方法(事先不知道总节点数),两个节点从头开始每次分别移动12个,当移动2个的节点移动到最后(奇数倒数第一个节点,偶数倒数第二个节点)时,另一个节点就正好移动到了中间(奇数是正中间,偶数是中间两个节点的左一个节点):

Node n1 = head;
Node n2 = head;
while(n2.next != null && n2.next.next != null) {
  n1 = n1.next;
  n2 = n2.next.next;
}

此时n1再往右移动一个,就到达了右半部分的头结点

将单向链表按某值划分成左边小、中间相等、右边大的形式

题目:将单向链表按某值划分成左边小、中间相等、右边大的形式

《程序员代码面试指南》第19题 P59 难度:尉★★☆☆

本题也有2种解法。普通解法是使用一个额外的数组,所以其额外空间复杂度为O(N)

首先遍历链表,算出总节点数,再创建同长度的数组,将节点依次放入数组

然后,执行各节点的swap过程,将于、pivot的节点通过与其它节点交换,分别放在数组的边和边。

据书上所说,调整过程就是“改进了快速排序中partition的调整过程”,“实现的具体解释请参看本书‘数组类似partition的调整’”。

这个arrPartition方法具体原理还是等刷到排序快排)的题再细看吧,这里留个悬念。

牛客题解代码如下:

import java.io.*;

public class Main {
    static int n, k;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        String[] str = br.readLine().split(" ");
        n = Integer.parseInt(str[0]);
        k = Integer.parseInt(str[1]);
        Node h = new Node(-1);
        Node p = h;
        String[] s1 = br.readLine().split(" ");
        for (int i = 0; i < n; i++) {
            p.next = new Node(Integer.parseInt(s1[i]));
            p = p.next;
        }
        h = h.next;//真实头结点
        h = listPartion(h, k);
        while (h != null) {
            bw.write(h.val + " ");
            h = h.next;
        }
        bw.newLine();
        bw.flush();
    }

    private static Node listPartion(Node h, int k) {
        Node cur = h;
        Node[] arr = new Node[n];
        int i = 0;
        //将链表存到数组
        for (i = 0; i != n; i++) {
            arr[i] = cur;
            cur = cur.next;
        }
        //调整数组
        arrPartion(arr, k);
        //重新连成链表
        for (i = 1; i != n; i++) {
            arr[i - 1].next = arr[i];
        }
        arr[i - 1].next = null;
        return arr[0];
    }

    private static void arrPartion(Node[] arr, int k) {
        int small = -1, big = arr.length;
        int index = 0;
        while (index != big) {
            if (arr[index].val < k) {
                swap(arr, ++small, index++);
            } else if (arr[index].val == k) {
                index++;
            } else {
                swap(arr, --big, index);
            }
        }
    }

    private static void swap(Node[] arr, int i, int j) {
        Node tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}
class Node {
    int val;
    Node next;

    public Node(int val) {
        this.val = val;
    }
}

进阶解法则只使用了有限的几个变量来完成所有的调整,所以额外空间复杂度为O(1)。

主要思路是将原链表依次划分进三个链表,分别为smallequalbig。然后再将三个链表重新串起来即可。

另外需要注意一下,三个链表串起来时,需要对null节点进行合理的判断。

贴上核心代码:

public static Node listPartition2(Node head, int pivot) {
    Node sH = null;
    Node sT = null;
    Node eH = null;
    Node eT = null;
    Node bH = null;
    Node bT = null;
    Node next = null;
    while (head != null) {
        next = head.next;
        head.next = null;
        if (head.val < pivot) {
            if (sH == null) {
                sH = head;
                sT = head;
            } else {
                sT.next = head;
                sT = sT.next;
            }
        } else if (head.val == pivot) {
            if (eH == null) {
                eH = head;
                eT = head;
            } else {
                eT.next = head;
                eT = eT.next;
            }
        } else {
            if (bH == null) {
                bH = head;
                bT = head;
            } else {
                bT.next = head;
                bT = bT.next;
            }
        }
        head = next;
    }

    if (sT != null) {
        sT.next = eH;
        eT = eT == null ? sT : eT;
    }
    if (eT != null) {
        eT.next = bH;
    }
    return sH != null ? sH : eH != null ? eH : bH;
}

(这题我自己的解法特别复杂,看了下进阶解法,感觉自己属实是智障啊┭┮﹏┭┮)

posted @ 2021-11-14 13:11  幻梦翱翔  阅读(44)  评论(0)    收藏  举报