背景
最开始打算在牛客网刷《剑指offer》结果发现刷了几道链表的题后,还得买书才能继续刷,我这个一毛不拔怎么能忍,况且我已经有这本书了,干嘛还要买,本来想在网上再找找刷这本书的地方,但木有找到,所以转战leetcode。
看了leetcode的题库,就从第一个《面试经典150题》来吧,里面有些题其实刷过,所以可能有的题会很快写出来,但是不会的话估计就很慢了,然后设置每天两道,因为不想给自己那么大压力,而且还想学点别的。
好了要开始我的刷题之旅了,有木有一起刷题的小伙伴,可以一起来哦~
刚开始是数组的题目,相对简单,而且之前都做过,所以做的还是比较快,把昨天和今天刷的题一并总结下,共4题。
一、数组合并和链表合并
题目一 88. 合并两个有序数组
哈哈,我一看这个题,简单呀,我要一遍ac,一顿操作猛如虎写完信心满满提交,结果竟然不对!惊掉下巴,为啥不对。我是新建的一个数组来保存结果的,然后还放到了chrome里运行,没错啊,什么情况!然后我又去看了题目,看到:
请你 合并
nums2**到nums1中,使合并后的数组同样按 非递减顺序 排列。
原来是这样,这题有坑啊,看似简单,实际上暗藏玄机,如果用原数组的话,那写法就有的说了,我最开始写的还是新开一个数组,把nums1拷贝过去,😂,换汤不换药,只是把结果保存到nums1,然后就通过了。
var merge = function(nums1, m, nums2, n) {
let i = 0;
let j = 0;
let nums1backup = [...nums1];
let count = 0;
while (i < m && j < n) {
if (nums1backup[i] < nums2[j]) {
nums1[count++] = nums1backup[i++];
} else {
nums1[count++] = nums2[j++];
}
}
while (i < m) {
nums1[count++] = nums1backup[i++];
}
while (j < n) {
nums1[count++] = nums2[j++];
}
};
因为昨天睡前刷的,太瞌睡了,就直接看了答案,看大家是如何原地合并的。
看到了解题的关键:从后向前遍历,妙啊。不错不错,顿时又一顿自卑==,写一遍如下:
// 拷贝到nums1
var merge = function(nums1, m, nums2, n) {
let sum = m + n;
let i = m - 1;
let j = n - 1;
while (i >= 0 && j >= 0) {
if (nums1[i] > nums2[j]) {
nums1[--sum] = nums1[i--];
} else {
nums1[--sum] = nums2[j--];
}
}
while (j >= 0) {
nums1[--sum] = nums2[j--];
}
}
这道题的启示:
- 看似简单的题却又不简单,我们需要仔细审题
- 掌握从后向前遍历的思路,这个思路值得学习。
- 看人家脑袋多灵光。啧啧
做了数组的合并,那是不是还有链表的合并,正好前两天也做了,那直接放到一起吧。插入一题,链表的合并。
题目二 21. 合并两个有序链表
解法一 构造新链表
这个和合并数组一样,依然是2种解法,一种是构造一个新列表,挨个构造新节点合并过去。
var mergeTwoLists = function(list1, list2) {
let node1 = list1;
let node2 = list2;
let newHead = new ListNode();
let node = newHead;
while (node1 && node2) {
const newNode = new ListNode();
if (node1.val < node2.val) {
newNode.val = node1.val;
node1 = node1.next;
} else {
newNode.val = node2.val;
node2 = node2.next;
}
node.next = newNode;
node = newNode;
}
if (node1) {
node.next = node1;
}
if (node2) {
node.next = node2;
}
return newHead.next;
};
解法二 合并到list1中
合并到链表1中,相当于不断的往list1插入节点,考虑到头节点,所以需要一个哑节点来处理,所以构造一个哑节点指向list1,使用pre和cur两个指针,用来方便插入,cur指向list1当前遍历到的节点。所以这时有两种情况:
- 如果list1的节点小于list2的节点,则pre和cur同时向后移动,无需操作
- 否则,将list2当前的节点插入到pre后面,然后将pre移动到新插入的节点,而list2的节点后一位。
想清楚了,写出来,一遍AC,😄,开森。
var mergeTwoLists = function(list1, list2) {
let dummyHead = new ListNode();
dummyHead.next = list1;
let pre = dummyHead;
let cur = list1;
let node2 = list2;
while (cur && node2) {
// list1节点小于list2的节点,则无需操作,pre和cur向后走一步
if (cur.val < node2.val) {
pre = pre.next;
cur = cur.next;
} else {
let tmp = node2.next;
pre.next = node2;
node2.next = cur;
pre = node2;
node2 = tmp;
}
}
if (node2) {
pre.next = node2;
}
return dummyHead.next;
}
这个题给我的启示是:
- 对于链表的删除和插入题目,一般都需要构造一个哑节点,方便处理。
- 链表的题目思路不难,主要在于熟练指针的操作,写的多了基础的题就可以写出来了。
二、数组删除有套路
后面的三道题都是跟数组删除元素相关了,而且可以说有模版,都是双指针的应用。我写的三道题几乎一样,只是判断的地方不一样,感受一下吧。
题目三 27. 移除元素
这题写了好几遍了,现在看到后可以很快写出来。双指针,一遍ac~~,其实这道题最开始做的时候其实是想不到这种写法的,但看了这种写法后,赞叹“不错不错真不错”。
var removeElement = function(nums, val) {
let j = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== val) {
nums[j++] = nums[i];
}
}
return j;
};
题目四 26. 删除有序数组中的重复项
这题是不是跟上个题目很像,以致我琢磨了会,这两题啥区别😆,噢,这题是删除重复项,数组有序,那重复项就是挨着的,所以写法跟上题几乎一样。跟上个题的区别是判断条件变了。
var removeDuplicates = function(nums) {
let j = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== nums[i + 1]) {
nums[j++] = nums[i];
}
}
return j;
};
这例可能有人说,最后一个数组会越界啊,但是却通过了,我在想是不是因为js的越界不像c一样那么严格会报错,它不会报错,取出的是undefined,所以肯定不等于,所以对了。算是投机取巧了。以至于下一道题也是这样。大家继续看。
题目五 80. 删除有序数组中的重复项 II
这个题在上一道题的基础上又加大难度了,区别就是使得出现次数超过两次的元素**只出现两次**,其他统统一样。
那这个题关键是想清楚,什么时候慢指针j不用往后移动,那什么时候呢,就是当前元素等于后一个元素,并且等于后一个的后一个元素,即nums[i] === nums[i + 1] && nums[i + 1] === nums[i + 2], 这个条件又可以简化为什么呢?nums[i] === nums[i + 2],因为是有序的,所以中间的元素必定和左右两个相等。这个搞清楚了,再取一下反,凡是不符合条件的,慢指针就正常往后,否则不移动。所以代码如下:
var removeDuplicates = function(nums) {
let j = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== nums[i + 2]) {
nums[j++] = nums[i];
}
}
return j;
};
所以看到了吗,跟上面两道题的唯一区别是判断条件!哈哈,竟然总结了一个模版代码。
总结
至此,我终于掌握了删除元素的方法了。总结一下模版代码如下:
var removeDuplicates = function(nums) {
let j = 0;
for (let i = 0; i < nums.length; i++) {
if (快指针可以拷贝到慢指针的条件) {
nums[j++] = nums[i];
}
}
return j;
};
最后
今天的文章也AC了,可是还有一个链表的题目没总结,接着写,最后一句鸡汤结尾,送给大家:
人生的一大目标应该是掌控自己的时间。理想的工作是利用杠杆效应的工作。在这种工作模式下,你可以掌控自己的时间,并能对自己的产出成果负责。如果你通过提供绝佳的解决方案给企业带来了神奇的转机,那么你必将得到金钱的回报。
这是这两天在看的书《纳瓦尔宝典》的句子,这本小书还是挺不错的,推荐大家阅读~~