编程导航算法通关村第1关|白银教程学习总结

刷算法没有思路怎么办 ?
把常见的数据结构和算法思想过一遍。

常用的数据结构有数组、链表、队、栈、Hash、集合、树、堆。
常用的算法思想有查找、排序、双指针、递归、迭代、分治、贪心、回溯和动态规划等等。

两个链表的第一个公共子节点

剑指offer52

一定要好好读题目 !

剑指offer52

使用集合方法

遍历其中一个链表, 放入到 Set 、Hash 中, 在边遍历便从集合之中取出元素进行比较

/**
     * 方法1:通过Hash辅助查找
     *
     * @param pHead1
     * @param pHead2
     * @return
     */
    public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        ListNode current1 = pHead1;
        ListNode current2 = pHead2;
        Map<ListNode, Integer> map = new HashMap<>();

        while (current1 != null) {
            map.put(current1, null);
            current1 = current1.next;
        }

        while (current2 != null) {
            if (map.containsKey(current2)) {
                return current2;
            }
            current2 = current2.next;
        }

        return null;
    }

 /**
     * 方法2:通过集合来辅助查找
     *
     * @param headA
     * @param headB
     * @return
     */
    public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
        Set<ListNode> set = new HashSet<>();

        while (headA != null) {
            set.add(headA);
            headA = headA.next;
        }

        while (headB != null) {
            if (set.contains(headB)) {
                return headB;
            }
            headB = headB.next;
	 }

使用栈

不太了解栈的,可以看看 hello算法的介绍(里面也有相关方法的介绍)

最主要的特征就是先进后出 (PS 像极了我食堂买饭🤣)

stackA.peek() == stackB.peek() 为什么需要满足这个条件才能取出栈顶元素 ?

原因 : 假设存在公共节点,那么最后面的一定相同的系节点!如果不是相同的系节点那么就不存在公共节点,互相矛盾了。当条件村成立时,就表示找到了第一个开始相同的子节点,直接返回即可

在这里插入图片描述

/**
     * 方法3:通过栈
     */
    public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
        Stack<ListNode> stackA = new Stack();
        Stack<ListNode> stackB = new Stack();

        while (headA != null) {
            stackA.push(headA);
            headA = headA.next;
        }

        while (headB != null) {
            stackB.push(headB);
            headB = headB.next;
        }

        ListNode node = null;

        while ((stackA.size() > 0) && (stackB.size() > 0)) {
        // 注意这里时 peek() 方法而不是 pop() 
        // peek() 只是访问栈顶元素而不取出
            if (stackA.peek() == stackB.peek()) {
                node = stackA.pop();
                stackB.pop();
            } else {
                // 返回
                break;
            }
        }
        return node;
    }

通过序列拼接

在这里插入图片描述
相同的部分经过组合之后,一定会出现在后面!
在这里插入图片描述

 	/**
     * 方法4:通过序列拼接
     */
    public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;

        while (p1 != p2) {
            p1 = p1.next;
            p2 = p2.next;

            if (p1 != p2) {
                if (p1 == null) {
                    p1 = pHead2;
                }

                if (p2 == null) {
                    p2 = pHead1;
                }
            }
        }
        return p1;
    }

通过差值实现

为什么公共子节点,不会在某一个长的链表前面?

应为你没读题😢(ps 说我自己)。如果出现在前面,那么两个链表的长度一定不可能不相同 !

    /**
     * 方法5:通过差值来实现
     *
     * @param pHead1
     * @param pHead2
     * @return
     */
    public static ListNode findFirstCommonNodeBySub(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        ListNode current1 = pHead1;
        ListNode current2 = pHead2;
        int l1 = 0, l2 = 0;
        while (current1 != null) {
            current1 = current1.next;
            l1++;
        }

        while (current2 != null) {
            current2 = current2.next;
            l2++;
        }
        current1 = pHead1;
        current2 = pHead2;
		// 这里还可以使用相关方法,比如去绝对值
        int sub = l1 > l2 ? l1 - l2 : l2 - l1;

        if (l1 > l2) {
            int a = 0;
            while (a < sub) {
                current1 = current1.next;
                a++;
            }
        }

        if (l1 < l2) {
            int a = 0;
            while (a < sub) {
                current2 = current2.next;
                a++;
            }
        }

        while (current2 != current1) {
            current2 = current2.next;
            current1 = current1.next;
        }

        return current1;
    }

判断回文字符序列

题目 LeetCode234

在这里插入图片描述

思路:

  1. 逃避链表,使用数组 (非常不推荐)
  2. 使用栈,利用先进后出的特性
  3. 优化思路,遍历一半就可以判断了
    /**
     * 使用栈进行判断是否为回文链表
     */
    public boolean isPalindrome(ListNode head) {
        // 创建一个 栈
        Stack<ListNode> stack = new Stack<>();
        ListNode current = head;
        while (current != null) {
            stack.push(current);
            current = current.next;
        }

        current = head;
        while (current != null) {
            if (current.val != stack.pop().val) {
                return false;
            }
            current = current.next;
        }
        return true;
    }

实现优化

    /**
     * 方法3:只将一半的数据压栈
     *
     * @param head
     * @return
     */
    public static boolean palindromeByHalfStack(ListNode head) {
        if (head == null) {
            return true;
        }
        Stack<ListNode> stack = new Stack<>();
        int len = 0;
        ListNode current = head;
        // 把链表节点的值存放到栈中
        while (current != null) {
            stack.push(current);
            current = current.next;
            len++;
        }
        // 除以 2
        len >>= 1;
        current = head;
        while (len-- > 0) {
            if (stack.pop().val != current.val) {
                return false;
            }
            current = current.next;
        }
        return true;
    }

