算法-链表基础结构算法

目录

offer25合并两个排序的链表

23合并K个升序列表

Offer22 链表中倒数第k个节点

 19删除链表的倒数第N个节点​编辑 

Offer06从尾到头打印链表

Offer18删除链表的结点

Offer24反转链表

25K个一组翻转链表

Offer35复杂链表的复制

2两数相加

141环形链表

142环形链表2

148排序链表

OfferII27回文链表

 对比数组类型的归并排序

 86分割链表​编辑 


offer25合并两个排序的链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
      public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummyHead=new ListNode(-1);
        ListNode tail=dummyHead;
        while (l1!=null&&l2!=null){
            if (l1.val>l2.val){
                tail.next=l2;
                l2=l2.next;
                tail=tail.next;
            }else {
               tail.next=l1;
               l1=l1.next;
               tail=tail.next;
            }
        }
        if (l1==null){
            tail.next=l2;
        }
        if (l2==null){
            tail.next=l1;
        }
        return dummyHead.next;
    }


}

23合并K个升序列表

  • 这里我们就是想找到n个链表中最小的一个数,加入到里面,一直到加完,我们可以利用优先级队列,Java提供了最小堆,我们把每个链表的头节点放进去,则堆头的数就是链表所有链表中最小的那个数,然后在将这个链表的下一个数加入,一直取到这个优先级队列为空
  • 优先级队列要求我们的元素必须是有排序规则的,所以我们传入一个comparator 
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
            public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length==0) return null;
        ListNode dummyHead=new ListNode(-1);
        ListNode tail=dummyHead;
        PriorityQueue<ListNode> minHeap=new PriorityQueue<>(lists.length, new Comparator<ListNode>() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                return o1.val- o2.val;
            }
        });
        for (ListNode head:lists) {
            if (head!=null){
                minHeap.add(head);
            }
        }
        while (!minHeap.isEmpty()){
            ListNode node=minHeap.poll();
            tail.next=node;
            tail=tail.next;
            if (node.next!=null){
                minHeap.add(node.next);
            }
        }
        return dummyHead.next;
    }


}

Offer22 链表中倒数第k个节点

  • 利用快慢指针,让快指针先走k步,然后让两个指针一起走,等快指针走完,慢指针就刚刚走到倒数第k个结点

public class Offer22 {
    /**
     * 利用快慢指针
     * @param head
     * @param k
     * @return
     */
    public ListNode getKthFromEnd(ListNode head, int k) {
        if (head==null){
            return null;
        }
        ListNode dummyHead=new ListNode(-1);
        dummyHead.next=head;
        ListNode node=dummyHead.next;
        ListNode node1=dummyHead.next;
        int i=0;
        while (i<k){
            node=node.next;
            i++;
        }
        while (node!=null){
            node1=node1.next;
            node=node.next;
        }
        return node1;
    }
}

  

 19删除链表的倒数第N个节点 

  • 思路,先用快慢指针来定位要删除的指针,再来一个指针来指向删除结点的前驱节点
  • 就是offer22题加一个前驱结点的结合

class Solution {
          public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead=new ListNode(-1);
        dummyHead.next=head;
        ListNode prev=dummyHead;
        ListNode low=dummyHead.next;
        ListNode fast=dummyHead.next;
        int i=0;
        while (i<n){
            fast=fast.next;
            i++;
        }
        while (fast!=null){
            fast=fast.next;
            low=low.next;
            prev=prev.next;
        }
        prev.next=low.next;
        low=low.next=null;
        return dummyHead.next;
    }



}

Offer06从尾到头打印链表

  • 利用递归先走到链表的结尾,然后再开始添加元素,也就是在所谓的后序位置操作,来添加元素,如果想按顺序添加,就在前序位置去进行添加元素的操作 
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    int []arr=null;
    int index=0;
    public int[] reversePrint(ListNode head) {
        helper(head,0);
        return arr;
    }
    public void helper(ListNode head,int length){
        if (head==null){
            arr=new int[length];
            return;
        }
        helper(head.next,length+1);
        arr[index++]=head.val;
    }
}

