LeetCode(剑指offer) DAY2

1.题目 从尾到头打印链表

解法一:先统计链表有多少节点,然后创建数组,再次遍历链表将值存储至数组,然后反转数组。这种解法思路简单,但是时间复杂度较高。

class Solution {
    int a = 0,b=0;
    public int[] reversePrint(ListNode head) {
        ListNode first = head;
        while (head != null) {
            head = head.next;
            a++;
        }
        int[] arr = new int[a];
        while (first!= null) {
            arr[b] = first.val;
            first = first.next;
            b++;
        }
        for (int j = 0; j < arr.length/2; j++) {
            int temp = arr[j];
            arr[j] = arr[arr.length - j - 1];
            arr[arr.length - j - 1] = temp;

        }
        return arr;
    }
}

 解法二:利用递归,先走至链表末端,回溯时依次将节点值加入列表 ,这样就可以实现链表值的倒序输出。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    int a = 0, b = 0;
    int[] arr = null;
    public int[] reversePrint(ListNode head) {
        flag(head);
        return arr;
    }
    public void flag(ListNode node) {
        if (node == null) {
            arr = new int[a];
            return;
        }
        a++;
        flag(node.next);
        arr[b] = node.val;
        b++;
    }
}

解法三: 题目要求倒序输出节点值,这种先入后出的需求可以借助栈来实现。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    
    public int[] reversePrint(ListNode head) {
       Stack<Integer> s = new Stack<>();
        while (head != null) {
            s.push(head.val);
            head = head.next;
        }
        int[] arr = new int[s.size()];
        for (int j = 0; j < arr.length; j++) {
            arr[j] = s.pop();
        }
        return arr;
    }
}

2.题目二:反转链表

解法一:

在遍历链表时,将当前节点的 next\textit{next}next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    ListNode last,current,next=null;
    public ListNode reverseList(ListNode head) {
        current=head;
        while(current!=null){
            next=current.next;//保存当前节点指向的下一个节点的指针
            current.next=last;//当前节点指针反转,使当前节点的指针指向前一个节点
            last=current;//移动指针:当前节点为前一个节点
            current=next;//移动指针:下一个节点为当前节点
        }
        return last;
    }
}

解法二:使用递归的方法,这种解法思路不易想到

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

解法三:利用栈先进后出的特点,反转链表,这种解法思路简单,但是算法时空复杂度都较高。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head==null){//若不判断为空,可能会引起栈为空而继续出栈的情况,后面代码报错
            return null;
        }
        Stack<ListNode> stack=new Stack<>();
        while(head!=null){
            stack.push(head);
            head=head.next;
        }
        ListNode flag=stack.pop();//弹出新的头结点作为已反转链表,其中flag为该反转链表的尾节点
        ListNode newHead=flag;//复制一份头结点用以保存
        ListNode current=null;
        while(!stack.isEmpty()){
            current=stack.pop();
            flag.next=current;//将当前节点链接到反转链表的尾节点,成为反转链表的一个成员
            current.next=null;//断掉当前节点与其他节点的链接
            flag=current;//将当前节点作为新的反转链表的尾节点
        }
        return newHead;
    }
}

3.题目三 复杂链表的复制

解法一:该解法没有太多的思路技巧,比较容易想到,但是算法的时空复杂度都比较差。具体思路是:先将原链表每一个节点一一添加到ArrayList中 

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        ArrayList<Node> oldList = new ArrayList<>();
        while (head != null) {
            oldList.add(head);
            head = head.next;
        }
        int size=oldList.size();
        ArrayList<Node> newList = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            newList.add(new Node(oldList.get(i).val));
        }
        for (int j = 0; j < size; j++) {
            newList.get(j).random =oldList.get(j).random == null ? null
                    : newList.get(oldList.indexOf(oldList.get(j).random));
            if (j != newList.size() - 1) {
                newList.get(j).next = newList.get(j + 1);
            }
        }
        return newList.get(0);
    }
}

解法二:这是官方给出的思路。我们利用回溯的方式,让每个节点的拷贝操作相互独立。对于当前节点,我们首先要进行拷贝,然后我们进行「当前节点的后继节点」和「当前节点的随机指针指向的节点」拷贝,拷贝完成后将创建的新节点的指针返回,即可完成当前节点的两指针的赋值。具体地,我们用哈希表记录每一个节点对应新节点的创建情况。遍历该链表的过程中,我们检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。在实际代码中,我们需要特别判断给定节点为空节点的情况。

class Solution {
    private Map<Node,Node> map = new HashMap<>();
    public Node copyRandomList(Node head) {
        // 终止条件next给过来的node为null;
        if(head == null){
            return null;
        }
        // 中间处理方法
        // 1. 递归创建节点到最后一个节点,并吧所有创建的节点保存在map中;
        Node newNode = new Node(head.val);
        map.put(head,newNode);
        newNode.next = copyRandomList(head.next);
        // 2. 因为递归过程中会创建所有的节点,所以回溯过程中直接进行newNode.random赋值
        newNode.random = map.get(head.random);
        return newNode;
    }
}

解法三:这是官方给出的思路。我们首先将该链表中每一个节点拆分为两个相连的节点,例如对于链表 A→B→C,我们可以将其拆分为 A→A′→B→B′→C→C′ 。对于任意一个原节点 S,其拷贝节点 S′ 即为其后继节点。这样,我们可以直接找到每一个拷贝节点 S′的随机指针应当指向的节点,即为其原节点 S的随机指针指向的节点 T 的后继节点 T′。需要注意原节点的随机指针可能为空,我们需要特别判断这种情况。当我们完成了拷贝节点的随机指针的赋值,我们只需要将这个链表按照原节点与拷贝节点的种类进行拆分即可,只需要遍历一次。同样需要注意最后一个拷贝节点的后继节点为空,我们需要特别判断这种情况。

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    /*
    原地拼接+拆分:
    1.构建"旧->新->旧->新"交替的链表
    2.构建新链表的random指向
    3.将链表拆分恢复现场
    */
    public Node copyRandomList(Node head) {
        // 阴间案例
        if(head == null) {
            return null;
        }
        // 1.创建新旧节点交替的链表
        Node cur = head;
        while(cur != null) {
            // 创建新的链表节点
            Node tmp = new Node(cur.val);
            // tmp右边连接
            tmp.next = cur.next;
            // tmp左边连接
            cur.next = tmp;
            // cur指针移动
            cur = tmp.next;
        }
        // 2.构建新链表的random指向
        // cur指针指向旧链表头结点
        cur = head;
        while(cur != null) {
            // 新的链表random指向旧的链表的random的下一个
            // 这里记住要判断一下cur.random是否为空,防止空指针
            // 如示例1:head.random==null,因此head.next也指向null,也就是默认值
            if(cur.random != null) {
                cur.next.random = cur.random.next;
            }
            // cur后移两位
            cur = cur.next.next;
        }
        // 3.将链表拆分恢复现场
        // pre指向旧链表的节点,res存储新链表头结点
        Node pre = head, res = head.next;
        // cur指针指向新链表的节点
        cur = head.next;
        // 这里进入循环的条件是cur.next!=null,表明不是最后一个cur,也防止了下面空指针
        while(cur.next != null) {
            // 这里注意顺序:防止目标丢失
            pre.next = pre.next.next;
            cur.next = cur.next.next;
            // pre和cur指针各移动一位即可,因为已经改变指向
            pre = pre.next;
            cur = cur.next;
        }
        // 最后一个pre的指针还粘着cur(画图看看就知道),需要手动去除一下
        pre.next = null;
        // 返回新链表头结点
        return res;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

城南皮卡丘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值