LeetCode刷题之旅

LeetCode刷题之旅

一、链表

1.链表逆序(leetcode 206.Reverse Linked List)esay

题目描述:已知链表头节点指针head,将链表逆序。

思路:从链表的头节点依次遍历,每遍历一个节点就进行逆序, 使用头插法进行逆序。

代码实现

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode newHead = null;
        while(head != null) {
            ListNode nextTemp = head.next;
            head.next = newHead;
            newHead = head;
            head = nextTemp;           
        }
        return newHead;
    }
}

2.链表部分逆序(leedcode92.Reverse Linked List2)medium

题目描述:已知链表头节点指针head,将链表从位置m到n逆序。

思路:需要完成m到n位置的链表逆序,其中逆序算法已经清楚,只需要确定开始逆序的位置(从起始节点后移m个位置),以及需要逆序的节点个数(m-n+1)。同时,需要记录开始逆序节点的前驱(pre_start),以便逆序完可以直接链接上。逆置段尾结点的后继也需要记录。需要注意一点,讨论m是否为1,如果从第一个节点开始逆置,则逆置完的链表段头节点即为起始节点。

代码实现

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {
        if (head == null || head.next == null) return head;
        ListNode pre_start, new_start, new_end;
        pre_start = new_start = new_end = null;
        ListNode list = head; //记录list
        int length = n-m+1; //长度
        int steps = m-1;
        while(head!=null && steps!=0) {
            pre_start = head;
            head = head.next;
            steps--; //head指针移动到m对应位置
        }
        new_end = head;
        ListNode next_head = null;
        while (head!=null && length!=0) {
            next_head = head.next;
            head.next = new_start;
            new_start = head;
            head = next_head;
            length--; //逆序length个节点
        }

        new_end.next = head;
        if (m == 1) {
            list = new_start;
        } else {
            pre_start.next = new_start; //将pre_start链接上逆置后的链表段
        }
        return list;
        

3.求两个链表的交点(leetcode160.Intersection of Two Linked Lists)easy

题目描述:已知链表A的头节点指针headA,链表B的头结点指针headB,两个链表相交,求两个链表相交的交点

思路:1.使用set集合。遍历链表A,将A中节点对应的指针(地址)存入set中。遍历链表B,将B中节点对应的指针(地址),在set中查找,发现在set中的第一个节点地址,即是两个链表的起始交点。

​ 2.若链表A与链表B相交,则只是相交节点的前面链表段不相同。分别计算链表A与B的长度,将较长的链表起点移动到与短链表起点对齐的地方,这样一同出发,相交点即是指针相同的交点。

代码实现

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) return null;
        ListNode listA = headA;
        ListNode listB = headB;
        int lengthA = 0;
        int lengthB = 0;
        while(headA != null) {
            lengthA++;
            headA = headA.next;
        } // 计算链表A的长度
        while(headB != null) {
            lengthB++;
            headB = headB.next;
        } // 计算链表B的长度
        int dis = 0;
        if (lengthB > lengthA) {
            dis = lengthB - lengthA;
            while(dis > 0){
                listB = listB.next;
                dis--;
            }
        } else {
            dis = lengthA - lengthB;
            while(dis > 0){
                listA = listA.next;
                dis--;
            }
        } //将较长链表的起点移动到与短链表起点对齐的地方
        while (listA!=listB && listA!=null && listB!=null) {
            listA = listA.next;
            listB = listB.next;
        }
        return listA;    
    }      
}

4.链表求环(leetcode 141.Linked List Cycle)easy

问题描述:已知链表可能有环,若有环则返回true,否则返回false

思路:因为只需要判断是否有环,不用确定起始节点,所以方法较多,列举常见的两种。

1.利用set集合,依次将节点指针插入set中,当在set中查找,第一个在set中发现的节点地址,即是链表环的起点

2.从起点开始遍历,依次判断该节点的next是否为本身,如果是,则返回true;如果不是,则将该节点的next指针指向自己,并递归遍历其后面的链表。

代码实现:方法1:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null) return false;
        Set <ListNode> nodeSet = new HashSet<ListNode>();        
        while(head != null) {
            if (nodeSet.contains(head)) {
                return true;
            } else {
                nodeSet.add(head);
                head = head.next;
            }
        }
        return false;
    }
}