Offer18删除链表的结点

  • 最重要的就是要找到删除节点的前驱节点,如果没有虚拟头节点,那么需要去区分头节点和其他节点,如果有虚拟头节点就不需要
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
        public ListNode deleteNode(ListNode head, int val) {
        ListNode dummyHead=new ListNode(-1);
        dummyHead.next=head;
        ListNode prev=dummyHead;//前驱结点
        while (prev.next!=null){
            ListNode node=prev.next;
            if (node.val==val){
                prev.next=node.next;
                node=node.next=null;
                break;
            }else {
                prev=prev.next;
            }
        }
        return dummyHead.next;
    }

}

Offer24反转链表

利用迭代实现(递归实现)

        ListNode dummyHead=new ListNode(-1);
    ListNode tail=dummyHead;
    public ListNode reverseList(ListNode head) {
        helper(head);
        return dummyHead.next;
    }

    private void helper(ListNode head) {
        if (head==null){
            return;
        }
        helper(head.next);
        //后序位置
        tail.next=head;
        tail=tail.next;
        //从后面断链接,不会影响前面的链表
        head.next=null;
    }

 利用递归语义解决问题

 //利用递归函数将一个链表反转
    public ListNode reverseList(ListNode head) {
            if (head==null||head.next==null){
                return  head;
                //终止条件,如果只剩一个结点,不需要处理
            }
            ListNode next=head.next;//头节点的后面一个结点,也是反转后的最后一个结点
            ListNode reverseHead=reverseList(head.next);//将后面的链表反转,返回反转后的头节点
            next.next=head;
            head.next=null;
            return reverseHead;
    }

25K个一组翻转链表

  •  这道题就是多个子链表反转,然后将多个子链表链接在一起,有一个细节就是当子链表的长度小于k的时候,这个链表就不需要除了、
  • 其实链表是天然的递归和迭代的数据结构,所以用递归和迭代的都能解决
  • 先完成一个功能,就是将链表反转,然后返回反转链表后的头节点,因为是区间链表,我们使用迭代比较简单,但是递归也能做
    private ListNode reveser(ListNode left, ListNode right) {
            ListNode prev=null;
            ListNode cur=left;
            ListNode next=left;
            while (cur!=right){
                next=cur.next;
                cur.next=prev;
                prev=cur;
                cur=next;
            }
            return prev;
    }
  • 然后实现怎么讲链表变成一个个区间链表,然后链接起来
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head==null){
            return null;
        }
        ListNode left=head;
        ListNode right=head;
        //区间是左闭右开,
        for (int i = 0; i < k; i++) {
            if (right==null){
                return left;
            }
            right=right.next;
        }
        ListNode node= reveser(left,right);//返回反转链表的头节点,也就是最后一个结点
        ListNode nextHead= reverseKGroup(right,k);
        left.next=nextHead;//最开始的点被反转到最后面
        return node;
    }

Offer35复杂链表的复制

  • 这道题的难点就是在于,我们不能确定他的random到底对应哪个结点,它不像是我们的next,知道next的指向就是链表的下一个元素,random的指向可能是在这个结点前面,或者在后面,或者是null,还可能是本身
  • 对于这种,我们合理利用Map这种结构,Map这中key-value,是一对一的结构,我们可以让新构造的结点跟对应的结点对应,然后遍历一遍Map就可以
class Solution {
   public Node copyRandomList(Node head) {
        if (head==null){
            return null;
        }
        HashMap<Node,Node> map=new HashMap<>();
        for (Node node=head;node!=null;node=node.next){
            Node newNode=new Node(node.val);
            map.put(node,newNode);
        }
        for (Node node=head;node!=null;node=node.next) {
            map.get(node).next=map.get(node.next);
            map.get(node).random=map.get(node.random);
        }
        return map.get(head);
    }
}

 

  • 让A走完去走B,B走完去走A ,逻辑上就相当于走了同一条的链表

class Solution {
        ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode node1=headA;
        ListNode node2=headB;
        if (headA==null||headB==null){
            return null;
        }
        while (node1!=node2){
            if (node1==null){
                node1=headB;
            }else {
                node1=node1.next;
            }
            if (node2==null){
                node2=headA;

            }else{
                node2=node2.next;
            }

        }
        if (node1==null){
            return null;
        }else {
            return node1;
        }
    }


}

