算法通关村第二关——链表反转白银挑战笔记

该部分主要针对链表反转中变式问题进行总结,进一步理解链表反转过程,深化链表反转变式问题的解题思路,巩固链表反转模板,扩充区间链表反转的新模板。

1.指定区间链表反转

区间链表反转问题是链表反转问题的经典变式问题,主要有头插法穿针引线法两大解题策略,其中穿针引线方法是应用链表反转模板,头插法则作为链表反转问题领域的新模板

我们从穿针引线方法入手开始分析,温故知新!

穿针引线法

何谓穿针引线?几块布料缝合在一起形成一块完整的布料就是穿针引线法的形象比喻!

对于指定区间反转问题,我们可以将原有链表切分成三段A、B、C,只需要将B段进行反转,然后将三段缝合,即可完成指定区间链表反转问题!

穿针引线过程中,关键点是什么呢?我猜你已经知道了!A段尾节点pre,B段头节点leftNode,B段尾节点rightNode,C段头节点succ。厘清思路,直接上代码!

    public static ListNode reverseBetween(ListNode head, int left, int right) {
        //找到三段链表pre leftNode rightNode succ
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy, leftNode, rightNode, succ;
        for(int i = 0; i < left - 1; i++){
            pre = pre.next;
        }
        leftNode = pre.next;
        rightNode = pre.next;
        for(int i = left; i < right; i++){
            rightNode = rightNode.next;
        }
        succ = rightNode.next;
        //断链
        pre.next = null;
        rightNode.next = null;
        //反转leftNode和rightNode,模板
        reverseList(leftNode);
        //拼接链表
        pre.next = rightNode;
        leftNode.next = succ;
        return dummy.next;
    }

    //反转链表模板
    public static ListNode reverseList(ListNode head) {
        ListNode pre = null, cur = head, next;
        while(cur != null){
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }

头插法

既然需要将B段进行反转,《算法通关村第二关——链表反转白银挑战笔记》中多次提到头插法,可以将链表进行反转,那我们也采用头插法,遍历B段链表中的每一个元素,将B段链表头插法加入A段链表的表尾,完成链表反转任务!

这么一听,头插法和穿针引线法的思路一样,那为什么还作为第二个方法呢?答:头插法过程中原有链表没有断链!(嗯,穿针引线法是将几段链表缝合起来,头插法没有断链,那确实可以独立作为一种方法)

令人振奋的是,确实是这样,且该方法可作为模板使用!那赶紧分析分析啊,等不及了!别急,慢慢来!

看到pre,cur和next是不是倍感亲切,哪里用到了?答:链表反转模板之不使用虚拟头节点!很遗憾的告诉你,链表反转模板之不使用虚拟头节点的算法过程断链处理,当然不适用于我们这个没有断链的模板啦!

别慌,有救!看我分析!从特殊到一般,初始B段链表如上图,首先将链表的前两个元素进行反转,顺序由12变成了21,我们可以发现,next节点头插法接入pre节点之后。

一般情况,B段链表部分反转,比如原来1234部分发生反转变成2134,即pre2134,下一步需要将3头插法接入pre节点之后,使其变换成pre3214。那么,规律就可以总结了!

首先,将待反转节点做断链处理,然后,将断链的节点头插法插入pre节点之后,依次循环。那么,待反转节点到底是谁?是cur还是next?答:只能是next,不然会cur后继节点会发生断链!

厘清思路,直接上代码!

    public static ListNode reverseBetween2(ListNode head, int left, int right) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy, cur, next;
        //找到left的前驱节点
        for(int i = 0; i < left -1; i++){
            pre = pre.next;
        }
        cur = pre.next;
        //反转链表
        //先取出取出next节点,然后头插
        for(int i = left; i < right; i++){
            next = cur.next;
            cur.next = next.next;
            next.next = pre.next;
            pre.next = next;
        }
        return dummy.next;
    }

2.两两交换链表中的节点

两两交换链表中节点,不就是对区间长度为2的子链表进行翻转嘛?以上两个方法都可以解决!

