剑指offer:Day2

题目1

在这里插入图片描述

解题思路

这道题跟翻转链表比较相似,只不过不需要去修改链表,只需从尾到头去返回每一个节点的值即可

第一种解法:使用栈

栈的特性是先进后出,所以我们完全可以遍历链表,将链表的值存进栈中,然后再从栈中弹出放进数组里面

/**
 * 使用栈来完成倒序
 */
class Solution {

    private Stack<Integer> stack = new Stack<Integer>();

    public int[] reversePrint(ListNode head) {
        int size = 0;
        ListNode h = head;
        while (h != null){
            stack.push(h.val);
            h = h.next;
            size++;
        }
        int[] result = new int[size];

        for (int i = 0; i < result.length; i++) {
            result[i] = stack.pop();
        }
        return result;
    }
}

第二种解法:

第一种解法还需要使用到栈来完成倒序的动作,那为什么不直接从数组中往后存放呢?

可以直接从数组后面往前去存放链表的值,这样就完成了倒序,但不足之处就是需要计算数组的长度,所以需要加多一个长度计算出链表的长度然后得出数组应该初始化的长度

class Solution {
 public int[] reversePrint(ListNode head) {
        ListNode h = head;
        ListNode h2 = head;
        int size = 0;
        while (h != null){
            size++;
            h = h.next;
        }  
        //往后存放进数组里面
        int[] result = new int[size];
        for (int i = result.length-1; i >= 0; i--) {
            result[i] = h2.val;
            h2 = h2.next;
        }
        return result;
    }
}

题目2

在这里插入图片描述

解题思路

解法1

同样可以利用栈的先进后出特性去完成反转,遍历链表,将节点中的值(或者整个节点)存进去栈中,然后通过栈弹出来的就是节点就是翻转后的顺序

class Solution2 {
    public ListNode reverseList(ListNode head) {
        Stack<Integer> stack = new Stack<Integer>();
        ListNode h = head;
        //将链表节点遍历存进去栈中
        while(h != null){
            stack.push(h.val);
            h = h.next;
        }
        if(stack.isEmpty()){
            return null;
        }
        //从栈中弹出节点来进行构建新的链表
        ListNode node = new ListNode(stack.pop());
        ListNode temp = node;
        while (!stack.isEmpty()){
            temp.next = new ListNode(stack.pop());
            temp = temp.next;
        }
        return node;
    }
}

解法2:

使用栈会提高空间复杂度,所以尝试进行原地翻转

首先分析这个链表是只有下一个next指针的,没有pre指针,而原地翻转需要使用到pre指针来让下一个节点的next指向

举个栗子

一开始,current为头节点,而pre为null,同时使用next来记录current.next,这是为了让记录后面的链表,因为翻转后的节点会断开与后面链表的联系,也就是current.next = pre会断开
在这里插入图片描述
第一次循环后,更新next节点为current.next,让current的next指向pre(current.next = pre),并且更新pre为当前的current,因为下一轮循环的pre就是当前轮的current
在这里插入图片描述
第二次循环后
在这里插入图片描述
第三次循环后
在这里插入图片描述
第四次循环后
在这里插入图片描述
代码如下

class Solution {
    public ListNode reverseList(ListNode head) {
        //让当前节点指向前一个节点,h节点相当于current节点
        ListNode h = head;
        //记录上一个节点
        ListNode pre = null;
        //记录下一个节点,
        //因为让当前节点指向前一个节点,所以下一个节点会断开,需要记录下来
        ListNode next;
        while (h != null){
            //如果当前是头节点,让其下一个节点为null
            if(h == head){
                next = h.next;
                h.next = null;
            }
            //下一个节点不为head
            //让其指向上一个节点
            else{
                next = h.next;
                h.next = pre;
            }
            //更新pre节点和h节点
            pre = h;
            h = next;
        }
        return pre;
    }
}

题目3

在这里插入图片描述

解题思路

