渣渣兔 发表于 2025-1-6 18:34:45

左神算法底子巩固--2

稳固性

稳固性是指算法在排序过程中保持相等元素之间相对次序的特性。具体来说,如果一个排序算法是稳固的,那么对于恣意两个相等的元素,在排序前它们的相对次序与排序后它们的相对次序是相同的。
稳固性对于常见进行数字排序的情况没有意义但对于复杂范例的排序来说是一个特别重要的一件事。
举例说明:我们对于一个班级的全部学生先按照年事进行排序,再按照结果进行排序,如果排序算法具有稳固性的话便会得到一个如果结果相同则年事较小的会排在前面的对象数组。这就是算法稳固性的运用。
前文我们提到的那几种排序算法中并不是全部的算法都具有稳固性的
选择排序

选择选择排序是不具有稳固性的,根据选择排序的算法思路来说,选择排序会先在数组中找到小值并将其与对应位置的数进行交换,故如果出现以下这种情况,那么其就不具有稳固性
https://i-blog.csdnimg.cn/direct/45c01becd22648eabf9ec61b2c0c2a5d.png
冒泡排序

冒泡排序是否具有稳固性得看你在什么时间交换位置,如果相等你不交换二者的位置,那么冒泡排序便是具有稳固性的。以以下数组为例
由冒泡排序的算法头脑得在第一次遍历后0位置上的6会在5位置上只要原0位置上的数不与5位置上的6交换则此时算法是具有稳固性的
https://i-blog.csdnimg.cn/direct/877e223a146c48f782b2c276cfa639b6.png
插入排序

插入排序和冒泡排序雷同,只要我们设定正确的在两数相同的情况下不交换两数的位置,那么此时该算法就是具有稳固性的以以下数组为例
https://i-blog.csdnimg.cn/direct/adeb91341f7c41a289e268535acde658.png
该数组在0-0范围内是有序的,在0-1范围内需要交换位置,交换后数组为2 3 2 … ,在0-2范围内由于2位置上的数与0位置上的数相同只要我们不交换二者的位置,此时该算法具有稳固性
归并排序

归并排序的稳固性取决于其merge的实现,只要我们在merge的时间实现两数相等时先拷贝左侧的数便能实现稳固性
以以下数组为例
https://i-blog.csdnimg.cn/direct/49e9c0e146664089813b3ed805305326.png
在这个数组中1与1相等,只要我们在将数组整理时,先拷贝左侧的1便能实现该算法的稳固性
快速排序

快速排序则是做不到稳固性的,重要原因在于快速排序的partition过程,在partition过程中会将从前今后的第一个小于基准值的数与左界限后的第一个数交换位置,这一步调便破坏了稳固性。
以以下数组为例
https://i-blog.csdnimg.cn/direct/da95dbad9671422d9089d35059287dfd.png
在该数组中找到第一个小于基准数的数为3此时他要与左界限后的第一个数6交换位置此时便破坏了稳固性。
堆排序

堆排序也做不到稳固性,堆排序是以维护自身的堆结构来实现排序的,因此其在筹划之初便完全没有思量稳固性
以以下数组为例
https://i-blog.csdnimg.cn/direct/b0cceab2643645a388bf36010aaa1028.png
在加入这个6后堆排序会去维持其堆结构即将6挂载在第一个4的后面并进行比力由于6比4大,所以6会与第一个4交换位置,此时便破坏了稳固性
下面是算法稳固性的总结
https://i-blog.csdnimg.cn/direct/0b9ab65c6f2d4b40b44f4ea95e45f973.png
一些常见的坑
https://i-blog.csdnimg.cn/direct/2ac0a71fd8884525ab32b704cf305c23.png
注意:这些算法并不是相互分离不可结合的,以选择排序和快速排序为例,虽然选择排序的时间复杂度为n^2快速排序的时间复杂度为nlogn 但是在n比力小的情况下,其实选择排序的所需时间是比快速排序要少的,因此可以进行判断如果n较小便使用选择排序,如果n较大便使用快速排序。
https://i-blog.csdnimg.cn/direct/d129166c52ac466da680d90a5072c22f.png
哈希表

