【ArchSummit架构师峰会】基于大模型的基础框架、中台、应用层等专题全覆盖 >>> 了解详情
写点什么

用户故事 | 刷算法面试题的 4 种思考方式

  • 2019-02-28
  • 本文字数:4368 字

    阅读完需:约 14 分钟

用户故事 | 刷算法面试题的4种思考方式

不管是为了求职面试,还是为了提高自己的算法基础能力,“刷算法题”都是每个程序员的必经之路。如何对待刷题?如何让刷题变得更高效?我们搜集了来自《算法面试通关 40 讲》的用户分享,他们也许可以给你一点启发。

@jason :最优解永远在探索中

刚看老师的课程没多久, 收获不多, 我就把自己的第一道练习题解题心得发出来吧。这个是老师讲的第一道 leetcode 算法题: 两数之和


题目:


给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。


示例:


给定 nums = [2, 7, 11, 15], target = 9


因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]


解答如下:


完成这道题,第一次花了半个小时,时间还是蛮长的,毕竟是自己完成的第一道 leetcode 算法题。不过之前确实算法练习得很少,解法很简单,用的是穷举法,连续两次遍历,时间复杂度为 O(n²)。


int[] twoSum(int[] nums, int target) {    if (nums.length < 2) {        return new int[0];    }    for (int i = 0; i < nums.length; i++) {        for (int j = i + 1; j < nums.length; j++) {            if (nums[i] + nums[j] == target) {                return new int[]{i, j};            }        }    }    return new int[0];}
复制代码


后来看了一下评论里其他同僚的解法,发现有很多很优化的解法。于是我把代码优化了一下,变成了下面这样:


int[] twoSum2(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<Integer, Integer>(SIZE); // 默认给 hashmap 初始化大小, 能够减少内部动态扩展空间, 复制速度造成的时间开销
// 将数组存入 hashmap
for (int i = 0; i < nums.length; i++) {
// 值为 key, 索引为 value
map.put(nums[i], i);
}
// 遍历数组里的每一个元素
for (int i = 0; i < nums.length; i++) {
// 计算需要从 hashmap 里面找出的元素
int complement = target - nums[i];
// 判断 hashmap 里面是否存在该元素, 并且该元素不能与当前 nums[i] 是同一个元素
if (map.containsKey(complement) && map.get(complement) != i) {
return new int[]{i, map.get(complement)};
}
}
return new int[0];
}
复制代码


将传入的数组转换成 hashmap, 利用 hashmap 查询速度快的优势 O(1),将整体查询时间降到 O(n),hashmap 通过以空间换取速度的方式,将查询速度提高到了 O(n),这里用到了分别两次的循环,虽然时间复杂度变成了 O(n),但实际上是两倍的 O(n)。


之后又思考了一下,发现一次循环也能解决问题,时间复杂度可以再次减半,变成真正的 O(n),于是便有了下面的代码。