方法2:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if (null == head) return false;
        if (head == head.next) return true;
        ListNode n = head.next; 
        head.next = head; // 将指针指向自己
        return hasCycle(n);
    }
}

5.链表求环2(leetcode 142.Linked List Cycle2)medium

问题描述:已知链表可能有环,若有环则返回环起始节点,否则返回NULL

思路:1.简单的方法是,利用set集合实现。依次将节点指针插入set中,当在set中查找,第一个在set中发现的节点地址,即是链表环的起点。

​ 2.相对巧妙的想法,利用快慢指针实现。见下面,fast与slow指针同时从起点X出发,其中fast是slow速度的两倍,当fast指针追到slow指针时,此时相遇于Z点。对于slow指针,走了a+b的路程。那么fast指针的路程为2(a+b),同时也为 a+b+c+b,即a+2b+c=2(a+b),得a=c。这时,如果从它们的交点Z出发的话,同时有一指针head从起点X出发,必会相交于Y点,也即为环的起始点。
在这里插入图片描述

代码实现:方法1

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        // if (null == head) return null;
        Set<ListNode> nodeSet = new HashSet<ListNode>();
        while(head != null) {
            if (nodeSet.contains(head)) {
                return head;
            } else {
                nodeSet.add(head);
            }
            head = head.next;
        }
        return null;
    }
}

方法2:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {      
        ListNode fast = head;
        ListNode slow = fast;
        while (fast!=null && fast.next!=null && fast.next.next!=null) {      
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) { // fast与slow指针相交时
                while (head != slow) {
                    head = head.next; // head指针从起始点出发
                    slow = slow.next;
                }
                return head;
            }               
        }
        return null;
    }
}

6.链表划分(leetcode86.Partition List)medium

问题描述:已知链表头指针head与数值x,将所有小于x的节点放在大于等于x的节点前,且保存这些节点的原来的相对位置。如,划分前:1->4->3->2->5->2,x=3,划分后:1->2->2->4->3->5

思路:遍历链表,将小于x的节点插入到新链表less_head中,将大于等于x的节点插入到新链表more_head中,然后将more_head.next插入在less_head的末尾,返回less_head即可。

或者直接将原链表中大于x的节点移出并链接为新链表mhead,然后将mhead链接至原链表的结尾即可。

代码实现:方法1

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode lhead = new ListNode(0); // 使用两个链表less_head和more_head来分别记录
        ListNode less_head = lhead;    
        ListNode mhead = new ListNode(0);
        ListNode more_head = mhead;
        while(head!=null) {
            if (head.val < x) {
                lhead.next = head;
                lhead = lhead.next;
            } else {
                mhead.next = head;
                mhead = mhead.next;
            }
            head = head.next;
        }
        mhead.next = null;
        lhead.next = more_head.next;
        
        return less_head.next;
    }
}

方法2:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode partition(ListNode head, int x) {       
        ListNode list = new ListNode(0); // 将原链表中大于x的值连接为一个链表mhead
        list.next = head;
        ListNode new_head = list;
        ListNode more_head = new ListNode(0);
        ListNode mhead = more_head;
        while(list.next!=null) {
            if (list.next.val < x) {
                list = list.next;
            } else {
                more_head.next = list.next;
                more_head = more_head.next;
                list.next = list.next.next;           
            }
        }
        list.next = mhead.next;
        more_head.next = null;
        return new_head.next;
    }
}

7.链表的深度拷贝(leetcode138.Copy List With Random Pointer)medium

问题描述:已知一个复杂的链表,链表节点除了有一个指向下一个节点的next指针,还有一个random指针,指向本链表的任意某个节点(也可能指向自己),求这个链表的深度拷贝(构造生成一个完全新的链表,即使原链表毁坏,新链表也可独立使用)

思路:本问题中,主要是一个random指针也需要拷贝下来。利用map来实现,其中map的key记录原链表的各个节点,value来保存新建的节点(val值为key中节点的值),对于新建的节点的next和random指向的节点位置,由对应的key获得。即,map.get(head).next = map.get(head.next)、map.get(head).random = map.get(head.random)。

代码实现