https://i-blog.csdnimg.cn/direct/fec18f6ce8f64c4c99d4bb826d2c6079.png
https://i-blog.csdnimg.cn/direct/61b62bf9c1df4b8fa229c70f9c00d1ec.png
常用的操纵
https://i-blog.csdnimg.cn/direct/b29c0f3241174c77945e47e2730b19db.png
https://i-blog.csdnimg.cn/direct/41a87f9de5ee439cbe4d7341080488b8.png
链表

https://i-blog.csdnimg.cn/direct/399df594cfcd4159877f6b048de41128.png
解题

https://i-blog.csdnimg.cn/direct/3906193325aa474991eecd96a6ec0723.png
笔试思路:先用快慢指针遍历这个链表得到链表的前半部门和后半部门,将这个链表的前半部门加入到一个栈中,在加入后,遍历后半个链表,每遍历一个都与使栈弹出的数进行比力如果两数相等便接着比力,如果栈或链表为空了则说明该链表是回文结构,如果出现不相等的情况则说明该链表不是回文结构。
import java.util.Stack;

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

public class Solution {
    public boolean isPalindrome(ListNode head) {
      if (head == null || head.next == null) {
            return true;
      }

      // 使用快慢指针找到中间节点
      ListNode slow = head;
      ListNode fast = head;
      while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
      }

      // 将前半部分链表的节点值压入栈中
      Stack<Integer> stack = new Stack<>();
      ListNode temp = head;
      while (temp != slow) {
            stack.push(temp.val);
            temp = temp.next;
      }

      // 如果链表长度为奇数,跳过中间节点
      if (fast != null) {
            slow = slow.next;
      }

      // 比较后半部分链表的节点值与栈中的值
      while (slow != null) {
            if (slow.val != stack.pop()) {
                return false;
            }
            slow = slow.next;
      }

      return true;
    }

    public static void main(String[] args) {
      Solution solution = new Solution();
      ListNode head = new ListNode(1);
      head.next = new ListNode(2);
      head.next.next = new ListNode(2);
      head.next.next.next = new ListNode(1);

      boolean isPalindrome = solution.isPalindrome(head);
      System.out.println("链表是否为回文结构: " + isPalindrome);
    }
}
面试思路: 先用快慢指针,使当快指针走到尽头时,慢指针指向中点位置,然后将链表的后半部门反转,并使前半部门和后半部门均向中点靠拢并比力,如果两数相等便接着比力,直到遍历前半部门的指针和遍历后半部门的指针指向中点,就先将后半部门复原后返回true,如果不等就复原后返回false
class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

public class Solution {
    public boolean isPalindrome(ListNode head) {
      if (head == null || head.next == null) {
            return true;
      }

      // 使用快慢指针找到中间节点
      ListNode slow = head;
      ListNode fast = head;
      while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
      }

      // 反转链表的后半部分
      ListNode prev = null;
      ListNode curr = slow;
      while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
      }

      // 比较前半部分和反转后的后半部分
      ListNode left = head;
      ListNode right = prev;
      boolean isPalindrome = true;
      while (right != null) {
            if (left.val != right.val) {
                isPalindrome = false;
                break;
            }
            left = left.next;
            right = right.next;
      }

      // 恢复链表的后半部分
      curr = prev;
      prev = null;
      while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
      }

      return isPalindrome;
    }

    public static void main(String[] args) {
      Solution solution = new Solution();
      ListNode head = new ListNode(1);
      head.next = new ListNode(2);
      head.next.next = new ListNode(2);
      head.next.next.next = new ListNode(1);

      boolean isPalindrome = solution.isPalindrome(head);
      System.out.println("链表是否为回文结构: " + isPalindrome);
    }
}
https://i-blog.csdnimg.cn/direct/6761d3d57bd14ae39d37483a548c2e14.png
笔试思路:遍历链表,将链表的值加入到数组中,对数组进行partition,之后再将用得到的数组构建出一个链表。
class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