2两数相加

class Solution {
        public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int carry=0;
        ListNode dummyHead=new ListNode(-1);
        ListNode tail=dummyHead;
        while (l1!=null||l2!=null||carry!=0){
            int val1=l1==null?0: l1.val;
            int val2=l2==null?0: l2.val;
            int sum=val1+val2+carry;
            carry=sum/10;
            sum=sum%10;
            ListNode node=new ListNode(sum);
            tail.next=node;
            tail=tail.next;
            if (l1!=null){
                l1=l1.next;
            }
            if (l2!=null){
                l2=l2.next;
            }
        }
        return dummyHead.next;
    }


}

141环形链表

public class Solution {
    public boolean hasCycle(ListNode head) {
        // 快慢指针初始化指向 head
        ListNode slow = head, fast = head;
        // 快指针走到末尾时停止
        while (fast != null && fast.next != null) {
            // 慢指针走一步,快指针走两步
            slow = slow.next;
            fast = fast.next.next;
            // 快慢指针相遇,说明含有环
            if (slow == fast) {
                return true;
            }
        }
        // 不包含环
        return false;
    }
}

142环形链表2

  •  首先思路肯定是用快慢指针去遍历,如果它有环的话,必定是在环的范围里面相遇,我们假设快指针的速度是慢指针的速度的2倍,那么快指针走了2k,慢指针走了k相遇
  • fast 一定比 slow 多走了 k 步,这多走的 k 步其实就是 fast 指针在环里转圈圈,所以 k 的值就是环长度的「整数倍」。
  • 相遇点到环的起点就是k-m,如果我们从相遇点继续走k-m步,就凑齐k步,也就肯定能到环起点
public class Solution {
       public ListNode detectCycle(ListNode head) {
        ListNode low=head;
        ListNode fast=head;
        boolean flag=false;
        while (fast!=null&&fast.next!=null){
            low=low.next;
            fast=fast.next.next;
            if (low==fast){
                flag=true;
                break;
            }
        }
        // 上面的代码类似 hasCycle 函数
        if (fast == null || fast.next == null) {
            // fast 遇到空指针说明没有环
            return null;
        }
        // 重新指向头结点
        low = head;
        // 快慢指针同步前进,相交点就是环起点
        while (low != fast) {
            fast = fast.next;
            low = low.next;
        }
        return low;
    }

}

148排序链表

  • 这道题就是归并排序的链表实现,归并排序每次都要找到中间结点,作为左右区间的分割(对于链表找中间结点,用快慢指针),然后就是两个有序列表的合并(offer25),有一个细节,就是对于数组,我们提供索引来区分区间,但是链表我们是通过next来连接,所以我们需要将左右两个区间的连接断了
  • 归并思想,就是将一个区间先分成更小的区间(中止条件,区间只有一个元素或者0个肯定是有序的),然后将一个个有序的小区间变成有序的大区间,直到整个区间有序
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
   public ListNode sortList(ListNode head) {
        //利用归并排序的思想
        if (head==null||head.next==null){
            return head;
        }
        ListNode mid=midNode(head);
        ListNode x=head;
        while (x.next!=mid){
            x=x.next;
        }
        x.next=null;//将左右区间断开
        ListNode left= sortList(head);//排序左区间
        ListNode right= sortList(mid);//排序右区间
        return merge(left,right);//因为两个子链表断了,肯定需要重新链接起来 如果想提供效率,
        // 来比较左区间的最大值小于由区间的最小值,就直接链接就行
    }

    private ListNode merge(ListNode left, ListNode right) {
       ListNode dummyHead=new ListNode(-1);
        ListNode tail=dummyHead;
        while (left!=null&&right!=null){
            if (left.val<=right.val){
                tail.next=left;
                left=left.next;
                tail=tail.next;
            }else {
                tail.next=right;
                right=right.next;
                tail=tail.next;
            }
        }
        if (left==null){
            tail.next=right;
        }
        if (right==null){
            tail.next=left;
        }
        return dummyHead.next;
    }


    private ListNode midNode(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        while (fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        return slow;
    }
}