复杂链表的特殊之处就在于其random形成新的链表,而且这里要求还是一个深拷贝,就是不能使用原有的节点,是完完全全新的一份(使用原有节点LeetCode会报错,本地不会报错)

对链表复制很简单,难就难在怎么维持random形成的链表

那么就分析一下这个random具体有哪几种情况

  1. 当前节点的random是后面的节点,因为后面的节点没有生成,所以我们可以用一个hashMap来保存,key为random指向的节点,value为当前新复制的节点(当后面复制到random指向的节点,我们可以从该hashMap来取出)
  2. 当前节点的random是前面的节点,因为前面的节点已经生成,所以我们还需要使用一个hashMap来存储前面生成的节点,key为原节点,value为复制的节点,这样当我们复制的节点的random是前面的节点时,可以通过key来找到前面生成的复制节点
  3. 当前节点的random是自己,那就直接让复制的节点的random指向自己即可
  4. 当前节点没有random,没有就什么都不需要做

通过分析,我们需要使用到两个HashMap

  • 第一个HashMap存储原节点的random与新复制节点的映射关系,即(old.random,List),为什么要使用List呢?因为可能前面存在多个节点的random都是它,如果不使用List,会出现覆盖情况,导致前面的新复制的节点丢失random
  • 第二个HashMap存储原节点与新复制节点的映射关系,即(old,newCreate)

当我们在复制一个原节点时,先通过第一个HashMap去判断前面有没有节点的random指向它,如果有,那就从第一个HashMap取出random指向它的节点,然后改变random指向当前复制新创建的节点

然后,我们判断当前复制的节点有没有自己的random指向,如果有,判断是属于上述哪种情况,如果是前面的节点,我们可以通过第二个HashMap来获取(注意此时random为key,去寻找random前面复制的新节点),如果是后面的节点,我们就将random和newCreate存放进第一个HashMap,等待后面新创建的节点检测出当前节点需要创建random连接即可

class Solution {
    Map<Node, Node> cachedNode = new HashMap<Node, Node>();
    public Node copyRandomList(Node head) {
        //将链表分为两部分
        //一份就是正常的链表
        //另外一份为random的链表
        //使用hashmap进行映射
        //最后组装起来!
                Node h = head;
        HashMap<Node, List<Node>> randomMap = new HashMap<>();
        HashMap<Node, Node> copyMap = new HashMap<>();
        Node copy = null;
        Node result = null;
        while (h != null){
            Node newNode = new Node(h.val);
            //先从哈希表中看有没有当前复制的节点需不需要被人random连接
            if(randomMap.containsKey(h)){
                //有random需要设置
                List<Node> link = randomMap.get(h);
                for (Node node : link) {
                    node.random = newNode;
                }
            }
            //判断当前节点有没有random,有random就存进去
            if (h.random != null){
                //如果是自己连接自己
                if(h == h.random){
                    newNode.random = newNode;
                }
                //证明random是前面的
                else if(copyMap.containsKey(h.random)){
                    Node link = copyMap.get(h.random);
                    newNode.random = link;
                }
                //没有的话证明是后面的ramdom(需要等后面那个节点设置后,再进行连接)
                else{
                    //出现了替换!!!
                    //当大家都是同一个random时出现了替换
                    //前面的newNode没有保住!
                    if(randomMap.containsKey(h.random)){
                        List<Node> list = randomMap.get(h.random);
                        list.add(newNode);
                    }else{
                        ArrayList<Node> nodes = new ArrayList<>();
                        nodes.add(newNode);
                        randomMap.put(h.random,nodes);
                    }
                    
                }
            }
            if(h == head){
                result = newNode;
                copy = result;
            }
            else{
                result.next = newNode;
                result = result.next;
            }
            //记录此时链表与复制链表的映射关系
            //当新复制进来的节点有random是前面的节点时,从这里选
            copyMap.put(h,newNode);
            h = h.next;
        }
        return copy;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值