public class Solution {
    public ListNode partition(ListNode head, int pivot) {
      if (head == null) return null;

      // 初始化三个指针,分别指向小于、等于、大于pivot的链表头部
      ListNode beforeSmaller = new ListNode(0);
      ListNode beforeEqual = new ListNode(0);
      ListNode beforeGreater = new ListNode(0);

      // 当前节点
      ListNode current = head;
      // 尾指针
      ListNode tailSmaller = beforeSmaller;
      ListNode tailEqual = beforeEqual;
      ListNode tailGreater = beforeGreater;

      while (current != null) {
            if (current.val < pivot) {
                tailSmaller.next = current;
                tailSmaller = tailSmaller.next;
            } else if (current.val == pivot) {
                tailEqual.next = current;
                tailEqual = tailEqual.next;
            } else {
                tailGreater.next = current;
                tailGreater = tailGreater.next;
            }
            current = current.next;
      }

      // 连接三个链表
      tailSmaller.next = beforeEqual.next;
      tailEqual.next = beforeGreater.next;
      tailGreater.next = null;

      // 返回新链表的头节点
      return beforeSmaller.next;
    }

    public static void main(String[] args) {
      Solution solution = new Solution();
      ListNode head = new ListNode(4);
      head.next = new ListNode(3);
      head.next.next = new ListNode(2);
      head.next.next.next = new ListNode(-1);
      head.next.next.next.next = new ListNode(2);
      head.next.next.next.next.next = new ListNode(3);
      head.next.next.next.next.next.next = new ListNode(5);

      int pivot = 2;
      ListNode newHead = solution.partition(head, pivot);

      // 打印结果,用于验证
      while (newHead != null) {
            System.out.print(newHead.val + " ");
            newHead = newHead.next;
      }
    }
}
面试思路:遍历这个链表并根据他们的情况将链表节点加入到新建的三个链表中,遍历完成后,对链表进行拼接便能得到完整的解
class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

public class Solution {
    public ListNode partition(ListNode head, int pivot) {
      // 虚拟头节点,方便操作
      ListNode dummyBeforeSmaller = new ListNode(0);
      ListNode dummyBeforeEqual = new ListNode(0);
      ListNode dummyBeforeGreater = new ListNode(0);
      
      ListNode smaller = dummyBeforeSmaller;
      ListNode equal = dummyBeforeEqual;
      ListNode greater = dummyBeforeGreater;
      
      while (head != null) {
            if (head.val < pivot) {
                smaller.next = head;
                smaller = smaller.next;
            } else if (head.val == pivot) {
                equal.next = head;
                equal = equal.next;
            } else {
                greater.next = head;
                greater = greater.next;
            }
            head = head.next;
      }
      
      // 连接三个链表
      smaller.next = dummyBeforeEqual.next;
      equal.next = dummyBeforeGreater.next;
      greater.next = null;
      
      return dummyBeforeSmaller.next;
    }

    public static void main(String[] args) {
      Solution solution = new Solution();
      ListNode head = new ListNode(4);
      head.next = new ListNode(3);
      head.next.next = new ListNode(2);
      head.next.next.next = new ListNode(-1);
      head.next.next.next.next = new ListNode(2);
      head.next.next.next.next.next = new ListNode(3);
      head.next.next.next.next.next.next = new ListNode(5);
      
      int pivot = 2;
      ListNode newHead = solution.partition(head, pivot);
      
      // 打印结果,用于验证
      while (newHead != null) {
            System.out.print(newHead.val + " ");
            newHead = newHead.next;
      }
    }
}
https://i-blog.csdnimg.cn/direct/df5d5a8b49e448c5a267961a1a5fc918.png
https://i-blog.csdnimg.cn/direct/752124e5bef04efba9625f255023ee85.png
笔试思路:使用hash表将原节点与原节点对应的克隆节点加入到hash表中,其中hash表的key为原节点,value为克隆节点,之后再从原链表中找到对应的next指针对应的节点,将其从hash表中找到对应的克隆节点并赋值给源节点对应克隆节点的next指针,rand指针也是一样不停重复直到全部节点都判断过
class Node {
    int value;
    Node next;
    Node rand;
    Node(int val) {
      value = val;
      next = null;
      rand = null;
    }
}

