合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
这个要多讲讲,因为完全涉及到了我的知识盲区!!!有序链表这种东西真的没怎么接触过,树也接触的少,毕竟我就是一个非科班,在小外包公司成长起来的,一直以来对这种数据结构还有算法什么的保有憧憬和向往(这也是现在刷算法的原因)。刚一接触题目,这个ListNode都够让我看好久了,就是不明白为啥next就是下一个节点。还是我狗群友给了我指点,在此表示感谢。步入正题。
思路:我自己做的麻烦了,因为是先把两个链表取出来排序然後在放到一个链表了,看了大佬思路才发现可以两个链表取出来直接放到链表里,省去了中间的过程。
/**
* 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 && l2==null){
return new ListNode(0).next;
}
//遍历俩链表,元素都取出来
List<Integer> list = new ArrayList<Integer>();
while(l1!=null){
list.add(l1.val);
l1=l1.next;
}
while(l2!=null){
list.add(l2.val);
l2=l2.next;
}
//为了保证链表的有序性,所以把list排序
Collections.sort(list);
//保存头节点
ListNode result = new ListNode(0);
//用cur来控制指针往下挂节点?
ListNode cur = result;
for(Integer i:list){
cur.next = new ListNode(i);
cur = cur.next;
}
return result.next;
}
}
这个其实不夸张的说真的是一道题卡半天,因为关于链表,节点什么的没什么意识,差不多做点就问群里人,然後现在为止也不能说多了解链表吧。但是勉强上面的代码可以解释的通,理解面上的意思了(我不知道我注解是不是错了,如果错了欢迎指出)。
然後我都做完了,甚至哪怕看了大神的解法开始分析了,还是在群友的提醒下发现了问题:题目要求是要拼接两个链表节点,我这里取了巧,用的list排序。所以下面老老实实介绍大神的写法
其实大神的写法思路也很清晰,如果懂了链表的结构也很简单就能看懂。我反正现在是勉勉强强可以写出来。
/**
* 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 result = new ListNode(0);
//用这个cur来挂载节点
ListNode cur = result;
//两个链表都不为空才有必要比较,只剩一个了直接全挂上就好了
while(l1!=null && l2!=null){
//因为要按照顺序挂,所以先挂小的
if(l1.val<l2.val){
//因为头结点0.所以往下挂
cur.next = l1;
l1 = l1.next;
cur = cur.next;
}else{
cur.next = l2;
l2 = l2.next;
cur = cur.next;
}
}
if(l1!=null){
cur.next = l1;
}else{
cur.next = l2;
}
//cur的指针已经指到最后了,所以只能从result开始,头节点那个0是自己加的,所以不算,从头节点的下一个开始
return result.next;
}
}
删除排序数组中的重复项
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
思路:这个题目其实挺唬人的,什么不使用额外的数组空间,就是不能创建新数组呗。O(1)这个概念还是我现去百度的
O(1):就是最低的时空复杂度了,也就是耗时/耗空间与输入数据大小无关,无论输入数据增大多少倍,耗时/耗空间都不变。 哈希算法就是典型的O(1)时间复杂度,无论数据规模多大,都可以在一次计算后找到目标(不考虑冲突的话)
但是抛除了这几句话开始正常考虑思路其实也没那么难。不就是要做到两点:一使数组中没有重复元素,二返回数组长度么。首先不能创建新数组,所以只能在原数组中改动,其次因为是有序数组,最多就是一样的数组连续排在一起,不会存在隔过一个跟之前的相同,所以只要比较上一个元素就行而不是要遍历比较之前所有的元素。
接下来就是实现的代码:
public int removeDuplicates(int[] nums) {
//打标
int j = 0;
int i = 1;
while(i<nums.length){
//初始i=1,j=0.两个值相等说明重复了
if(nums[j]==nums[i]){
i++;
//两个值不相等才算是增加一个数组中元素个数
}else{
j++;
nums[j] = nums[i];
i++;
}
}
return nums.length==0?0:j+1;
}
然后因为这个理清楚逻辑是很好写出来的,说句题外话,我最近用了LeetCode才深感编译器的重要性。无论是自动提示还是报错提醒都特别有用。因为用LeetCode经常遇到忘记加标点符号或者后半个括号什么的,还非要运行才报错,麻烦死了。
上面的代码一开始我用的for循环,跑出了两次数组下标越界才改成现在这个版本的!
然后贼自豪的是跑出来的用时超过了百分百的用户,第一次哎!
上面代码中用了两个标:一个j是数组的下标、我们都知道数组的元素数是下标+1.因为下标从0开始。一个是原数组中的下标i。这个只是为了判断是否还有元素没有比较到。等到数组中所有元素比较完了,把唯一出现的存进去,重复出现的不用管了反正不占位就放那放着,占位了的就用新的把老的替换了。
之前看了下大神的解析,差不多都是用的这种思路。只不过细节上可能是略有不同,有的for循环有的do-while也有的和我一样是while。
然后好像这种思路有一个专有名词,叫做双指针解法?反正思路是大同小异,所以我也不多说了。
移除元素
题目:给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
这个题目其实只要是上道题会做了这个也自然会做了。只不过上题判断的是重复,这个题判断的是等于某值而已。不多说了,上代码:
public int removeElement(int[] nums, int val) {
//打标,作为原数组下标
int i = 0;
//打标,作为现在的数组下标
int j = 0;
//原数组下标不越界就ok,所以这里用j<nums.length做条件
while(i<nums.length){
//从第一个元素开始判断是否为val,如果是则将这个元素替换成下一个元素再判断
if(nums[i]==val){
i++;
}else{
nums[j] = nums[i];
i++;
j++;
}
}
return j;
}
这个题没什么可解释的了,除了那个if条件剩下一样一样的代码。不过这个是从第0个开始判断,上一个因为判重,所以第一个元素肯定不会重复,所以是从第一个开始判断。
今天因为一些工作的原因,还有链表耽误了很多时间,所以几天就这三道题,如果稍微帮助到了你点个喜欢点个关注,也祝大家工作顺顺利利!