Day5 LeetCode 23 26 33
目录
1. 合并K个升序链表(LeetCode 23 题)
1.1题目
1.2思路
第一次做困难题,一次通过测试哈哈哈哈。
说一下本题思路,首先就是有很多有序的链表,你需要在短时间内把这些链表整理到一个链表里,同时保证新链表有序。
我们可以把这个问题化简一些。如果这个数组里只有两个链表,怎么办?
这就是把21题合并两个链表的问题扩展了。
首先合并两个链表的方法我再重新讲一下,你需要准备一个新链表来存储你新链表,那么可以依次对两个链表中的值进行比较。小的数放入新链表,大的数等待下次计较。如果其中一个链表遍历结束,即可把剩余的链表并入。
然后我们看看如何把许多个这样的链表合并起来,我采用的方法是分治的思想,然后通过递归来实现。
首先构建我们的返回链表,对长度等于1的链表直接返回原数据。如果长度大于2,就对前半部分进行单独合并,后半部分单独合并。这样相当于整体数据递归的分出两部分进行合并,然后逐级整合。时间复杂度为nlogn。通过mergeKLists逐级拆分,然后通过mergeTwoLists逐级合并。
1.3代码
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
ListNode returnans = new ListNode(0);
ListNode keypoint = returnans;
if (lists.length==1) return lists[0];
else if (lists.length>=2)
return mergeTwoLists(
mergeKLists(Arrays.copyOfRange(lists,0,lists.length/2)),
mergeKLists(Arrays.copyOfRange(lists,lists.length/2,lists.length))
);
return keypoint.next;
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode returnans = new ListNode(0);
ListNode keypoint = returnans;
while (l1!=null&&l2!=null){
if (l1.val<l2.val){
keypoint.next = l1;
l1 = l1.next;
}else {
keypoint.next = l2;
l2 = l2.next;
}
keypoint = keypoint.next;
}
keypoint.next = (l1!=null)?l1:l2;
return returnans.next;
}
}
2.删除排序数组中的重复项(LeetCode 26 题)
2.1 题目
2.2 思路
看到这个题的时候,我比较大意没有认真读题,想着简单题随便写。但是测试没过就认真读了读。
我们需要做两件事,一个是找出有多少个不重复元素,另一个就是原来数组的前N项必须为不重复元素的数。
我采用了双指针的解法,首先判断输入的列表是否为空。其次简历指针ans和指针i,i是保证我们可以遍历完数组,ans做返回长度的同时也实现了对nums的更新。
首先ans设为0,因为我们从头开始遍历,然后如果后面与【0】的数据不同表示出现了新的不重复项。(题目给定递增)
采用循环的方式,对每个数据进行遍历,如果数据和ans的数据不同,ans增加1,即达到对前N个数据的更新,也能让返回不重复个数的数量得到保证。
时间复杂度n 空间复杂度1。
2.3 代码
class Solution {
public int removeDuplicates(int[] nums) {
if(nums==null) return 0;
int ans = 0;
for(int i = 0; i<nums.length;i++){
if(nums[i]!=nums[ans]){
nums[++ans]= nums[i];
}
}
return ans+1;
}
}
3.搜索旋转排序数组(LeetCode 33 题)
3.1 题目
3.2 思路
第一次看到这个题还挺懵逼的,然后看了题解,然后用二分的方法还是没解出来。再看了一次题解发现题目还是出的很不错。需要对题目有一定的理解才能很好地解答。
首先从暴力解法开始,就是遍历一遍,然后直接找到我们的答案,我想这样解答虽然没问题但是还有更巧妙的方法在等我们。
递归二分查找的方法如下,首先我们对原函数重写,searchp(int[] nums,int target ,int start,int end);
加入双指针进行判断。然后写跳出递归的条件,
if(end-start<=1){
if (nums[start]==target) return start;
else if (nums[end]==target) return end;
else return -1;
}
这里我解释一下为什么这样写,首先start,end是对指针的记录,因为本题需要返回的是一个下标。有两个指针方便我们后面对二分方法递归的调用,所以重写了原方法。同时初始化了start,end指针的值0和length-1。接下来就是如果数组长度小于等于1,就直接判断,也就是我们跳出递归的条件。
说一下递归主体部分,如果这个题不是旋转的序列,大家可以简单的递归二分查找的方法很方便的找到。但是我们这里的序列进行了旋转。这个操作可能就会难住大家。讲一下怎么对转的序列进行解析。
如果序列旋转,在start,(start+end)/2,end 这三个点我们可以划分成两个区域。
拿这两个区域举一些例子。
0,1,3,4 target = 1, start ->0 (start+end)/2 ->1 end->4
3,4,0,1 target = 3, start ->3 (start+end)/2 ->4 end->1
0,1,3,4 target = 1, start ->0 (start+end)/2 ->1 end->4
3,4,5,0,1 target = 3, start ->3 (start+end)/2 ->5 end->1
如果start<(start+end/2) ,start <=target<=(start+end)/2,说明target在有序的区域。 另一边可能是无序。
如果start>(start+end/2) ,(start+end)/2<=target<=end,说明target在有序的区域。 另一边可能是无序。
这样子就可以把代码分成四块 然后进行分别递归查找,减少一般的查找时间和空间。
3.3 代码
class Solution {
public int search(int[] nums, int target) {
int start = 0;
int end = nums.length-1;
return searchp(nums,target,start,end);
}
public int searchp(int[] nums,int target ,int start,int end){
if(end-start<=1){
if (nums[start]==target) return start;
else if (nums[end]==target) return end;
else return -1;
}else {
if (nums[start]>nums[(start+end)/2]){
if (nums[(start+end)/2]<=target&&nums[end]>=target){
return searchp(nums,target,(start+end)/2,end);
}else {
return searchp(nums,target,start,(start+end)/2);
}
}else {
if (nums[(start+end)/2]>=target&&nums[start]<=target){
return searchp(nums,target,start,(start+end)/2);
}else {
return searchp(nums,target,(start+end)/2,end);
}
}
}
}
}
4 小结
今天的题目比较复杂,也是完成最晚的一次。但是在之前学习的铺垫下不觉得有多困难,同时最近进步飞快,代码的逻辑性和条理性都得到了很大的提升。继续加油!