public class Solution {
    public Node copyRandomList(Node head) {
      if (head == null) return null;

      // Step 1: 创建哈希表存储原始节点和克隆节点的映射
      Map<Node, Node> map = new HashMap<>();

      // Step 2: 遍历原始链表,克隆每个节点并存储在哈希表中
      Node current = head;
      while (current != null) {
            map.put(current, new Node(current.value));
            current = current.next;
      }

      // Step 3: 复制next和rand指针
      current = head;
      while (current != null) {
            Node clone = map.get(current);
            clone.next = map.getOrDefault(current.next, null);
            clone.rand = map.getOrDefault(current.rand, null);
            current = current.next;
      }

      // Step 4: 返回新链表的头节点
      return map.get(head);
    }
}
面试头脑:首先在原链表上克隆每个节点并使原节点的的next指针指向克隆节点,克隆节点的next指针指向原节点的next指针所指节点,之后再遍历原链表节点的next指针和rand指针并在克隆节点上对应指向
class Node {
    int value;
    Node next;
    Node rand;
    Node(int val) {
      value = val;
      next = null;
      rand = null;
    }
}

public class Solution {
    public Node copyRandomList(Node head) {
      if (head == null) return null;

      // 第一步:克隆节点并建立连接
      Node current = head;
      while (current != null) {
            Node clone = new Node(current.value);
            clone.next = current.next;
            current.next = clone;
            current = clone.next;
      }

      // 第二步:复制 rand 指针
      current = head;
      while (current != null) {
            Node clone = current.next;
            if (current.rand != null) {
                clone.rand = current.rand.next;
            }
            current = clone.next;
      }

      // 第三步:分离链表并返回新链表的头节点
      Node newHead = head.next; // 新链表的头节点是原链表第一个节点的克隆节点
      current = head;
      while (current != null) {
            Node clone = current.next;
            current.next = clone.next; // 断开原始节点和克隆节点的连接
            if (clone.next != null) {
                clone.next = clone.next.next; // 跳过原始节点,连接到下一个克隆节点
            }
            current = current.next;
      }

      return newHead;
    }
}
https://i-blog.csdnimg.cn/direct/5a13ad79c47d4ac99908ecbc1ad6b8e9.png
https://i-blog.csdnimg.cn/direct/1982ca193ec34545b6b6ccea6b1f97c4.png
这道题需要直到环中快慢指针的问题,需要明确的是用两个指针其中快指针一次走两步,慢指针一次走一步遍历这个链表当这个链表存在环时,他两一定在环上相遇,同时再将快指针指向第一个节点,再次同时出发,他两一定在入环处相遇。
这道题要分为几种情况进行讨论:
1.两个链表不相交且无环
https://i-blog.csdnimg.cn/direct/0aca88bf7f8540e186c9c8f8895e1e42.png
在这种情况下,我们先用快慢指针知道是否有环后,遍历这两个链表,并记载下每个链表的末了的节点和这个数组的长度,当这两个链表不相交且无环时,这两个链表所对应的末了的节点是不同的。
2.无环但相交
https://i-blog.csdnimg.cn/direct/f280f64120c8412cabd4c90051956028.png
这种情况的判定方法,第一种情况雷同我们先用快慢指针知道是否有环后,遍历这两个链表,并记载下每个链表的末了的节点和这两个链表的长度,并作差值n,当这两个链表相交但无环时,这两个链表所对应的末了的节点是相同的,同时记载下较长的链表让用指针指向其头部并先走n步,之后用指针指向另一链表的头部,两者同时走,两指针相等时便是两指针的相交节点。
之后便是有环结构了,这种情况需要同一分析
https://i-blog.csdnimg.cn/direct/66de009415574ce9ad46d33951414ce4.png
这三种情况的判定条件是入环节点loop1和loop2均不为空判断loop1是否等于loop2,如果相等则为情况2,否则让loop1继续往下走,如果loop1每遇见loop2就是第一种情况,如果遇见了就是情况3。
遇到情况1便可直接返回null,遇到情况二则与前面的无环链表相交范例,但以loop1为链表末了一个节点,遇到情况三则返回loop1或loop2
总结

提示:这里对文章进行总结:
比方:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处置惩罚数据的函数和方法。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 左神算法底子巩固--2