OfferII27回文链表

  • 思路:我们就是要比较123和321的逆序是不是相同
  • 通过快慢指针找到中间结点(12 3 2 1),我们比较的是12 和21,所以当我们遇到奇数个节点,就让slow前进一步,去掉这个多余的中间节点(判断条件:当fast不为null的时候,说明有奇数个节点)
  • 然后讲后面的链表反转,比较这两个链表是否每个值都相同
class Solution {
    public boolean isPalindrome(ListNode head) {
            //首先通过快慢指针来获得链表的中间结点
            // 有奇数和偶数之分 1 2 3 2 1 为奇数个,通过快慢指针获得的中间结点是3,我们应该比较的是12 21
            // 1 2 3 3 2 1 获得的就是是左边的三 我们应该比较的是123 321
            ListNode mid=midNode(head);
            ListNode right=head;
            ListNode left=reverse(mid);
            while (left!=null){
                if (left.val!=right.val){
                    return false;
                }
                left=left.next;
                right=right.next;
            }
            return true;
    }

    private ListNode reverse(ListNode mid) {
        ListNode prev=null;
        ListNode cur=mid;
        ListNode next=mid;
        while (cur!=null){
            next=cur.next;
            cur.next=prev;
            prev=cur;
            cur=next;
        }
        return prev;
    }

    private ListNode midNode(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        while (fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        if (fast!=null){
            //对应奇数个结点,我们让slow多走一步
            slow=slow.next;
        }
        return slow;
    }
}

 对比数组类型的归并排序

class Solution {
   public int[] sortArray(int[] nums) {
        //采用归并排序
        sortHelperBymerge(nums,0,nums.length-1);
        return nums;
    }

    private void sortHelperBymerge(int[] nums, int left, int right) {
        //left-right是我们需要排序的区间
        if (left>=right){
            //说明区间为空,或者区间就一个元素,不需要排序
            return;
        }
        int mid=left+(right-left)/2;
        //开始归
        sortHelperBymerge(nums,left,mid);
        sortHelperBymerge(nums,mid+1,right);
        //开始并
        if (nums[mid]>nums[mid+1]){
            //说明这两个子区间 直接合起来不是有序的,需要我们自己去合并
            //这里的思想就是合并两个有序的数组
            merge(nums,left,mid,right);
        }
    }

    /**
     * 合并两个有序区间为一个更大的有序区间
     * [left,mid] [mid+1,right]
     * [0,1],[2,3]
     * @param nums
     * @param left
     * @param mid
     * @param right
     */
    private void merge(int[] nums, int left, int mid, int right) {
        int []arr=new int[right-left+1];
        int index=0;
        for (int i = left; i <=right ; i++) {
            arr[index++]=nums[i];
            //将这两个区间的值都放在一个数组中
        }
        int l1=left;//用来指向当前左区间的指针
        int l2=mid+1;//用来指向当前右区间的指针
        int i=left;
        while (i<=right){
            if (l1>mid){
                //说明当前左区间已经遍历完,只能去右区间的值
                nums[i]=arr[l2-left];
                l2++;
                i++;
            }else if(l2>right){
                //说明当前右区间已经遍历完,只能走左区间
                nums[i]=arr[l1-left];
                l1++;
                i++;
            }else if (arr[l1-left]>=arr[l2-left]){
                //当前左区间的指针的值大于等于有区间的值
                nums[i]=arr[l2-left];
                l2++;
                i++;
            }else {
                nums[i]=arr[l1-left];
                l1++;
                i++;
            }
        }
    }
}

 86分割链表 

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode dummyHead1=new ListNode(-1);
        ListNode tail1=dummyHead1;
        ListNode dummyHead2=new ListNode(-1);
        ListNode tail2=dummyHead2;
        while (head!=null){
            if (head.val<x){
                tail1.next=head;
                ListNode node=head;
                head=head.next;
                node=node.next=null;
                tail1=tail1.next;
            }else {
                tail2.next=head;
                ListNode node=head;
                head=head.next;
                node=node.next=null;
                tail2=tail2.next;
            }
        }
        tail1.next=dummyHead2.next;
        return dummyHead1.next;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

库里不会投三分

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

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

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

打赏作者

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

抵扣说明:

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

余额充值