牛客算法题

[TOC]

入门

简单

运行时间与排名成反比

NC3 链表中环的入口结点

题述

给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

数据范围: n -10000n≤10000,1<=结点值<=100001<=结点值<=10000

要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

例如,输入{1,2},{3,4,5}时,对应的环形链表如下图所示:

img

可以看到环的入口结点的结点值为3,所以返回结点值为3的结点。

解体思路

  1. 这题我们可以采用双指针解法,一快一慢指针。快指针每次跑两个element,慢指针每次跑一个。如果存在一个圈,总有一天,快指针是能追上慢指针的。

  2. 如下图所示,我们先找到快慢指针相遇的点,p。我们再假设,环的入口在点q,从头节点到点q距离为A,q p两点间距离为B,p q两点间距离为C。

  3. 因为快指针是慢指针的两倍速,且他们在p点相遇,则我们可以得到等式 2(A+B)= A+ B + n(C+B)

  4. 由3的等式,我们可得,C = A。(C有肯是转了好几个小圈子)

    • 论证:3等式化简得A=(n-1)(C+B)+C

    • 论证:慢指针转一圈内必然会和快指针相遇

  5. 这时,因为我们的slow指针已经在p,我们可以新建一个另外的指针,slow2,让他从头节点开始走,每次只走下一个,原slow指针继续保持原来的走法,和slow2同样,每次只走下一个。

  6. 我们期待着slow2和原slow指针的相遇,因为我们知道A=C,所以当他们相遇的点,一定是q了。

  7. 我们返回slow2或者slow任意一个节点即可,因为此刻他们指向的是同一个节点,即环的起始点,q。

图片说明

NC7 买卖股票的最好时机(一)

参考数据结构与算法分析java语言描述里的论述

大概思路

​ 设置margin,一旦margin大于max,就重置差值起点,继续寻找最大值;

NC9 二叉树中和为某一值的路径(一)

解法1:递归算法

时间复杂度:O(N):N是节点的个数

空间复杂度:O(H):H是树的高度


NC11 将升序数组转化为平衡二叉搜索树

描述

给定一个升序排序的数组,将其转化为平衡二叉搜索树(BST).

平衡二叉搜索树指树上每个节点 node 都满足左子树中所有节点的的值都小于 node 的值,右子树中所有节点的值都大于 node 的值,并且左右子树的节点数量之差不大于1

数据范围:0 \le n \le 100000≤n≤10000,数组中每个值满足 |val| \le 5000∣val∣≤5000 进阶:空间复杂度 O(n)O(n) ,时间复杂度 O(n)O(n)

例如当输入的升序数组为[-1,0,1,2]时,转化后的平衡二叉搜索树(BST)可以为{1,0,2,-1},如下图所示:

img

或为{0,-1,1,#,#,#,2},如下图所示:

img

返回任意一种即可。

注意点:

  1. 已经排序,所以使用二分法

  2. 二分法除以二使用位运算更佳

  3. 基准情形有限使用start>end

NC16 对称的二叉树

描述

给定一棵二叉树,判断其是否是自身的镜像(即:是否对称) 例如: 下面这棵二叉树是对称的 img 下面这棵二叉树不对称。 img

数据范围:节点数满足

0 \le n \le 10000≤n≤1000,节点上的值满足 |val| \le 1000∣val∣≤1000

要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

备注:

你可以用递归和迭代两种方法解决这个问题

错误思路

开始构想

  1. 先进入最底层,从最底层开始判断是否平衡

  2. 基准情形为全空,用bottom判断

存在的问题

  1. 在不进入最低层时候就能判断出是否平衡

  2. 失败的原因貌似是因为要去最后一层会生成指数倍的节点,出现溢出

==正确思路==

方法一、递归:

如果一个树的左子树与右子树镜像对称,那么这个树是对称的。

img

因此,该问题可以转化为:两个树在什么情况下互为镜像?

如果同时满足下面的条件,两个树互为镜像:

  1. 它们的两个根结点具有相同的值。

  2. 每个树的右子树都与另一个树的左子树镜像对称。

img

就像人站在镜子前审视自己那样。镜中的反射与现实中的人具有相同的头部,但反射的右臂对应于人的左臂,反之亦然。

顺藤摸瓜:

一次处理一对镜像节点,先处理内,后处理外

Java 代码实现

方法二、迭代:

除了递归的方法外,我们也可以利用队列进行迭代。队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像。最初,队列中包含的是 root 以及 root。该算法的工作原理类似于 BFS,但存在一些关键差异。每次提取两个结点并比较它们的值。然后,将两个结点的左右子结点按相反的顺序插入队列中。当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。

Java 代码实现

复杂度分析

  • 时间复杂度:[公式]。因为我们遍历整个输入树一次,所以总的运行时间为 [公式],其中 [公式] 是树中结点的总数。

  • 空间复杂度:搜索队列需要额外的空间。在最糟糕的情况下,我们不得不向队列中插入 [公式] 个结点。因此,空间复杂度为 [公式]

NC19 连续子数组的最大和

题目描述

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n). 示例1 输入:[1,-2,3,10,-4,7,2,-5] 返回值:18 说明:输入的数组为{1,-2,3,10,—4,7,2,一5},和最大的子数组为{3,10,一4,7,2},因此输出为该子数组的和 18。