/*
// Definition for a Node.
class Node {
    public int val;
    public Node next;
    public Node random;

    public Node() {}

    public Node(int _val,Node _next,Node _random) {
        val = _val;
        next = _next;
        random = _random;
    }
};
*/
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) return null;
        Map<Node, Node> map = new HashMap<>();
        Node iter = head;
        while(iter != null) {
            map.put(iter, new Node(iter.val));
            iter = iter.next;
        } // 将链表的各个节点指针存为map的key,value为该节点的拷贝。
        iter = head;
        while(iter != null) {
            map.get(iter).next = map.get(iter.next);
            map.get(iter).random = map.get(iter.random);
            iter = iter.next;
        }
        return map.get(head);
    }
}

8.排序链表的合并(2个)(leetcode21.Merge Two Sorted Lists)easy

问题描述:已知两个已排序链表头结点l1和l2,将这两个链表合并,合并之后仍是有序的,返回合并后链表头节点。

思路:本题相对简单,依次比较l1和l2的头结点,谁小就将谁插入新链表l中。最后返回l链表即可。

代码实现:方法1

/**
 * 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 l = new ListNode(0);
        ListNode head = l;
        while(l1!=null && l2!=null) {
            if (l1.val < l2.val) {
                head.next = l1;
                head = head.next; 
                l1 = l1.next;
            } else {
                head.next = l2;
                head = head.next;
                l2 = l2.next;
            }
        }
        if (l1 != null) head.next = l1; // 如果l1还有剩余节点,直接链接到l后面
        else head.next = l2; // l2还有剩余
        return l.next;
    }
}

方法2(递归实现):

/**
 * 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) {        
        if(l1 == null) return l2;
		if(l2 == null) return l1;
		if(l1.val < l2.val){
			l1.next = mergeTwoLists(l1.next, l2);
			return l1;
		} else{
			l2.next = mergeTwoLists(l1, l2.next);
			return l2;
		}
    }
}

9.排序链表的合并(多个)(leetcode23.Merge K Sorted Lists)hard

问题描述:已知k个已排序链表头结点l1和l2,将这k个链表合并,合并之后仍是有序的,返回合并后链表头节点。

思路:本题相当于上一题难度变大,主要是如何分配这k个链表,让其分别两两合并,最后合并为一个整体。如果采用暴力破解,从第一个开始,合并第二个,然后再合并第三个。。。,共k个链表,平均每个链表有n个节点,则时间复杂度为:(n+n)+(2n+n)+(3n+n)+…+((k-1)n+n) = (1+2+3+…+k-1)n+(k-1)n = (k^2+k-1)/2*n = O(k^2 *n).

另一个思路,采用分治法,即两两进行合并。第一轮,进行k/2次,每次处理2n个;第二轮,进行k/4次,每次处理4n个,…,最后一次,进行k/(2logk)次,每次处理2logk*n个值。时间复杂度为:k/2 *2n+k/4 *4n+…+k/(2^logk) *(2^logk *n) = nk+nk+…+nk = O(klogk * n)。
代码实现

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        return mergeKLists(lists, 0, lists.length-1);
    }
    
    public ListNode mergeKLists(ListNode[] lists, int first, int end) {
        if (first == end) return lists[first];
        if (first > end) return null;
        if (end-first == 1) return mergeTwoLists(lists[first], lists[end]);
        int mid = (first + end)/2;
        ListNode l1 = mergeKLists(lists, first, mid); // 递归合并前半部分
        ListNode l2 = mergeKLists(lists, mid+1, end); // 递归合并后半部分
        return mergeTwoLists(l1, l2);
    }
    
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) { // 合并两个链表
        if (l1 == null) return l2;
        if (l2 == null) return l1;
        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

利用数组实现:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) return null;
        if (lists.length == 1) return lists[0];
        if (lists.length == 2) return mergeTwoLists(lists[0], lists[1]);
        int mid = lists.length/2;
        ListNode[] lists1 = new ListNode[mid];
        for (int i=0; i<mid; i++) { // 将lists前半部分存入数组list1[]中
            lists1[i] = lists[i];
        }
        ListNode[] lists2 = new ListNode[lists.length-mid];
        for (int i=mid; i<lists.length; i++) { // 将lists后半部分存入数组list2[]中
            lists2[i-mid] = lists[i];
        }        
        ListNode sub_list1 = mergeKLists(lists1); // 将list1[]中链表合并
        ListNode sub_list2 = mergeKLists(lists2); // 将list2[]中链表合并
        return mergeTwoLists(sub_list1, sub_list2);
    }
      
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) return l2;
        if (l2 == null) return l1;
        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值