那我给你改改变量的名字,看看你还会不会刚才的模板,会不会做变通呢?怎么做,取出node2,然后将node2头插法加入cur后面完成node2和node1的反转,反转完成后,需要完成后面节点的反转,因此让cur指向node1,此时node1重新指向3,node2重新指向4。

废话不多说,直接上代码!(赶紧巩固刚刚学的模板!

    public static  ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode cur = dummy, node1, node2;
        while(cur.next != null && cur.next.next != null){
            node1 = cur.next;
            node2 = cur.next.next;
            node1.next = node2.next;
            node2.next = cur.next;
            cur.next = node2;
            cur = node1;
        }
        return dummy.next;
    }

3.单链表加1(变式问题)

题目LeetCode369其描述为,一个数字采用链表进行存储,高位数字存储在链表表头,低位数字存储在链表的表尾,将该数字加1,按照同样的规则将加1后的数字用链表进行存储。

题目分析,很显然,加法需要从个位开始计算,然后计算十位、百位,依次...这个过程就是从链表表尾开始向表头方向进行计算的呀。嗯,很明显的逆向过程,什么数据结构?答:栈!

第一种思路清晰明了,但是逆向过程,还有一个方法吧,头插法,什么领域?答:链表反转!第二种思路呼之欲出!剩下的只有一点点细节处理,是先进位,还是先计算?代码如下!

栈实现!

    public static ListNode plusOne1(ListNode head) {
        Stack<ListNode> stack = new Stack<>();
        ListNode cur = head;
        ListNode dummy = new ListNode(-1);
        while(cur != null){
            stack.push(cur);
            cur = cur.next;
        }
        stack.peek().val += 1;
        int increase = 0;//进位
        int val = 0;
        while(!stack.empty()){
            val = (stack.peek().val + increase) % 10;
            increase = (stack.peek().val + increase) / 10;
            stack.peek().val = val;
            cur = stack.pop();
            cur.next = dummy.next;
            dummy.next = cur;
        }
        //最后是否还有进位情况
        if(increase != 0){
            ListNode increaseNode = new ListNode(increase);
            increaseNode.next = dummy.next;
            dummy.next = increaseNode;
        }
        return dummy.next;
    }

链表反转实现!

    public static ListNode plusOne2(ListNode head) {
        head = reverseList(head);
        ListNode cur = head;
        cur.val += 1;
        int increase = 0;
        int val = 0;
        ListNode tail = null;
        while(cur != null){
            val = (cur.val + increase) % 10;
            increase = (cur.val + increase) / 10;
            cur.val = val;
            if(cur.next == null){
                tail = cur;
            }
            cur = cur.next;
        }
        if(increase != 0){
            ListNode increaseNode = new ListNode(increase);
            tail.next = increaseNode;
        }
        return reverseList(head);
    }

4.链表加法(变式问题)

什么,刚才那题太简单了?行行行,再给你出一题!(地上qi个猴,树上qi个猴,一共几个猴?)正经点,算法训练呢!题目描述LeetCode445。