int[] twoSum3(int[] nums, int target) {        Map<Integer, Integer> map = new HashMap<Integer, Integer>(SIZE); // 默认给 hashmap 初始化大小, 能够减少内部动态扩展空间, 复制速度造成的时间开销        for (int i = 0; i < nums.length; i++) {            int complement = target - nums[i];            if (map.containsKey(complement) && map.get(complement) != i) {                return new int[]{i, map.get(complement)};            }            map.put(nums[i], i);        }        return new int[0];    }
复制代码


之后我写了一个简单的测试案例来测试这 3 种算法的耗时,创建了一个长度为 10 万的数组,分别执行这 3 种算法,发现当数据量大的时候,第 2 种和第 3 种算法比第 1 种快了不是一个数量级。而且数据量越大,速度差异越明显:


int[] nums = new int[100 * 1000];
for (int i = 0; i < nums.length; i++) { if (i == nums.length - 1 - 1) nums[i] = 2; else if (i == nums.length - 1) nums[i] = 7; else nums[i] = 1; }
long start = System.currentTimeMillis(); //int[] indexResult = twoSum(nums, 9); // 数组长度 100000 耗时 1578 ms //int[] indexResult = twoSum2(nums, 9); // 数组长度 100000 耗时 20 ms int[] indexResult = twoSum3(nums, 9);// 数组长度 100000 耗时 14 ms
long end = System.currentTimeMillis(); System.out.printf("%s, %s", nums[indexResult[0]], nums[indexResult[1]]);
System.out.printf("\r\n 时间花费: %d ms", end - start);
复制代码


查看经典面试题:两数之和

@ elbowrocket:用理论指导实践

之前做 leetcode 的题目一直没什么思路,我从课程中学到了如何运用所学理论去思考。


下面是今天刚看的题目:滑动窗口的最大值


给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。返回滑动窗口最大值。


示例:


输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 输出: [3,3,5,5,6,7]


解释:


滑动窗口的位置 最大值[1 3 -1] -3 5 3 6 7 31 [3 -1 -3] 5 3 6 7 31 3 [-1 -3 5] 3 6 7 51 3 -1 [-3 5 3] 6 7 51 3 -1 -3 [5 3 6] 7 61 3 -1 -3 5 [3 6 7] 7


注意:你可以假设 k 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。进阶:你能在线性时间复杂度内解决此题吗?


下面是通过学习后得到的思路:


思路:


1、根据优先队列的概念,我们假设一个大顶堆,那么一开始的 [1,3,-1],这样一排列成堆的样子就是 3 在最上面,-1 在左下角,1 在右下角… 下一步就是 [3,-1,-3] 了,1 就要被挤开了,挤开了也不影响什么,-3 再加进来就好了。总之我们需要做的是:


(1)、维护我们的 Heap,也就是删除离开窗口的元素,加入新的元素。这里时间复杂度是 logK


(2)、Max->Top,就是让结果是堆顶的元素。复杂度是 O(1),最后整体的复杂度是 NLogK。有没有更好的解法?


2、直接用队列,而且是双端队列,也就是两边都能进能出的队列。首先就是入队列,每次滑动窗口都把最大值左边小的数给杀死,也就是出队,后面再滑动窗口进行维护,这样相当于每一个数走过场,时间复杂度就是 O(N*1),比思路 1 要小。


代码如下:


class Solution:    def maxSlidingWindow(self, nums, k):        """        :type nums: List[int]        :type k: int        :rtype: List[int]        """        # 严谨判断输入的数字是否合法        if not nums:return []        window, res = [], []        for i, x in enumerate(nums):            if i>=k and window[0] <= i-k: # 窗口滑动时的规律                window.pop(0)            while window and nums[window[-1]] <= x: # 把最大值左边的数小的就清除。                window.pop()            window.append(i)            if i >= k-1:                res.append(nums[window[0]])        return res
复制代码


希望能学习到更多东西。


查看白板理论讲解:滑动窗口的最大值

@梦想家罗西:学而时习之,不亦乐乎?

看老师的课程大概一周了,之前看数据结构和算法懵懵懂懂的,老师结合实例题,一下子清楚很多了,特别是动态规划哪一课,一下子茅塞顿开。



刷的一些算法题。


第一步:递归 + 暂存


function fib(n) {    let memo = [];    let r = null;    if (n <= 1) {      r = n;    } else if (memo[n]) {      r = memo[n];    } else {      memo[n] = fib(n - 1) + fib(n - 2);    }    return r;  }
复制代码


使用这种方法试了下 n=100 的时候直接挂了,效率依然很低。所以接下来第二步:


第二步:动态规划


function fib(n) {    let f = [0, 1];    for (let i = 2; i <= n; i++) {      f[i] = f[i - 2] + f[i - 1];    }    return f;  }
复制代码


其实矩阵相乘效率更高,等我学会了再来更新代码,学会了简单得动态规划已经很开心了。


戳此查看:让你茅塞顿开的动态规划

@Chenng:做题是一种享受,高效的思考模式受益终身

听完最后一课,突然有点不舍。本来学习算法的初衷是为了面试,现在发现做题本身就是一种享受。


课上学到很多收益终身的思考模式:


五种算法模式:


  • 递归


function recursion(level, param1, param2) {  // 递归终止条件  if (level > MAX_LEVEL) {    // 打印结果    return;  }
// 处理当前层级的逻辑 processData(level, data);
// 递归 recursion(level + 1, p1, p2);
// 如果需要,反向当前层级 reverseState(level);}
复制代码


  • 递归 DFS


const visited = new Set();function dfs(node, visited) {  visited.add(node);
// 处理当前的 node
for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; if (!visited.has(child)) { dfs(child, visited); } }}
复制代码


  • BFS


const visited = new Set();function bfs(grapg, start, end) {  const queue = [];  queue.push(start);  visited.add(start);
while (queue.length) { node = queue.pop(); visited.add(node);
process(node); nodes = generateRelatedNodes(node); queue.push(nodes); }}
复制代码


  • 二分查找


function binarySearch(arr, x) {  let left  = 0;  let right = arr.length - 1;
while(left <= right) { const mid = Math.floor((left + right) / 2);
if (x === arr[mid]) { return mid; }
if (x < arr[mid]) { right = mid - 1; continue; }
if (x > arr[mid]) { left = mid + 1; continue; } }
return -1;}
复制代码


  • DP 方程


// 状态定义const dp = [[]];
// 初始状态dp[0][0] = x;dp[0][1] = y;
// DP 状态推导
for (let i = 0; i <= n; i++) { for (let j = 0; j <= matchMedia; j++) { dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]); }}
return dp[m][n]
复制代码

