「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战」
我们一起看一下我今天做的五道题,还是那句话,解题思路可能不是最优解,都是我自己所能想到的
解法,如果大家有什么更好的解法,希望能不吝赐教。先行谢过。
第一题
剑指 Offer II 024. 反转链表
给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
这个就是想办法把一个链表反过来,由于我们知道链表是不能使用数组的反转的,我们拿到1的时
候,只能知道next.val = 2,我们要实现的是能让2→1,然后3→2,可是我们直接2.next =1的时候,
我们就没办法获得3了,因为2→3的链已经断了,所以我们要实现在2→1,的同时还能让2→3。这时
候我想大家都想到了,我们可以不再原有的链表上改变指针,而是新建一个链表,一次把原来的链
表,放到新链表上,正常一个链表,比如1→2→3,我们想查到3的时候我们需要head = head.next ,
这样我们得到了链表的下一个,那如果我们反排链表要怎么做呢。
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function (head) {
// 假定一个新链表是空的
let link = null;
while (head != null) {
// 我们保留链表的next
let next = head.next;
// 把当前的第一位单独拿出来,将它的next链接到新链表上
head.next = link;
// 再把这个复制到目标链表里
link = head;
// 再循环获取下一位,取出,把目标放到它的next上,复制再取出
head = next;
}
return link;
};
由于我还不会作图,我把我能写出的流程,都以文字的形式写出来
第一次循环 head 2->3->4->5->null link 1->null
第二次循环 head 3->4->5->null link 2->1->null
......
这样,一个链表循环就做完了
第二题
面试题 02.07. 链表相交
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个
链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
这道题,我一开始理解的是找出两个链表中第一个相等的值,我就循环了两个链表去对比val,后来
我发现事情并没有我想得那么简单,我要找的是后面完全一样。我就想到了那我反转两个链表,然后
再找到最后相等的,取前面一段,再反转回来,不就好了,但是上面说了不能改变链表原始结构,而
且这个想法想想就难以实现,而且时间复杂度爆表,最后还是老老实实的一个一个往后排,可以链表
长度相等的可以这样对比,要是不相等的呢,这该怎么办呢。既然长度不相等,那我们就把它变得相
等不就好了,那就是a+b=b+a,两个链表拼一下,这样长度不就相等了吗。
于是乎就有了
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getIntersectionNode = function(headA, headB) {
let a = headA;
let b = headB;
while (a !== b) {
// 如果a,b任意一个到头了,就用另一个拼上去
a = a == null ? headB : a.next;
b = b == null ? headA : b.next;
}
return a
};
如:
a = 0->9->1>2->4
b = 3->2->4
a+b = 0->9->1>2->4->3->2->4
b+a = 3->2->4->0->9->1>2->4
最后总会得到我们想要的,没毛病吧,兄弟们。
第三题
703. 数据流中的第 K 大元素
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的
元素。
请实现 KthLargest 类:
-
KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
-
int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
输入: ["KthLargest", "add", "add", "add", "add", "add"] [[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]] 输出: [null, 4, 5, 5, 8, 8]
解释: KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]); kthLargest.add(3); // return 4 kthLargest.add(5); // return 5 kthLargest.add(10); // return 5 kthLargest.add(9); // return 8 kthLargest.add(4); // return 8
讲道理,我第一次做这种题,我看了十分钟题干,我并没有看懂这是什么,每个字我都认识,可是连
在一次我一个曾经语文分数也不低的人,竟一时不知道它讲的是什么。经过我的不懈努力,我终于读
懂了题干。
翻译成人话就是,[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]],这里面[3, [4, 5, 8, 2]]的3是我要找到第3大的
元素,从哪里找呢,初始数组是 [4, 5, 8, 2],这个我们能理解吧,这是5,很简单的,那么后面的是什么意思呢,就是这个数组是会一直往里添加的, [3], [5], [10], [9], [4],这就是每次都添加的数,比
如第一个3,就是 [4, 5, 8, 2]加了个3之后,返回此时第3大的元素。然后再加一个5,再返回此时的第
3大的元素。
读懂了题干,那我们就开始想要怎么实现了,实现起来就简单多了,把数组从大到小排序,然后往里
面在该添加的位置加进去后来的数,再从前往后找我们需要找到的元素就好了,但是我试过,在
LeetCode上耗时非常长,时间复杂度特别高,数组排序,我就用的sort,那么能缩短时间的只能是插
入新元素的时候了,于是我采用了2分法
/**
* @param {number} k
* @param {number[]} nums
*/
var KthLargest = function (k, nums) {
this.k = k
this.nums = nums.sort((a, b) => b - a);
};
/**
* @param {number} val
* @return {number}
*/
KthLargest.prototype.add = function (val) {
let low = 0;
let high = this.nums.length-1;
// 先确定数组的长度
while(high >= low){
// 找到中点
const mid = Math.floor((low + high) / 2)
// 找到中点值
const v = this.nums[mid]
// 判断新加入的值在哪部分里,根据判断进行二分
if(v==val){
this.nums.splice(mid, 0, val)
break
}else if(val>v){
high = mid-1
}else{
low = mid+1
}
}
// 最后插入
if(low>high){
this.nums.splice(low, 0, val)
}
// 输出
return this.nums[this.k - 1]
};
这一定也不是最优解,但是这是我能独立想出来的最优解了,如果有更好的办法,希望小伙伴们能提
出指正,我比较笨,如果能把我教会,那你一定很厉害。
第四题
226. 翻转二叉树
输入
4
/ \\
2 7
/ \\ / \\
1 3 6 9
输出
4
/ \\
7 2
/ \\ / \\
9 6 3 1
看这道题题干多容易理解,按我们东北话讲,这不就掉个个儿么,这有啥难的。
简简单单,一个递归。
var invertTree = function(root) {
// 结束标志
if (root==null) return null;
// 两侧递归
let left = invertTree( root.left)
let right = invertTree( root.right)
// 反转
root.right = left
root.left = right
// 输出
return root
};
这道题主要就是想清楚在哪里递归就好了,left一定是invertTree( root.left)而不是invertTree(
root.right),不然第一次是反的,第二次又反了,那不就又回去了么。
第五题
61. 旋转链表
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k **个位置。
这道题讲道理我看字还是没看懂,但是有图啊,这个图就一目了然与胸中有数了么。前面是链表,后
面是表示链表最后一位挪到第一位的次数。这可咋整呢,我先把最后一位拿出来,放到第一位吗?我
想了好久,最后一位拿出来容易,可是我要怎么才能得到没有最后一位的前四位呢,这难倒了我。以
我的知识储备,我试了好久,都以失败告终了。但是呢,既然最后一位往前放这么难,那我为什么不
把第一位放到最后呢,一开始我想的是,那我先遍历一次,找到长度,然后我再一次次把第一个放到
最后,比如说1→2→3→4→5,k=2,长度len=5,那我就三次把第一位放到最后不就好了,可是呢,
这样我需要3次拿出第一位,再放到最后,想想链表没办法直接在后面填值,还要再循环到最后一
位,再加上去,然后还要把原来的head存起来,不然就找不到。天啊,有这么麻烦。
于是,既然我想到了吧第一位放到最后一位,那为什么不直接把它们接起来呢,形成一个环形链表,
这样我就不用一个个拿走,再一个个加进去了。只要在需要的位置截断不就好了吗。这个问题不就解
决了吗。可是还有一个问题,如果我链表的长度没有k大,那我是不是就要数这个循环链表好几圈,
跑圈我可以,转圈数链表我不干,既然中间走满一圈的是多余的查询,那我们只要查不到一圈的就好了。
/**
* @param {ListNode} head
* @param {number} k
* @return {ListNode}
*/
var rotateRight = function(head, k) {
// 空值判断
if(head==null||head.next == null||k==0)return head
// 还是要转存一次
let link = head
let n = 1;
// 为的是能数出链表的长度
while (link.next) {
n++
link = link.next
}
// 然后算不到一圈,我们应该数几次
let len = n - k % n
// 如果正好是整圈数,那太好了了,我们什么都不用做
if (len == n) return head;
// 这时候我们就要把链表首尾相连
link.next = head
// 按我们需要的位置,截断
while (len) {
link = link.next
len--
}
// 用一个新的链表表示阶段后的链表
const res = link.next
// 截断之后,需要在尾部加上null
link.next = null
return res
};
就这样,说难不难,说简单我也是做了好久好久才做完的。这五道题的解法一定是不完美的,但是总归是我自己做出来的,成就感满满,现在已经凌晨两点半了快,要累死个人。大家最好不要熬夜啊,一起努力吧!