前言
1.2021年已过半,“金九银十”笔试即将要开始,整理一些算法题一起学习。
2.我统一使用JavaScript(V8/Node)解答,都已经调试通过。
3.一起加油!一起进步!
1.排序
以下两个函数是排序中会用到的通用函数,就不一一写了
function checkArray(array) {
if (!array || array.length <= 2) return }
function swap(array, left, right) {
let rightValue = array[right]
array[right] = array[left]
array[left] = rightValue
}
2.时间复杂度
通常使用最差的时间复杂度来衡量一个算法的好坏。
常数时间 O(1) 代表这个操作和数据量没关系,是一个固定时间的操作,比如说四则运算。
对于一个算法来说,可能会计算出如下操作次数 aN + 1 , N 代表数据量。那么该算法的时间复杂度就
是 O(N)。因为我们在计算时间复杂度的时候,数据量通常是非常大的,这时候低阶项和常数项可以忽略
不计。
当然可能会出现两个算法都是 O(N) 的时间复杂度,那么对比两个算法的好坏就要通过对比低阶项和常
数项了。
3.位运算
位运算在算法中很有用,速度可以比四则运算快很多。
在学习位运算之前应该知道十进制如何转二进制,二进制如何转十进制。这里说明下简单的计算方式
- 十进制 33 可以看成是 32 + 1 ,并且 33 应该是六位二进制的(因为 33 近似 32 ,而 32 是 2
的五次方,所以是六位),那么 十进制 33 就是 100001 ,只要是 2 的次方,那么就是 1否则都为 0 - 那么二进制 100001 同理,首位是 2^5 ,末位是 2^0 ,相加得出 33
4.左移 <<
10 << 1 // -> 20
左移就是将二进制全部往左移动, 10 在二进制中表示为 1010 ,左移一位后变成 10100 ,转换为十
进制也就是 20,所以基本可以把左移看成以下公式 a * (2 ^ b)
5.算数右移 >>
10 >> 1 // -> 5
算数右移就是将二进制全部往右移动并去除多余的右边, 10 在二进制中表示为 1010 ,右移一位后变
成 101 ,转换为十进制也就是 5,所以基本可以把右移看成以下公式 int v = a / (2 ^ b)
右移很好用,比如可以用在二分算法中取中间值
13 >> 1 // -> 6
6.最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 动态规划
res = cur = nums[0]
for i in range(1, len(nums)):
cur = max(nums[i], cur + nums[i])
res = max(res, cur)
return res
动态规划。时间复杂度O(n), 空间复杂度 O(1)。
7.链表
面试题:反转单向链表
题目需要将一个单向链表反转。思路很简单,使用三个变量分别表示当前节点和当前节点的前后节点,虽然这题很简单,但是却是一道面试常考题
以下是实现该算法的代码
var reverseList = function(head)
{
// 判断下变量边界问题
if (!head || !head.next) return head
// 初始设置为空,因为第一个节点反转后就是尾部,尾部节点指向 null
let pre = null
let current = head
let next
// 判断当前节点是否为空
// 不为空就先获取当前节点的下一节点
// 然后把当前节点的 next 设为上一个节点
// 然后把 current 设为下一个节点,pre 设为当前节点
while(current) {
next = current.next
current.next = pre
pre = current
current = next
}
return pre
};
8.二叉树的先序,中序,后序遍历
先序遍历表示先访问根节点,然后访问左节点,最后访问右节点。
中序遍历表示先访问左节点,然后访问根节点,最后访问右节点。
后序遍历表示先访问左节点,然后访问右节点,最后访问根节点。
9.递归实现
递归实现相当简单,代码如下
function TreeNode(val)
{
this.val = val;
this.left = this.right = null;
}
var traversal = function(root) {
if (root)
{
// 先序
console.log(root);
traversal(root.left);
// 中序
// console.log(root);
traversal(root.right);
// 后序
// console.log(root);
}
};
10.树的深度
面试题:树的最大深度(题目需要求出一颗二叉树的最大深度)
以下是算法实现
var maxDepth = function(root)
{
if (!root) return 0
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1
};
11.动态规划
动态规划背后的基本思想非常简单。就是将一个问题拆分为子问题,一般来说这些子问题都是非常相似的,那么我们可以通过只解决一次每个子问题来达到减少计算量的目的。
一旦得出每个子问题的解,就存储该结果以便下次使用。
12.合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
以下是实现该算法的代码
function mergeTwoLists(l1, l2) {
if (l1 = null && l2 == null) {
return null;
}
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}