解题思路

**方法1:**连续的子数组,即数组中从i下标到j下标(0<=i<=j<数组长度)的数据,想要获得所有的子数组和,可以通过暴力法,两次循环获得,但时间复杂度为O(n^2),效率不高。

**方法2:**动态规划,设动态规划列表 dp,dp[i] 代表以元素 array[i] 为结尾的连续子数组最大和。 状态转移方程: dp[i] = Math.max(dp[i-1]+array[i], array[i]); 具体思路如下: 1.遍历数组,比较 dp[i-1] + array[i] 和 array[i]的大小; 2.为了保证子数组的和最大,每次比较 sum 都取两者的最大值; 3.用max变量记录计算过程中产生的最大的连续和dp[i]; 图片说明

**方法3:**我们可以简化动态规划,使用一个变量sum来表示当前连续的子数组和,以及一个变量max来表示中间出现的最大的和。

代码实现

方法1:暴力法,时间复杂度O(n^2),空间复杂度O(1)

方法2:动态规划,时间复杂度O(n),空间复杂度O(n)

==方法3:优化动态规划,时间复杂度O(n),空间复杂度O(1)==!!!!

NC22 合并两个有序的数组

注意对数组的访问尽可能避免越界

NC32 求平方根

我的思路

二分法,但是mid*mid会溢出,所以转换为long类型

改良二分法

NC34 求路径

我的想法

动态规划题型

1
1
1

1

1+1=2

1+2=3

1

2+1=3

3+3=6

NC61 两数之和 未学习完全

给出一个整型数组 numbers 和一个目标值 target,请在数组中找出两个加起来等于目标值的数的下标,返回的下标按升序排列。

(注:返回的数组下标从1开始算起,保证target一定可以由数组里面2个数字相加得到)

要求:空间复杂度 O(n)O(n),时间复杂度 O(nlogn)O(nlogn)

我的想法

暴力算法,时间超时

哈希算法

用空间换时间

NC66 两个链表的第一个公共结点

我的想法

先遍历找到长短差,然后从同一位置开始找相同节点

其他思路

巧妙:交叉指针,一定会在某个点相遇,交点或者null

NC69 链表中倒数最后k个结点

  • 输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。

  • 如果该链表长度小于k,请返回一个长度为 0 的链表。

  • 要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

  • 进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

我的思路

使用数组记录

时间复杂度O(n),空间复杂度O(n)

其他思路

双指针,第一个与第二个间隔k,第一个到尾部,第二个位置便是最后k个节点

NC70 单链表的排序

使用冒泡算法,两两交换,理论很简单,但链表实现起来有些难度

可优化的点:控制什么时候结束

NC192 二叉树的后序遍历

非递归算法--两个栈

思路: 这是一个非常巧妙的算法,后序遍历的顺序为左、右、根,反过来就是根、右、左,类似先序遍历的根、左、右, 先序遍历的非递归很简单:根节点入栈,出栈并依次判断右、左子树,有右入右,有左入左,然后循环出栈判断,后续遍历的反向遍历就是在 出栈判断时,先入左,后入右

非递归算法-一个栈

PS:只记录需要记录的

Last updated