[路飞]_前端算法第三弹

307 阅读9分钟

「这是我参与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
};

就这样,说难不难,说简单我也是做了好久好久才做完的。这五道题的解法一定是不完美的,但是总归是我自己做出来的,成就感满满,现在已经凌晨两点半了快,要累死个人。大家最好不要熬夜啊,一起努力吧!