看完题目描述,paper tiger直接上代码!(还不如刚才那个题目呢!)

    public static ListNode addInListByReverse(ListNode head1, ListNode head2) {
        head1 = reverse(head1);
        head2 = reverse(head2);
        int increase = 0;
        int val = 0;
        ListNode cur1 = head1, cur2 = head2, tail = null;
        while(cur1 != null && cur2 != null){
            val = (cur1.val + cur2.val + increase) % 10;
            increase = (cur1.val + cur2.val + increase) / 10;
            cur1.val = val;
            if(cur1.next == null || cur2.next == null){
                tail = cur1;
            }
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        tail.next = cur1 == null ? cur2 : cur1;
        while(cur1 != null){
            val = (cur1.val + increase) % 10;
            increase = (cur1.val + increase) / 10;
            cur1.val = val;
            if(cur1.next == null){
                tail = cur1;
            }
            cur1 = cur1.next;
        }
        while(cur2 != null){
            val = (cur2.val + increase) % 10;
            increase = (cur2.val + increase) / 10;
            cur2.val = val;
            if(cur2.next == null){
                tail = cur2;
            }
            cur2 = cur2.next;
        }
        if(increase != 0){
            ListNode increaseNode = new ListNode(increase);
            tail.next = increaseNode;
        }
        return reverse(head1);
    }

5.回头看回文序列

回文序列前面介绍的方法是采用进行处理的。现在我们可以放心大胆的使用链表反转来处理回文序列了!

回文序列关键点在于,找到中间节点反转一半链表,比较即可!那么fast、slow、pre、cur、next这些老朋友又要出来干活咯!能不能少叫几个老朋友啊,行吧,少叫一个next,由于slow移动的时候slow = slow.next。因此slow扮演slow和next两个角色。直接上代码!

奇数节点时,如12321 slow指向3,此时链表变成pre指向的21和slow指向的321,因此,slow需要向后移动一步之后再做比较!

    public static boolean isPalindromeByReverse(ListNode head) {
        //slow起到slow和next两个作用
        ListNode slow = head, fast = head;
        ListNode pre = null, cur = head;
        while(fast != null && fast.next != null){
            cur = slow;
            slow = slow.next;
            fast = fast.next.next;
            cur.next = pre;
            pre = cur;
        }
        //奇数节点情况
        if(fast != null){
            slow = slow.next;
        }
        while(slow != null && pre != null){
            if(slow.val != pre.val){
                return false;
            }
            slow = slow.next;
            pre = pre.next;
        }
        return true;
    }

Ok,《算法通关村第二关——链表反转白银挑战笔记》结束,喜欢的朋友三联加关注!关注鱼市带给你不一样的算法小感悟!(幻听)

再次,感谢鱼骨头教官的学习路线!鱼皮的宣传!小y的陪伴!ok,拜拜,第二关第三幕见!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
哈希表是一种高效的数据结构,可以用来存储和查找键值对。其中,哈希函数将键映射到一个特定的桶中,每个桶中存储一组键值对。在哈希表中,如果两个键被映射到同一个桶中,就会发生碰撞。为了解决这个问题,可以使用链表法。 链表法是一种解决哈希表碰撞问题的方法。具体来说,对于哈希表中的每个桶,可以使用一个链表来存储所有映射到该桶的键值对。如果发生碰撞,只需要将新的键值对添加到链表的末尾即可。 下面是一个使用链表法实现哈希表的示例代码: ```python class Node: def __init__(self, key, value): self.key = key self.value = value self.next = None class HashTable: def __init__(self, capacity): self.capacity = capacity self.buckets = [None] * capacity def hash_function(self, key): return hash(key) % self.capacity def put(self, key, value): index = self.hash_function(key) node = self.buckets[index] while node: if node.key == key: node.value = value return node = node.next new_node = Node(key, value) new_node.next = self.buckets[index] self.buckets[index] = new_node def get(self, key): index = self.hash_function(key) node = self.buckets[index] while node: if node.key == key: return node.value node = node.next return None def remove(self, key): index = self.hash_function(key) node = self.buckets[index] prev = None while node: if node.key == key: if prev: prev.next = node.next else: self.buckets[index] = node.next return prev = node node = node.next ``` 在这个示例中,我们定义了一个Node类来表示哈希表中的每个节点,每个节点包含一个键、一个值和一个指向下一个节点的指针。我们还定义了一个HashTable类来实现哈希表,其中包含一个桶数组和一些基本的操作方法,如put、get和remove。 在put方法中,我们首先使用哈希函数计算出键的索引,然后遍历桶中的链表,查找该键是否已经存在于哈希表中。如果找到了该键,我们只需要更新其对应的值即可。否则,我们创建一个新的节点,并将其添加到链表的开头。 在get方法中,我们同样使用哈希函数计算出键的索引,然后遍历桶中的链表,查找该键的值。如果找到了该键,我们返回其对应的值。否则,返回None。 在remove方法中,我们首先使用哈希函数计算出键的索引,然后遍历桶中的链表,查找该键。如果找到了该键,我们将其从链表中删除即可。 总的来说,链表法是一种简单且常用的哈希表解决碰撞问题的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值