合并有序链表

合并两个有序链表

LeetCod21 将两个升序链表合并为一个新的升序链表并返回,新链表是通过拼接给定的两个链表的所有节点组成的。

    public static ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        // write code here
        ListNode newHead = new ListNode(-1);
        ListNode res = newHead;
        while (list1 != null || list2 != null) {

            if (list1 != null && list2 != null) {//都不为空的情况
                if (list1.val < list2.val) {
                    newHead.next = list1;
                    list1 = list1.next;
                } else if (list1.val > list2.val) {
                    newHead.next = list2;
                    list2 = list2.next;
                } else { //相等的情况,分别接两个链
                    newHead.next = list2;
                    list2 = list2.next;
                    newHead = newHead.next;
                    newHead.next = list1;
                    list1 = list1.next;
                }
                // 公共部分提取出来
                newHead = newHead.next;
            } else if (list1 != null && list2 == null) {
                newHead.next = list1;
                list1 = list1.next;
                newHead = newHead.next;
            } else if (list1 == null && list2 != null) {
                newHead.next = list2;
                list2 = list2.next;
                newHead = newHead.next;
            }
        }
        return res.next;
    }

优化一下代码

public static ListNode mergeTwoLists2(ListNode list1, ListNode list2) {
        // write code here
        ListNode newHead = new ListNode(-1);
        ListNode res = newHead;
        while (list1 != null && list2 != null) {
            if (list1.val > list2.val) {
                res.next = list2;
                list2 = list2.next;
            } else if (list2.val > list1.val) {
                res.next = list1;
                list1 = list1.next;
            } else {
                // list2.val == list1.val
                res.next = list1;
                res.next = list2;
                list1 = list1.next;
                list2 = list2.next;
            }
            res = res.next;
        }

        while (list1 != null && list2 == null) {
            res.next = list1;
            list1 = list1.next;
            res = res.next;
        }
        while (list1 == null && list2 != null) {
            res.next = list2;
            list2 = list2.next;
            res = res.next;
        }

        return newHead.next;
    }


合并两个链表

1669. 合并两个链表
给你两个链表 list1list2 ,它们包含的元素分别为 n 个和 m 个。
请你将 list1 中下标从 ab 的全部节点都删除,并将list2 接在被删除节点的位置。

在这里插入图片描述

public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
        // 注意这边有两个指向的是同一个人链表
        ListNode pre1 = list1, post1 = list1, post2 = list2;
        int i = 0, j = 0;
        while (pre1 != null && post1 != null && j < b) {
            if (i < a - 1) {
                pre1 = pre1.next;
                i++;
            }

            if (j != b) {
                post1 = post1.next;
                j++;
            }
        }
        
        while (post2.next != null) {
            post2 = post2.next;
        }

        // 连接相关的节点
        // 下面有图片解析
        pre1.next = list2;
        post2.next = post1.next;

        return list1;
    }

双指针

寻找中间节点

LeetCode876

给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

定义连个指针, 一个 slow 另一个是 fast

  public static ListNode middleNode(ListNode head) {
        ListNode slow = head, fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

寻找倒数第K个元素

剑指 Offer 22. 链表中倒数第k个节点

需要提前把 fast 指针指向第 k + 1 位置, slow 指针就在第一个位置就 ok。当 fast 到达了 k + 1 位置,那么两个指针就一起向后遍历

ps 也可以到 第 k 个位置,那么后面 fast 指针需要到达的位置就是 最后以一个元素

在这里插入图片描述

结束时两个指针的位置

在这里插入图片描述

public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fast = head;
        ListNode slow = head;
        // 这里有可能会出现 k 大于 链表长度的情况
        while (fast != null && k > 0) {
            fast = fast.next;
            k--;
        }

        while (fast != null) {
            slow = slow.next;
            fast = fast.next;
        }

        return slow;
    }

反转链表

双指针,需要执行到图示位置。

在这里插入图片描述

    public ListNode rotateRight(ListNode head, int k) {
        if (head == null || k == 0) {
            return head;
        }
        ListNode temp = head;
        ListNode fast = head;
        ListNode slow = head;
        int len = 0;

        while (head != null) {
            head = head.next;
            len++;
        }
		// 如果这个条件成立那么就表示不需要进行反转,比如 k = 0 或这 k = len  ....
        if (k % len == 0) {
            return temp;
        }

        while ((k % len) > 0) {
            fast = fast.next;
            k--;
        }
        
        // 之后 fast 来到了第 k + 1 的位置
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }

        // 两个指针都到了该到的位置
        ListNode res = slow.next;
        slow.next = null;
        fast.next = temp;
        return res;
    }

删除链表元素专题

删除特定结点

删除的位置无非就是 链表的头节点,链表的其他位置节点

  1. 对于 头节点 来说, 由于其特殊性(没了整个链表都没了)所以需要一个虚拟的头节点 dummyHead
  2. 对于其他位置的节点,直接操作即可

在这里插入图片描述
题目: 203. 移除链表元素

 public ListNode removeElements(ListNode head, int val) {
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        ListNode current = dummyHead;
        
        while (current.next != null) {
            if (current.next.val == val) {
                current.next = current.next.next;
            } else {
                current = current.next;
            }
        }

        return dummyHead.next;
    }
  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值