四个切题步骤

  • Clarification:思考边界条件

  • Possible Solution:所有可能的解法、最优解

  • Coding

  • Test Cases

写在最后

课程的结束不是终点,而是起点,加油,开启自己成为真正工程师的道路。


查看课程回顾:面试切题四件套


感谢上面四位同学的精彩留言,欢迎大家在文末分享你的刷题故事和经验,我们一起进步。

专栏简介

我是覃超,作为 Facebook 早期员工 & 多年面试官,我对各大知名企业算法面试的考察点和面试套路,有非常清晰的理解以及丰富的第一手经验。在《算法面试通关 40 讲》这门课程里,我会帮你建立一套完整的算法切题思路,通过“白板演练 + 代码讲解”的方式,手把手带你掌握高效解题套路,彻底理解题目背后的考点,锻炼算法思维,让你在面试和平时的工作中大显身手。


学完这门课,你将收获以下四个方面:


1、常见算法知识点理论讲解


2、高频面试题目思路剖析


3、LeetCode 高效解题四步法


4、有效提升算法面试通过率


课程已更新完毕,共 62 讲,目前已有超过 10000 人加入学习,课程广受好评,期待你的加入!戳我立即订阅


2019-02-28 15:425673

评论

发布
暂无评论
发现更多内容

一文带你解读Volcano架构设计与原理

华为云开发者联盟

架构 Kubernetes 负载 Volcano 集群

15道类和对象面试题,快看看自己会几道

田维常

类集

2021最新Windows10环境下安装MacOS系统(黑苹果)亲测有效!!(VM安装黑苹果)

Z.

macos 黑苹果 windows vmware

大数据丨ClickHouse在京东能源管理平台的实践

京东科技开发者

数据库 大数据

Appium下的WDA使用个人开发者证书配置

行者AI

自动化测试

认识Nacos注册中心

登风

nacos

Serverless Kubernetes:理想,现实与未来

阿里巴巴云原生

Serverless 容器 运维 云原生 k8s

重温亮剑-感悟

superman

太牛了!美团Android开发工程师岗位职能要求,大厂面试题汇总

欢喜学安卓

android 程序员 面试 移动开发

顺利拿到OPPO公司Android架构师offer,Android跨进程通信导论,全套教学资料

欢喜学安卓

android 程序员 面试 移动开发

区块链如何帮助联合国支持全球教育?

CECBC

区块链

技术赋能教育,浅谈教育机构转型的制胜关键

华为云开发者联盟

音视频 在线教育

java中的类和object,其实没那么难~

田维常

类集

5G机遇 | 如何解决在核心场景的高并发、超低延迟需求?

VoltDB

数据库 5G 通信 VoltDB

怎么理解Kafka消费者与消费组之间的关系?

码农架构

Java 架构 消息队列 消息中间件

数字货币写进多地“十四五”规划纲要草案 专家建议扩大数字人民币试点范围

CECBC

数字经济

扎根CNCF社区贡献五年是怎样的体验?听听华为云原生开源团队的负责人怎么说

华为云开发者联盟

容器 Volcano cncf kubeedge 代码开发

12.4G阿里巴巴面经公开:技术笔记+视频讲解+简历模板,绝了!

996小迁

Java 架构 面试 程序人生

区块链有望被主流接纳的四个场景

CECBC

区块链

宅米网技术架构演进分析

Andy

从设计模式理解Vue响应式(多图警告)

coolFish(呔呆)

JavaScript vue.js 响应式 大前端 设计模式

Invalid bound statement (not found)

任广印

Java MyBatisPlus

硬核!我花5小时肝出这篇Redis缓存解决方案,带你起飞!

数据库 redis 缓存架构

每日知识总结

country

幕后故事 | YRCloudFile助力顶级视效制作公司MORE VFX打造视觉盛宴

焱融科技

高性能 存储 焱融科技 3D渲染 影视制作

企业级低代码平台的选型和建设思考

李小腾

音视频传输协议众多, 5G时代不同业务应该如何选择?

华为云开发者联盟

5G 音视频 直播 流媒体

666666666666666666666

Paul

大数据

个人web分享92道JavaScript面试题附加回答

我是哪吒

程序员 面试 大前端 程序媛

Kubernetes生产环境最佳实践

xcbeyond

Kubernetes 容器 28天写作

工具介绍 | 百度开源Server-Agent:高性能、高效率的任务调度执行引擎

百度开发者中心

开源

用户故事 | 刷算法面试题的4种思考方式_文化 & 方法_覃超_InfoQ精选文章