bytedance面试题1
1. URL反转
【Question】
给定形如 www.toutiao.com
的 URL,将其转换成 com.toutiao.www
的形式,要求必须原地操作
【Answer】
1. 原地全部翻转一遍; 2. 遍历遇到".",继续翻转该部分字符串; 该题目重点考察编码,需要保证代码简洁,要不不允许使用字符串库函数
2. 判断单向链表是否有环
【Question】
判断一个链表中是否有环
例如:A->B->C->D->B->C->D
D指向B形成环
要求:在空间复杂度O(1)的情况下,时间复杂度最小
【Answer】
创建两个指针slow,fast,同时指向这个链表的头节点。
然后让两个指针开始循环移动
slow每次向下移动一个节点,fast每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环
3. 给定两个链表,求它们交叉节点
【Question】
- 已知两个链表, 从某个节点开始就交叉了
- 已知这两个链表的头节点, 求出交叉的节点
【Answer】
1. 依次遍历两个链表分别得到链表的长度M和N 2. 然后让长的那个一个从头开始走|M-N|的步数; 3. 两个指针同时走, 直到碰头
4. 判断一个IP是否是国内
【Question】 如何快速判断一个ip地址是否属于国内?已知db中有几十万个国内ip地址段
【Answer】
1. 将ip地址通过位移运算转成int 2. 对ip地址进行排序(可以考察任意一种排序算法) 3. 二分查找
5. 用户在线峰值
【Question】 已知一天内用户登录登出的日志(数据量较大),求这一天用户在线的最大峰值和持续时间段
- 日志包含字段(userid, login_time, logout_time)
- 登录登出时间精确到秒
【Answer】
可以将这一天看成0-24*3600的离散的时间点,构造一个dict 每个用户在login_time对应value+1,logout_time对应value-1 得到用户在线数量的变化趋势,然后再遍历此dict求和 难点: - 想不到先求变化趋势 - 峰值时间段可能存在多个 ``` def get_max(logs): log_count = {} for log in logs: login_time = log['login_time'] logout_time = log['logout_time'] log_count[login_time] = log_count.setdefault(login_time, 0) + 1 log_count[logout_time] = log_count.setdefault(logout_time, 0) - 1 max, current_users, start, end, is_max, timeline = (0, 0, 0, 0, False, []) keys = log_count.keys() keys.sort(lambda a, b: a - b) for time_node in keys: current_users = current_users + log_count[time_node] if current_users > max: max = current_users start = time_node is_max = True elif current_users < max: if is_max: end = time_node is_max = False else: if is_max: end = time_node else: timeline.append((start, end)) start = time_node is_max = True timeline.append((start, end)) return max, timeline ```
6. 股票买卖问题
【Question】 给定一个数组代表股票每天的价格,请问只能买卖一次的情况下,最大化利润是多少?日期不重叠的情况下,可以买卖多次呢? 输入: {100, 80, 120, 130, 70, 60, 100, 125} 只能买一次:65(60 买进,125 卖出) 可以买卖多次: 115(80买进,130卖出;60 买进,126卖出)
提示:不用输出买卖的序列,只需要得到最大利润
【Answer】
1. 对于只能买一次的情况: ``` public static int maximumProfit(int[] stockPrices) { int profit = 0; int minimumPrice = Integer.MAX_VALUE; /* * 对于给定的一天,最大利润等于 - * max(昨天为止的最大利润, 当天的价格 - 之前的最小价格) */ for(int i = 0; i < stockPrices.length; i++) { profit = Math.max(profit, stockPrices[i] - minimumPrice); minimumPrice = Math.min(stockPrices[i], minimumPrice); } return profit; } ``` 2. 对于可以买卖多次的情况,累积递增序列的差就可以了: ``` public static int maximumProfit2(int[] stockPrices) { int totalProfit = 0; for(int i=1; i 0){ totalProfit += currentProfit; } } return totalProfit; } ```
7. 输出给定数字下一个比它大的数字
【Question】 比如数字:1234, 输出 1243
比如 1243,则输出 1324
【Answer】
用 1243 为例: 1. 从右向左扫描,找到第一个不是升序的数字,比如 2 2. 在 2 的右边,找到比它大的最小的数,是 3 3. 交换 2和 3,得到 1342 4. 把现在 3 右边的所有数字从大到小排序,得到 1324 (如果是排序则是 O(nlogn), 其实逆序就行了)
8. Path Sum
【Question】 给定一个二叉树和一个数字n,判断二叉树中是否有一个路径上的数字之和等于给定的数字n
For example: Given the below binary tree and sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1 return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.
【Answer】
public class Solution { public boolean hasPathSum(TreeNode root, int sum) { if(root == null) return false; int left = sum - root.val; if(root.left == null && root.right == null && left == 0) { return true; } return hasPathSum(root.left, left) || hasPathSum(root.right, left); } } 可以进一步问,输出所有和等于给定数字n的path
9. 单链表每隔k个元素做一次反转
【Question】 给定一个链表,每隔k个元素做一次反转
Example: Inputs: 1->2->3->4->5->6->7->8->NULL and k = 3 Output: 3->2->1->6->5->4->8->7->NULL.
Inputs: 1->2->3->4->5->6->7->8->NULL and k = 5 Output: 5->4->3->2->1->8->7->6->NULL.
【Answer】
Node* rollback(Node *&head, int k) { Node *pre = NULL; Node *next = NULL; Node *curr = head; int count = 0; while (curr != NULL&&countnext; curr->next = pre; pre = curr; curr = next; count++; } if (curr != NULL) { head->next = rollback(next, k); } return pre; }
10. 用两个栈实现一个队列
【Question】 用两个堆栈模拟队列的功能,实现push,pop,count三个方法
【Answer】
简单的做法:栈s1和s2,始终维护s1作为存储空间,以s2作为临时缓冲区,push直接进s1,pop时s1导入s2,栈顶出栈,导回s1 优化做法:入队时,将元素压入s1,出队时,判断s2是否为空,如不为空,则直接弹出顶元素;如为空,则将s1的元素逐个“倒入”s2,把最后一个元素弹出并出队
11. 赛马求最快N匹
【Question】
条件:
1.64匹马
2.8个赛道
3.每次比赛只能知道比赛结果名次,不能知道具体时间
求:
用最少的比赛次数,找出最快的4匹
【Answer】
1.每次比赛至少能淘汰4匹(树形淘汰算法),因此淘汰60匹,至少需要15次比赛,回答15,是最差答案
2.如果能回答出12次的(经过加速之后的简单排序),为次优解:
1)先每8个一组,一共8组比8次
2)从第9次开始
*1.先取每组第一名,每次比赛,找出最快1匹,取出
*2.将最快这匹对应的组里次快的加入比赛,一共4次,找出最快4匹
3.如果能答出特定情况下10次,最差11次,为最优解(剪枝算法):
1)先每8个一组,一共8组比8次
2) 第9次,先取每组第一名,进行比赛,找出前四名
3) 第10次,将前4名对应的组中的第2名加入比赛,一共8匹,比赛一次,如果对应的前四名没发生变化,说明前4名就是最快4名
4)第11次
*1.假设有一个组里的第2名,进入前四名:
①有一组的第1名被挤出前4名,该组所有的候选马无法进入前4名
②另外两组第2名之后无法进入前4名
因此,4(第2名在第10次进入前4名的组对应的前4名)+2(还在前4名的另外两个第1名)=6匹马进行比赛,决出前4名,即为最终答案
*2假设有两个组的第2名,进入前四名:
①则另外两组都无法进入前4名,而还在前4名的两组的前4名4+4=8,跑一次,最终能得到最快4名
12. LRU cache
【Question】 实现一个LRU过期算法的KV cache, 所有KV过期间隔相同, 满足如下性质:
- 最多存储n对KV;
- 如果大于n个, 则随意剔除一个已经过期的KV;
- 如果没有过期的KV, 则按照LRU的规则剔除一个KV;
- 查询时如果已经过期, 则返回空;
【Answer】
用一个map来维护K->V索引; 用一个双向链表list来维护K, 保证越靠前的K时间越早; 用一个map来维护K->list.node, 该map用于删除时使用 剔除时, 从list取出最老的K, 从map中剔除即可; 最老的那个, 肯定最先过期; 如果都没有过期, 也只能剔除最老的那个; 因此在解决剔除与过期时, 完全同上; 为了解决过期返回空, 有比较简单的办法是在每次查询前先过期一次; 解法大致同上; 为了实现LRU, 可在每次get后, 将该K从cache中删除并重新插入一遍;
13. 求数组的最大区间和
【Question】
输出一个 int 型数组的最大连续子数组(所有元素加和最大)各个元素之和
保证数组中至少有一个正数
例:
输入:{1,2,5,-7,8,-10}
输出:9 (子数组为: {1,2,5,-7,8})
【Answer】
复杂度 `O(n)` 的算法 ``` int _tmain(int A[], _TCHAR* argv[]) { int array_length = sizeof(A) / sizeof(A[0]); int sum = 0; int thisSum = 0; for (int i = 0; i < array_length; i++) { thisSum += A[i]; if (thisSum > sum) { sum = thisSum; } else if (thisSum < 0) { thisSum = 0; } } printf("%d",sum); return 0; } ```
14. 最长无重复子串
【Question】
无重复子串指:子串中每个字符都不相同 例如:s = 'aaabcdddd' 最长的无重复子串为'abcd'长度为4
【Answer】
用两个指针left、right指向子串的起止位置 通过set记录是否有重复元素,只要没有重复都可以移动right,更新最长子串长度 如果有重复,移动left,并从set里移除对应的字符
def max_sub_len(s): existed = set() res = 0 left, right = 0, 0 while right < len(s): if s[right] not in existed: existed.add(s[right]) res = max(res, len(existed)) right += 1 else: t.remove(s[left]) left += 1 return res
15. 服务循环依赖检测
【Question】
在微服务的架构下,公司内部会有非常多的独立服务。
服务之间可以相互调用,往往大型应用调用链条很长,如果出现循环依赖将出现非常恶劣的影响。
对于一个具体应用,已知各个服务的调用关系(即依赖关系),请判断是否存在循环调用。
输入:
一组服务依赖关系list,('A', 'B') 表示 A 会调用 B 服务
service_relations = [('A', 'B'), ('A', 'C'), ('B', 'D'), ('D', 'A')]
输出:
由于存在 A - B - D - A 故存在循环依赖,返回True;反之如果不存在,返回False
Follow up:
1. 如果有多个环,请都检测出来
2. 返回每个环中的服务名
【Answer】
可以采用拓扑排序 或者 DFS思路解决
16. 比较版本号
【Question】
比较两个版本号 version1 和 version2。
如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1, 除此之外返回 0。
你可以假设版本字符串非空,并且只包含数字和 . 字符。
. 字符不代表小数点,而是用于分隔数字序列。
例如,2.5 不是“两个半”,也不是“差一半到三”,而是第二版中的第五个小版本。
你可以假设版本号的每一级的默认修订版号为 0。例如,版本号 3.4 的第一级(大版本)和第二级(小版本)修订号分别为 3 和 4。其第三级和第四级修订号均为 0。
示例 1:
输入: version1 = "0.1", version2 = "1.1"
输出: -1
示例 2:
输入: version1 = "1.0.1", version2 = "1"
输出: 1
示例 3:
输入: version1 = "7.5.2.4", version2 = "7.5.3"
输出: -1
示例 4:
输入:version1 = "1.01", version2 = "1.001"
输出:0
解释:忽略前导零,“01” 和 “001” 表示相同的数字 “1”。
示例 5:
输入:version1 = "1.0", version2 = "1.0.0"
输出:0
解释:version1 没有第三级修订号,这意味着它的第三级修订号默认为 “0”。
【Answer】
方法1:分割+解析,两次遍历,线性空间
方法2:双指针,一次遍历,常数空间
17. 单链表(奇数位升序,偶数位降序)的排序
【Question】 单链表,奇数位升序,偶数位降序,现在要求整体排成全局升序 输入:1->200->10->120->30->8->88->4 输出:1->4->8->10->30->88->120->200
【Answer】
思路:链表可以随便拆、组合 先把奇数和偶数拆开,形成两个链表,一个升序和一个降序 1->10->30->88 200->120->8->4 然后将降序的反转,再合并成一个列表
18. 蛇形打印二叉树
【Question】 输入一棵二叉树,比如:
0
1 2
3 4 5 6
7 8 9
将它蛇形输出,结果如下: 0,1,2,6,5,4,3,7,8,9
【Answer】
1. 依赖栈记录每层节点值:层次便利,按偶数层(根是 0 层)从右到左,奇数层从左到右输出,时间空间复杂度都是 O(n),n 是节点数 2. 不依赖栈,递归:d 是层数,for i from 0 to d, 如果 i 是偶数,后序遍历二叉树的 0到i层,输出第i层的节点;如果i是奇数,先序遍历 0到 i 层,也只输出第i层节点。空间复杂度是 O(d) 即递归深度,时间复杂度是 o(d^2) 因为 0层节点会被访问 d 次,1 层节点 d-1 次,以此递推。
19. 冒泡排序和快速排序的复杂度区别是什么,如何实现?
【Question】
/**
* Quick Sort
**/
function quickSort(arr) {
// 补全代码
}
console.log(quickSort([1, 3, 10, 6, 2, 8, 7])) // output: [1, 2, 3, 6, 7, 8, 10]
/**
* BubbleSort
**/
function bubbleSort(arr) {
// 补全代码
}
console.log(bubbleSort([1, 3, 10, 6, 2, 8, 7])) // output: [1, 2, 3, 6, 7, 8, 10]
【Answer】
>1. 快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 >1. 冒泡排序算法的运作如下:(从后往前)比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 ```javascript /** * Quick Sort O(NLogN) **/ function quickSort(arr) { const res = []; if (arr.length <= 1) { return arr; } const leftArr = []; const rightArr = []; const q = arr[0]; for (let i = 1, l = arr.length; i < l; i++) { if (arr[i] > q) { rightArr.push(arr[i]); } else { leftArr.push(arr[i]); } } return res.concat(quickSort(leftArr), [q], quickSort(rightArr)); } /** * BubbleSort O(N*N) **/ function bubbleSort(arr) { for (let i = 0, l = arr.length; i < l - 1; i++) { for (let j = i + 1; j < l; j++) { if (arr[i] > arr[j]) { let tem = arr[i]; arr[i] = arr[j]; arr[j] = tem; } } } return arr; } ```
20. 岛屿数量
【Question】
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。
【Answer】
我们可以将二维网格看成一个无向图,竖直或水平相邻的 1 之间有边相连。
为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0
最终岛屿的数量就是我们进行深度优先搜索的次数
class Solution { private: void dfs(vector<vector
>& grid, int r, int c) { int nr = grid.size(); int nc = grid[0].size(); grid[r][c] = '0'; if (r - 1 >= 0 && grid[r-1][c] == '1') dfs(grid, r - 1, c); if (r + 1 < nr && grid[r+1][c] == '1') dfs(grid, r + 1, c); if (c - 1 >= 0 && grid[r][c-1] == '1') dfs(grid, r, c - 1); if (c + 1 < nc && grid[r][c+1] == '1') dfs(grid, r, c + 1); } public: int numIslands(vector<vector<char>>& grid) { int nr = grid.size(); if (!nr) return 0; int nc = grid[0].size(); int num_islands = 0; for (int r = 0; r < nr; ++r) { for (int c = 0; c < nc; ++c) { if (grid[r][c] == '1') { ++num_islands; dfs(grid, r, c); } } } return num_islands; } }; </code></pre> </pre> </details> --- ### 21. 把中文数字转成int数字 【Question】
在中文页面解析、中文数据处理中,常常遇到用中文表示的数字,例如:五千三百万零五百零一。
我们一般需要把它转化成int型整数,进行实际存储和使用。 请完成一亿(不含)以内的中文数字到int整数的转换
--- ### 22. Hash表设计 【Question】 常规的hash表设计及变通。问题由浅入深递进 1. 基本的原理,负载因子,扩容的原理 2. 假设内存受限4G,hash表已经占用3G内存,怎么使用剩下的那一个G的内存空间 3. 怎么在文件中设计类似于hash表的结构,能够在文件中快速查找一个key/value对【Answer】
// 递归解法 def cn2digital(number): name2val = {u'一': 1, u'二': 2, u'三': 3, u'四': 4, u'五': 5, u'六': 6, u'七': 7, u'八': 8, u'九': 9} unit2count = {u'十': 10, u'百': 100, u'千': 1000, u'万': 10000} for unit in [u'万', u'千', u'百', u'十']: if unit in number: n1, n2 = number.split(unit) return cn2digital(n1) * unit2count.get(unit) + cn2digital(n2) if not number: return 0 for c in number: if c == u'零': continue return name2val.get(c) // 非递归解法 def cn2digital(number): name2val = {u'一': '1', u'二': '2', u'三': '3', u'四': '4', u'五': '5', u'六': '6', u'七': '7', u'八': '8', u'九': '9'} unit2count = {u'十': 1, u'百': 2, u'千': 3, u'万': 4} res = [] base_count = 0 for num in number[::-1]: if num in name2val: res.append(name2val.get(num)) continue zero_count = 0 if num in unit2count: zero_count = max(0, unit2count.get(num) + base_count - len(res)) if num == u'万': base_count += 4 for _ in range(zero_count): res.append('0') return 0 if not res else int(''.join(res[::-1])) assert cn2digital(u'一万零一') == 10001 assert cn2digital(u'三千五百万') == 35000000 assert cn2digital(u'三千五百一十万') == 35100000 assert cn2digital(u'三千五百零一万') == 35010000 assert cn2digital(u'三千五百零一万零五百') == 35010500 assert cn2digital(u'三千五百零一万五千五百五十五') == 35015555 assert cn2digital(u'一百万') == 1000000 assert cn2digital(u'二百三十四万三千四百九十三') == 2343493
--- ### 23. 1-n数字字典序第k大 【Question】 给你一个数字n(n < 1e9), 再给你一个数字k(k < n), 要求你找到1, 2, 3, ... n按照字典序排序后, 第k大的数字; 如, n = 15, k = 7; 那1 ~ 15按照字典序排序为: 1, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5, 6, 7, 8, 9; 则答案为15;【Answer】
1. 扩容时注意关注 rehash 过程 2. 类似于多个Hash组成一个List 3. 类似于leveldb的思路--- ### 24. 在数组中找出和值为给定值的两个数 【Question】 输入一维数组array和n,找出和值为0的任意两个元素。例如: ``` python array = [2, 3, 1, 10, 4, 30] n = 31 ``` 则结果应该输出[1, 30] 顺序不重要 如果有多个满足条件的,返回任意一对即可【Answer】
利用字典树的思想; 我们假设有这么一棵树, 每个节点都要10个儿子, 10条到儿子的边分别对应数据0~9; 那么我们在这棵树上, 对边按照0~9的顺序进行DFS, 当走到第k个节点时, 该节点对应的数字既为我们的第k大字典序数字;--- ### 25. 老虎吃羊问题 【Question】 在岛上有100只老虎和1只羊,老虎可以吃草,但他们更愿意吃羊。 假设: A:每次只有一只老虎可以吃样,而且一旦他吃了羊,他自己就变成羊。 B:所有的老虎都是聪明而且完全理性的,他们的第一要务是生存。 问最后这只羊会不会被吃?如果是n只老虎和一只羊呢?【Answer】
解法1: 本题容易想到用哈希表,迭代一次边建表边查找n - array[i]是否在hash_table中即可。 该方法空间开销比较大 解法2:先对数组做排序,然后首尾两个指针求和,如果小于n则左指针右移,如果大于n则右指针左移。 该方法时间复杂度O(nlogn) 推荐考察解法2,附带做了排序算法的考察--- ### 26. 爬虫url去重-多线程并发 【Question】 用爬虫抓取网页时, 一个较为重要的问题, 就是对爬去的网页去重; 请你详细的设计一种数据结构, 用来检验某个URL之前是否已经被爬取过; 并给出每次检验的复杂度, 以及整体的空间复杂度; 一般爬虫在实现时, 都会利用多线程并发的爬取; 现在需要你升级一下之前的实现, 以保证并发安全;【Answer】
思路:先simplify。 - 1只老虎,肯定吃。 - 2只老虎肯定不吃,否则就被另一只吃了。 - 3只老虎,如果一只老虎吃掉了羊,问题就转换为2只老虎和1只羊的情况,显然另外两种老虎不敢轻举妄动。所以羊会被吃。 - 4只老虎,如果某一只老虎吃了羊,问题转化为3只老虎和1只羊的问题,它肯定会被接下来的某一只吃掉,然后其他两只只能等着。所以4只老虎,大家都不敢吃羊。 这样归纳,我们就可以发现如果老虎数目是奇数,那么羊肯定被吃,如果是偶数,那么羊肯定不会被吃。--- ### 27. 蓄水问题, 1维 【Question】 给定一个一维数组用于描述一个海拔,相邻的海拔高度不同,则下雨后低洼海拔的洼地会有积水,假设雨水足够,能够填满所有低洼地段,计算下雨后所有低洼地段总蓄水量。 例如给定数组为: 5, 2, 1, 4, 3 则:所有低洼地段蓄水为量为 5【Answer】
通常来说, 不需要改变数据结构本身, 只需要在其外围包裹一些简单的操作, 就能大大提高其并发度; 比如可以根据URL的后几位, 进行hash分桶; 注意这里选取URL的后几位, 而不是前几位, 是为了让hash更加均匀, 因为同个域名下的前缀大多是相同的; 然后每个桶内维护一个上述实现的去重的数据结构;--- ### 28. 轮流抛硬币问题 【Question】 # A和B玩抛硬币游戏,AB轮流抛一枚硬币,谁先抛到正面谁就获胜并结束游戏,硬币两面均匀。A先抛,请问A获胜的概率是多少?【Answer】
定义左极高点: 该点左边最高的那个点; 定义右极高点: 该点右边最高的那个点; 于是每个点的蓄水高度为: min(左极高点高度, 右极高点高度) - 该点高度,累加每个点的高度即可;所有点的左右极点可以分别通过一次向右和向左的扫描得到; 算法复杂度为 O(n)--- ### 29. 查找第一个缺失的正整数 【Question】 查找第一个缺失的正整数。 时间复杂度O(n) ,空间复杂度 O(1) Example 1: Input: [1,2,0] Output: 3 Example 2: Input: [3,4,-1,1] Output: 2 Example 3: Input: [7,8,9,11,12] Output: 1【Answer】
将A和B的获胜情况罗列,可以看到规律。A第一次抛获胜概率是1/2, A不获胜情况下B第一次获胜概率1/2*1/2=1/4。 所以A获胜概率是:1/2+1/8+1/32+...=2/3。B获胜的概率是:1/4+1/16+...=1/3--- ### 30. 微信跳一跳 【Question】【Answer】
1 排序之后查找 2 把出现的数值放到与下标一致的位置,再判断什么位置最先出现不连续的数值,就是答案了。 3 和2差不多,把出现过的数值的下标位置做好标识,如果没有出现过的数组的下标就没有标识,那么这个就是答案从起点开始接下来有 100 个方块,相邻方块间的距离都为 1,每个方块上有增加体力的食用蘑菇或减少体力的毒蘑菇,蘑菇带来的体力改变是已知的。一个人初始体力为 m,每次可以往前跳任意个方块,体力耗尽就会死掉。
- 每跳一次消耗的体力与跳的距离成正比,比例为 1。问这个人能否跳到终点,如果能,求可能剩余的最大体力。
- 每跳一次消耗的体力是跳的距离的平方。问这个人能否跳到终点,如果能,求可能剩余的最大体力。
- 每跳一次消耗的体力是跳的距离的平方,每跳一个方块加 1 分。问这个人能否跳到终点,如果能,求可能得到的最高分数。
【Answer】
- 第 1 问,贪心算法,时间复杂度 O(n)
def flip1(m, array): """ 微信跳一跳第一问 :param m: 初始体力 :param array: 蘑菇带来的体力改变 :return: 可能剩余的最大体力 """ for n in array: m -= 1 # 消耗的体力与跳的距离成正比 if m <= 0: # 体力不足死掉 return -1 if n > 0: # 只跳加体力的格子 m += n if array[-1] < 0: # 终点的蘑菇必须吃 m += array[-1] return m if m > 0 else -1
- 第 2 问,动态规划,时间复杂度 O(n^2)
def flip2(m, array): """ 微信跳一跳第二问 :param m: :param array: :return: 可能剩余的最大体力 """ # powers 表示在每个格子可能剩余的最大体力 powers = [m] + [0] * len(array) for i in range(1, len(array) + 1): for j in range(i): if powers[j] > (i - j) ** 2: powers[i] = max(powers[i], powers[j] - (i - j) ** 2 + array[i - 1]) return powers[-1] if powers[-1] > 0 else -1
- 第 3 问,动态规划,时间复杂度 O(n^3)
def flip3(m, array): """ 微信跳一跳第三问 :param m: :param array: :return: 可能跳的最多格子数 """ # scores 表示在每个格子得到不同分数时可能剩余的最大体力 scores = [{0: m}] + [{} for _ in range(len(array))] for i in range(1, len(array) + 1): for j in range(i): for score, power in scores[j].items(): left = power - (i - j) ** 2 if left > 0 and left + array[i - 1] > 0: scores[i][score + 1] = max(scores[i].get(score + 1, 0), left + array[i - 1]) return max(scores[-1].keys()) if scores[-1].keys() else 0
实现一个merge函数,功能是将两个有序数组,将它们合并成一个有序数组,如:
let arr1 = [1, 2]
let arr2 = [-1, 2, 8, 9]
merge(arr1,arr2) // 返回 [-1, 1, 2, 2, 8, 9]
【Answer】
function merge(arr1, arr2) { // 可以判断一下arr1、arr2是否是数组 if(!(Array.isArray(arr1) && Array.isArray(arr2))) throw '...' let i = 0, j=0, t=0 let temp = []; while (i < arr1.length && j < arr2.length) { temp[t++] = arr1[i] <= arr2[j] ? arr1[i++] : arr2[j++]; } while (i < arr1.length) { // 数组1有剩余 temp[t++] = arr1[i++]; } while (j < arr2.length) { // 数组2有剩余 temp[t++] = arr2[j++]; } return temp }
【Answer】
1. 用一个数组 validWords[] 记录字符串当前位置之前的字符串是否可以用 dict 组成,validWords[i]=True 可以,否则不可以。默认不可以 2. for 循环 i 从 0 到 s.length: a. 如果 s[0-i] 在 dict 中,设置 validWords[i]=True b. 如果validWords[i]=True,for 循环 j 从 i+1 到 s.length-1,判断 s[i+1 到 j] 是否在 dict 中,如果是,设置 validWords[j]=True 3. 如果 validWords[s.length-1] = True, return True
【Answer】
两个指针, 第一个先向后走k步; 然后两个一起走; 当第一个指针到达链表尾部时另一个指针指向的就是距离终点距离为 k 的节点。
【Answer】
从右往左扫描,同时维护另一个指针指向出现在最右侧的 0,每次扫描到非 0 数字则和它交换
【Answer】
由于输入的区间段,不一定按照起点排好序,所以先按照起点坐下排序利于后续合并。 ``` python def merge(intervals): res = [] intervals = sorted(intervals, key=lambda x:x.start) pre = None for interval in intervals: if not pre: pre = interval continue if interval.start <= pre.end: if interval.end >= pre.end: pre.end = interval.end else: res.append(pre) pre = interval if pre: res.append(pre) return res ```
- 爬楼梯问题:爬楼梯时,每一步会有两个选择:爬一个台阶和爬两个台阶,问:楼梯总台阶数为n,则一共有多少种爬法,写一个函数f,使得:总的爬法= f(n)。举例:n=3时,则共有:(1,1,1)、(1,2) 、(2,1)三种爬法,则f(3)=3。
【Answer】
斐波拉契:f(n)=f(n-1)+f(n-2)
代码需要判断边界
【Answer】
假设有m种零钱,具体面值存在arr中,要找的钱为n。 使用m种零钱去找零n元,可以拆分为: 完全不用第m种零钱 和 至少用一次第m种零钱 ``` python def zhaolin(arr, n, m): if n == 0: return 1 if n < 0: return 0 if m <= 0: return 0 return zhaolin(arr, n, m - 1) \ # 不用第m-1号零钱 + zhaolin(arr, n - arr[m - 1], m) # 至少使用1次m-1号零钱 ```
【Answer】
本题利用dfs递归比较好解,关键点是要把左子树的最后一个节点和右子树头接上。 关键代码: ``` python def flatten(root): if not root: return if root.left: flatten(root.left) if root.right: flatten(root.right) tmp = root.right root.right = root.left root.left = None while root.right: root = root->right root.right = tmp ```
【Answer】
本题用两个指针的思路解决,时间复杂度O(n) 指针left和right记录子数组的左右边界位置, 让right向右移,直到子数组和>=n或到达数组末尾,更新最短距离, 将left像右移一位,然后在和值中减去移去的值, 重复上面的步骤,直到right到达末尾,且left也无法再右移 ``` python def min_sub_len(arrary, n): res = sys.maxsize left, cur_sum = 0, 0 for i in range(0, len(array)): cur_sum += array[i] while left <= i && cur_sum >= n: res = min(res, i - left + 1) cur_sum -= array[left] left += 1 return 0 if res == sys.maxsize else res ```
给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度(每一层的节点可能为空)。
【Answer】
每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。
思路:主要想法是给每个节点一个 position 值,如果我们走向左子树,那么 position -> position * 2,如果我们走向右子树,那么 position -> positon * 2 + 1。当我们在看同一层深度的位置值 L 和 R 的时候,宽度就是 R - L + 1。
方法 1:深度优先搜索
方法 2:宽度优先搜索
【Answer】
二叉树的遍历按照根节点位置的不同,分为前序遍历、中序遍历、后序遍历。 前序遍历:根节点->左子树->右子树 中序遍历:左子树->根节点->右子树 后序遍历:左子树->右子树->根节点 例如二叉树结构如下 ........... a ........../.....\ ........b........c ....../....\ ....d........e .....\ ...../ ......f....g 前序遍历:abdfegc 中序遍历:dfbgeac 后序遍历:fdgebca 解法一:递归方式 void MidOrderPrint(BinaryTree tree) { if(NULL == tree) { return; } MidOrderPrint(tree->leftChild); printf("%s ", tree->data.c_str()); MidOrderPrint(tree->rightChild); } | | 解法二:非递归方式 //假设 Stack 为已经实现的堆栈类型,支持push和pop方法 class Stack { void push(BinTreeNode *); BinTreeNode *pop(); BinTreeNode *top(); }; void MidOrderPrint(BinaryTree tree){ if(NULL == tree) { printf("this tree is empty!\n"); return; } Stack s; BinTreeNode *curNode = tree; while(curNode != NULL || s.top() != NULL) { while(curNode != NULL) { s.push(curNode); curNode = curNode->leftChild; } curNode = s.pop(); printf("%s ",curNode->data); curNode = curNode ->rightChild; } } _
【Answer】
使用二分法寻找位置,seek到文件的该位置读取下一行的内容来判断需要寻找的行。 本题考察二分查找和对文件操作的了解。需要考虑seek到一行中间的情况。
【Answer】
如果直接列举所有情况也是可以得出答案的,但有简单方法可以剪枝。 我们用a表示A取胜,用b表示B取胜。 只要意识到,**无论结果如何,最多4局就可分出胜负**,这样就好计算了。 可以列举所有可能的情形如下: aaaa aaab abba bbab \ baaa baba abab babb \ abaa bbaa aabb abbb \ aaba baab bbba bbbb 也可以计算B获胜的情况 1(bbbb)+ 4(abbb babb bbab bbba) 所以A获胜概率是\dfrac{11}{16},B获胜概率是\dfrac{5}{16}
【Answer】
用一快一慢两个指针fast,slow,快指针提前走k步 然后快慢一起走,直到fast.next == NULL 这是slow->next即为新head,将fast.next指向head,并从slow处断开 本题要注意参数处理: 空链表 k大于链表长度
【Answer】
```java /** * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { /** * @param l1: the first list * @param l2: the second list * @return: the sum list of l1 and l2 */ public ListNode addLists(ListNode l1, ListNode l2) { if(l1 == null && l2 == null) return null; ListNode head = new ListNode(0); ListNode tail = head; int carry = 0; while(l1 != null && l2 != null){ int value = carry + l1.val + l2.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l1 = l1.next; l2 = l2.next; } while(l1 != null){ int value = carry + l1.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l1 = l1.next; } while(l2 != null){ int value = carry + l2.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l2 = l2.next; } if(carry > 0){ ListNode node = new ListNode(carry); tail.next = node; tail = tail.next; } return head.next; } } ``` 拓展1 翻转 拓展2 set/bitmap
要求:
- 自定义多叉树节点node结构(只需要定义节点结构即可,无需构建树)
- 求多叉树的最大高度 getDepth(node)
【Answer】
节点信息
// 定义树节点 Node { ... // 属性 children:[], // 子节点 }
getDepth实现
function getDepth(node){ if (!node) return 0 if (node.children && node.children.length){ let depth=0; for(let e of node.children){ depth=Math.max(getDepth(e),depth) } return depth+1 }else{ return 1 } }
【Answer】
解题思路 1. 先找到中点,将原链表1分为2,即为l_1, l_2; 2. 然后翻转l_2; 3. 最后对l_1和l_2归并。 本题主要考查候选人的编程功底以及好的编程习惯。 比较好的答案应该是4个函数: * def fold(head): # 对折入口函数 * def find_middle(head): # 找中点函数,用一快一慢两指针 * def reverse(head): # 链表翻转 * def merger(l1, l2): # 链表合并
【Answer】
``` python def print_martix(matrix): ''' : type martix: list[list] ''' if not matrix: return start_row, start_col, end_row, end_col = 0, 0, len(matrix) - 1, len(matrix[0]) - 1 while start_row <= end_row and start_col <= end_col: for k in xrange(start_col, end_col + 1): print matrix[start_row][k] start_row += 1 for k in xrange(start_row, end_row + 1): print matrix[k][end_col] end_col -= 1 if start_row > end_row or start_col > end_col: break for k in xrange(end_col, start_col - 1, -1): print matrix[end_row][k] end_row -= 1 for k in xrange(end_row, start_row - 1, -1): print matrix[k][start_col] start_col += 1 ``` 测试用例 ``` python input0 = [[]] input1 = [[1]] input2 = [[1, 2]] input3 = [[1], [2]] ```
【Answer】
本题用DP思路来解决,递推关系: 若str1[i] == str2[j],temp=0,否则temp=1 d[i][j] = min([i-1][j] + 1, d[i][j-1] + 1, d[i - 1, j - 1] + temp)
【Answer】
1. 最简单的,排序,复杂度最高; 2. 遍历整个数组,找到最小的数字比如 1,然后 (index+n/2)%n 3. 二分,找到最小的数字就能找到中位数。淘汰哪一半?
【Answer】
使用后序遍历O(n)。遍历过程中计算以当前节点为根的最长路径,返回当前节点的高度。
【Answer】
为了处理..,容易想到stack解决 ``` python def simplify_path(ppath): segs = path.split('/') stack = [] for seg in segs: if not seg or seg == '.': continue elif seg == '..': if len(stack): stack.pop() else: stack.append(seg) return '/' + '/'.join(stack) ``` 特殊case:/../; ////foo/
https://leetcode-cn.com/problems/majority-element/
定义:给定N个数,称出现次数最多的数为众数,若某数出现的次数大于N/2称为绝对众数。如
A={1, 2, 1, 3, 2}中,1和2都是众数,但都不是绝对众数
如A={1,2,1,3,1}中,1是绝对众数。
【Answer】
解题思路:任意删除2个不相同的数,不改变绝对众数
class Solution { public int majorityElement(int[] nums) { int m = nums[0]; // 绝对众数 int count = 0; // 计数 for(int i = 0; i< nums.length; i++) { if(count == 0) { m = nums[i]; count++; }else if(m == nums[i]) { count++; } else { count--; } } return m; } }
【Answer】
DP问题,令F(k, i)表示从0点出发经过k步到达i点的走法数,题目所求为F(n, 0) F(k, i) = F(k - 1, (i + 1) % 10) + F(k - 1, ((i - 1) + 10) % 10) 初始状态:f[1, 0] = 0, f[1, 1] = 1, f[1, 2] = 0, ... f[1, 9] = 1
给定1个二维字符数组cmap和单词1个word,搜索word是否在map中。
搜索的定义是从cmap的任意位置开始,可以上下左右移动,依次和word每个字符匹配,如果word能匹配完,则存在,否则不存在。
注:cmap中的每个位置只能被访问1次
a c d z
x t r o
f i w o
例如上面的cmap,则'zoo'能搜索到,'wto'不能搜索到
【Answer】
比较容易想到用DFS解决,如果候选没思路,可以提示属于哪类问题,如果想到图就好办了。
def search(cmap, word): if not cmap or not word: return False visited = [[False] * len(cmap[0]) for _ in range(len(cmap))] for i in range(len(cmap)): for j in range(len(cmap[0])): if dfs(cmap, word, 0, i, j, visited): return True def dfs(cmap, word, pos, i, j, visited): if pos == len(word): return True if i < 0 or i >= len(cmap) or j < 0 or j > len(cmap[0]) or visited[i][j] or cmap[i][j] != word[pos]: return False find = False visited[i][j] = True for (ii, jj) in [(-1, 0), (1, 0), (0, -1), (0, 1)]: if dfs(cmap, word, pos + 1, i + ii, j + jj, visited): find = True break visited[i][j] = False # 易错点 return find
【Answer】
O(n)肯定能解决问题,但本题结合二分能优化时间复杂度 如果a[mid] < a[mid + 1]说明峰值后半段,否则在前半段 核心代码 ``` python def find_peak(a): left, right = 0, len(a) - 1 while left < right: mid = left + (right - left) / 2 if a[mid] < a[mid + 1]: left = mid + 1 else; right = mid return right
【Answer】
```java /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ public class Solution { public boolean isSymmetric(TreeNode root) { if (root == null) return true; return solve (root.left, root.right); } public boolean solve(TreeNode t1, TreeNode t2) { if (t1 == null && t2 == null) return true; if (t1 == null && t2 != null || t1 != null && t2 == null || t1.val != t2.val) return false; return solve(t1.left, t2.right) && solve(t1.right, t2.left); } } ```
【Answer】
指针a与d指向要翻转部分的第一个节点,指针b指向a的上一个节点; 指针c指向a,并将a指向下一个节点,再将c插入到b的后面,重复执行该操作直到a走出要翻转的区间; 最后将a接在d后面,完成翻转。
【Answer】
用一个map来维护K->V索引; 用一个双向链表list来维护K, 保证越靠前的K时间越早; 用一个map来维护K->list.node, 该map用于删除时使用 剔除时, 从list取出最老的K, 从map中剔除即可; 最老的那个, 肯定最先过期; 如果都没有过期, 也只能剔除最老的那个; 因此在解决剔除与过期时, 完全同上; 为了解决过期返回空, 有比较简单的办法是在每次查询前先过期一次;
【Answer】
思路:先序遍历树的每个结点,若遍历到的结点有子结点,则交换它的两个子结点。 有两种实现方法: 1.递归实现: ``` void MirroRecursively(BinaryTreeNode *pNode) { if(NULL == pNode) return; if(NULL == pNode->Left && NULL == pNode->Right) return; BinaryTreeNode *pTemp = pNode->Left; pNode->Left = pNode->Right; pNode->Right = pTemp; if(pNode->Left) MirroRecursively(pNode->Left); if(pNode->Right) MirroRecursively(pNode->Right); } ``` 2.非递归实现,即使用循环实现: ``` void MirrorNonRecurively(BinaryTreeNode *pNode) { if(NULL == pNode) return; stack stackTreeNode; stackTreeNode.push(pNode); while(stackTreeNode.size()) { BinaryTreeNode *pNode = stackTreeNode.top(); stackTreeNode.pop(); if(NULL != pNode->Left || NULL != pNode->Right) { BinaryTreeNode *pTemp = pNode->Left; pNode->Left = pNode->Right; pNode->Right = pTemp; } if(NULL != pNode->Left) stackTreeNode.push(pNode->Left); if(NULL != pNode->Right) stackTreeNode.push(pNode->Right); } } ```
【Answer】
递归或者非递归都可以
【Answer】
由于时间是递增的, 可以先二分到指定时间, 然后再进行读取;
【Answer】
本题采用两个指针left、right指针指向子串的起始和结束位置。 right不断前行,对left和right内的字符存入dict并计数,当dict的keys()超过2个时,向右移动left。 如此往复,不断更新最长子串长度。 ``` python def max_sub_len(s): max_len, left = 0, 0 count = collections.Counter() for right in xrange(len(s)): count[s[right]] += 1 while len(count) > 2: count[s[left]] -= 1 if count[s[left]] == 0: count.pop(s[left]) left += 1 max_len = max(max_len, right - left + 1) return max_len
【Answer】
``` python def gen_matrix(n): if n <= 0: return None matrix = [[0] * n for _ in xrange(n)] val = 1 start_row, start_col, end_row, end_col = 0, 0, n - 1, n - 1 while val <= n * n: for k in xrange(start_col, end_col + 1): matrix[start_row][k] = val val += 1 start_row += 1 for k in xrange(start_row, end_row + 1): matrix[k][end_col] = val val += 1 end_col -= 1 for k in xrange(end_col, start_col - 1, -1): matrix[end_row][k] = val val += 1 end_row -= 1 for k in xrange(end_row, start_row - 1, -1): matrix[k][start_col] = val val += 1 start_col += 1 return matrix ```
【Answer】
1. 方案1:使用hashmap,遍历一次数组,将数组的数字当做key放入hashmap中,并将出现的次数作为value。之后再遍历hashmap将次数大于 n/k 的数打印出来即可 2. 方案2:假设要求空间复杂度为O(K),时间复杂度尽量低该怎么做? - 可以给一点提示:换一个思路,每次都从数组中删除K个互不相同的数,那么删除 n/k 次后,还在数组中的数的出现次数,应该至少都大于 n/k - 最终答案:申请一个K空间的hashmap,按照方案1的思路遍历数组并插入hashmap,每当hashmap有K个值时,就将hashmap里的value减1,如果为0,就从hashmap中删除该key。当数组遍历完,hashmap中剩余的key/value对,就基本是我们要找的数(还需要再次遍历hashmap检查一下次数)
在微服务的架构下,公司内部会有非常多的独立服务。
服务之间可以相互调用,往往大型应用调用链条很多也很长,我们需要找出耗时最大的链条进行优化。(假设服务同时调用其依赖的下游服务)
例如:
A服务依赖B服务,平均调用延迟100ms,记为(A, B, 100)
其他依赖和延迟如下:
(A, C, 200)
(A, F, 100)
(B, D, 100)
(D, E, 50)
(C, G, 300)
那么服务A有三条调用链:A-B-D-E,A-C-G,A-F,平均延迟250,500,100
延迟最大的调用链是A-C-G,延迟为500ms
输入:
[(A, B, 100), (A, C, 200), (A, F, 100), (B, D, 100), (D, E, 50), (C, G, 300)]
输出:
500
Follow up:
1. 能否输出延迟最大的调用链,如果存在多条,都输出
【Answer】
可以采用搜索的思路解决,例如DFS
【Answer】
我们看一般的case寻找连续剧集,其实就看当前剧集i的前一集i-1下载了没,或者后一集i+1下载了没 也就是往前查查、往后查查,为了方便查,我们可以用hash,例如python里的set。 知道这个原理后,我们可以比较容易的写出代码。 ``` python def long_episode_count(episodes): remains = set(episodes) long_count = 0 for i in episodes: if i not in remains: continue remains.remove(i) while pre in remains: remains.remove(pre) pre -= 1 while next in remains: remains.remove(next) next += 1 long_count = max(long_count, next - pre - 1) return long_count ```
【Answer】
首先对访问日志的文章id做计数,然后根据计数做排序得到top n。 在排序方法选择方面,top n适合用堆来实现。 ``` python def top_article_id(visit_log, n): article_cnt = collections.Counter() for article_id in visit_log: article_cnt[article_id] += 1 heap = article_cnt.keys() def _heap_adjust(heap, parent, heap_size, article_cnt): while parent < heap_size: left, right = parent * 2 + 1, parent * 2 + 2 swap_pos = parent if left < heap_size and article_cnt[heap[left]] < article_cnt[heap[parent]]: swap_pos = left if right < heap_size and article_cnt[heap[right]] < article_cnt[heap[swap_pos]]: swap_pos = right if swap_pos != parent: heap[parent], heap[swap_pos] = heap[swap_pos], heap[parent] parent = swap_pos else: break for i in range(int(math.ceil(n / 2) - 1), -1, -1): heap_adjust(heap, i, n, article_cnt) for i in range(n, len(heap)): if article_cnt[heap[0]] < article_cnt[heap[i]]: heap[0] = heap[i] heap_adjust(heap, 0, n, article_cnt) return heap[:n]
【Answer】
参考实现代码: ``` python class TrieNode(object): def __init__(self): self.is_leaf = False self.children = [None] * 26 class Trie(object): def __init__(self): self.root = TrieNode() def insert(self, word): if not word: return cur_node = self.root for char in word: index = ord(char) - ord('a') if not cur_node.children[index]: cur_node.children[index] = TrieNode() cur_node = cur_node.children[index] cur_node.is_leaf = True def search(self, word): if not word: return False cur_node = self.root for char in word: index = ord(char) - ord('a') if not cur_node.children[index]: return False cur_node = cur_node.children[index] return cur_node.is_leaf ```
【Answer】
``` python def add(num1, num2): ''' >>> add('', '') '' >>> add('', '1') '1' >>> add('ab', '') 'ab' >>> add('1b', '2x') '49' >>> add('0', '2x') '2x' >>> add('zz', '1') '100' ''' def _get_value(num): if ord('0') <= ord(num) <= ord('9'): return ord(num) - ord('0') return ord(num) - ord('a') + 10 def _to_chr(num): if 0 <= num <= 9: return str(num) return chr(ord('a') + num - 10) def _add(n1, n2, carry): result = _get_value(n1) + _get_value(n2) + carry carry = 1 if result >= 36 else 0 result %= 36 return _to_chr(result), carry len1, len2 = len(num1), len(num2) if len1 > len2: # 取巧:把两个数字长度对齐 num2 = '0' * (len1 - len2) + num2 elif len2 > len1: num1 = '0' * (len2 - len1) + num1 res = [] carry = 0 for i in xrange(max(len1, len2) - 1, -1, -1): tmp, carry = _add(num1[i], num2[i], carry) res.append(tmp) if carry: # 易错点:很容易遗漏 res.append('1') return ''.join(res[::-1]) # 易错点:需要翻转 ```
【Answer】
1. 二分查找 0 的位置; 2. 注意边界位置 0 的处理,在二分条件上需要做一些处理,如果只是找到 0 的位置然后遍历找到第一个和最后一个 0 的话, 复杂度为恶化; 3. 整体复杂度为 O(lg(n));
【Answer】
简单DP dp[i]表示调到第i的位置最少需要踩几个石子; 于是dp[i]可由dp[i-3], dp[i-4], dp[i-5]得来;
【Answer】
```javascript function findMaxDuplicateChar(str) { if (str.length === 1) { return str; } const charObj = {}; let maxChar = '', maxValue = 1; for(let i = 0; i < str.length; i++) { if (str[i].trim() !== '') { if (!charObj[str.charAt(i)]) { charObj[str.charAt(i)] = 1; } else { charObj[str.charAt(i)] += 1; } } } for (const k in charObj) { if (charObj[k] >= maxValue) { maxChar = k; maxValue = charObj[k]; } } return { maxChar, maxValue }; } const str = 'this is a fe test at toutiao on September'; findMaxDuplicateChar(str) // output: { maxChar:"t", maxValue:7 } ```
输入数组nums,要求输出升序排序后的结果。已知数组元素为非负整数,且当数组长度为n时,所有元素的值都小于n;
例:
[3, 2, 1, 3, 5, 0] -> [0, 1, 2, 3, 3, 5]
[0] -> [0]
【Answer】
O(n2)的排序算法:冒泡排序,插入排序,选择排序 等;
O(nlog(n))的排序算法:归并排序,快排 等;
O(N)的排序算法:空间换时间,利用计数实现,需要O(n)空间复杂度;
【Answer】
二叉树遍历;递归;链表操作 原理上看,对根节点、根的左子树、根的右子树分别作处理: 1. 比如根节点1, 对于左子树,找到它的最右节点 5, 把 5 和 1 连接起来; 2. 对于 1 的右子树,找到它的最左节点 6,把 6 和 1 连接起来。 实现上,假设已经把左子树变做双向链表了,让指针一直向右走,就能找到最右节点和根连接; 右子树同理。 返回结果的时候,找到双向链表的最左节点就可以了。 ``` private Node convertToDoublyLinkedList(Node root) { if (root.getLeft() != null) { Node left = convertToDoublyLinkedList(root.getLeft()); while (left.getRight() != null) { left = left.getRight(); } left.setRight(root); root.setLeft(left); } if (root.getRight() != null) { Node right = convertToDoublyLinkedList(root.getRight()); while (right.getLeft() != null) { right = right.getLeft(); } right.setLeft(root); root.setRight(right); } return root; } ```
【Answer】
根据情况,选择栈和队列即可 栈必须,队列可选
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
【Answer】
【Answer】
``` python def tree_path_sum(root, val): if not root: return 0 val = val * 10 + root.val if not root.left and not root.right: return val return tree_path_sum(root.left, val) + tree_path_sum(root.right, val) ```
【Answer】
使用贝叶斯公式求解,p(a|b)*p(b) = p(a)*p(b|a) 三种假设,即假设车在门A,门B,门C后面。 D表示打开了门B但是车不在门后。 假设A:先验概率=1/3,似然度=1/2,后验概率=(1/3)*(1/2)/(1/2)=1/3 假设B:先验概率=1/3,似然度=0, 后验概率=0 假设C:先验概率=1/3,似然度=1,后验概率=2/3 变化之后概率相同。
【Answer】
思路: 5个人太多了,首先把问题简化。从1个人到5个人逐个分析。 * 1个人,显然自己全拿 * 2个人。1,2。2号海盗显然可以全部拿走,因为他自己的一票保证了50%。 * 3个人。1,2,3。首先,如果3号的方案没通过,那么1号将什么都得不到,而2号必定要除3号而后快。因此3号必须征得1号的支持,但又不能完全不给1号任何金币(第三原则)。因此分配方案是1 2 3 = 1 0 99。这样3号的方案能够得到1和3的支持。 * 4个人。1,2,3,4。首先,如果4号的方案没通过,那么2号将什么都得不到。因此4号只需要分配1 2 3 4 = 0 1 0 99 * 5个人。1,2,3,4,5。5号清楚的知道一旦他的方案不通过,1和3将什么都得不到,因而他只需要分配1 2 3 4 5 = 1 0 1 0 98,这样即足以保证。
有一堆牛要过河,河的长度是l,河中间有n个石头,牛只能踩着石头过河,问去掉m个石头后(去掉这m个石头的方式是随机的)的每种情况牛能走的石头间距最小值中,最大的那一个是多少
【Answer】
二分最大值最小化问题
【Answer】
要求自己给出二叉树数类定义 本题很容易用递归解决,基本思路如下: ``` def path_exists(root, n): if not root: return False if not root.left and not root.right and root.value == n: return True return path_exists(root.left, n - root.value) or path_exists(root.right, n - root.value) ```
【Answer】
本题不好处理的是aaabccc,即:c和a相遇就要都去掉。 比较好的思路是用栈的思路解决。 ``` python def special_trim(s): if not s: return s res = [] for c in s: if c == 'b': continue if c == 'c' and res and res[-1] == 'a': res.pop() continue res.append(c) return ''.join(res) ```
【Answer】
考察链表使用。最简单直接的方法是使用循环链表。 具体思路是: 1. 构建循环链表,初始化数据; 2. 每到第k-1个结点,便p->next = p->next->next。 3. 循环结束条件为p = p->next,即只有一个结点,该结点所对应的值即为最后剩下的人。
【Answer】
结合BST的性质,用递归很好解决,按照中序遍历即可 核心代码: ``` python def find_k_small(root, k): def _find(root): if not root: return -1 val = _find(root) if not k: return val k -= 1 if not k: return root.val return _find(root) _find(root) ```
【Answer】
本题解法应该有不少,大体思路是按照某种遍历的顺序记录下每个节点,叶子节点的空指针可以用特殊字符表示 例如用先根遍历解决: ``` python def serialize(root): def pre_order(node): if node: vals.append(str(node.val)) pre_order(node.left) pre_order(node.right) else: vals.append('#') vals = [] pre_order(root) return ' '.join(vals) def deserialize(data): def pre_order(): val = next(vals) if val == '#': return None node = TreeNode(int(val)) node.left = pre_order() node.right = pre_order() return node vals = iter(data.split()) return pre_order()
【Answer】
通过组合一些基本的数据结构, 来实现一些更高级的性质; 内部维护一个链表, list, 其元素为一个三元组(ID, timestamp, obj), 分别为对象ID, 上次被访问时间, 和对象内容; 在维护该list时, 需要保持一个性质, 越靠后的元素越新, 既timestamp越大; 内部再维护一个map, 该map表示一个ID到list节点的索引, 格式为map(ID, node); 对于get(id)操作: 1: 先在map中查找ID对应的list node; 2: 将node从list中取出, 即list.Remove(node); 3: 检查node.timestamp, 如果过期, 则返回null, 表示无数据, 并将ID从map中删除; 4: 如果未过期, 设置node.timestamp = now(), 并将node添加到list尾部, 即list.Append(node); 5: 返回node.obj; 对于set(id, obj)操作: 1: 同get(id)的1~3步操作, 删除对应的ID; 2: 如果此时空间满了, 既对象数为n, 则将list中表头的那个元素删除; 3: 更新list和map: node = new(ID, now(), obj), list.Append(node), map[ID] = node;
输入一维数组array和n,找出和值为sum的n个元素即可,不用找出所有组合。
array = [2, 3, 1, 10, 4, 30] n = 2, sum = 31
result = find(array, n, sum)
// result = [1, 30]
【Answer】
基础解法供参考
function find(arr, n, sum, shouldSort = true) { let sorted = arr; if (shouldSort) { sorted = arr.sort(); } const length = sorted.length; if (n === 2) { let front = 0; let back = length - 1; while(front < back) { const value = sorted[front] + sorted[back]; if (value === sum) { return [sorted[front], sorted[back]]; } else if (value > sum) { back -= 1; } else { front += 1; } } return null; } for(let i = 0; i < length; i += 1) { const val = sorted[i]; const result = find(sorted.slice([i + 1]), n - 1, sum - val, false); if (!result) { return null; } else { return [val, ...result]; } } }
要求:
- 自定义多叉树节点node结构(只需要定义节点结构即可,无需构建树)
- 按照广度优先查找符合要求的节点(没有符合要求的节点返回null),比如查找电话号码为 phone的用户信息,调用如下:
let node = wideTraversal(node,(e)=>e.phone===phone)
【Answer】
节点定义:
// 定义树节点 Node { ... // 属性 children:[], // 子节点 }
实现:
wideTraversal(node: Node, predict): Node | null { if (!node) return null let queue: Array
= []; queue.push(node) while (queue.length) { let cur = queue.shift() if (!cur) continue if (predict(cur)) return cur if (cur.children&& cur.children.length) { queue.push(...cur.children) } } return null }</code></pre> </pre> </details> --- ### 90. 给定无向图和它一个顶点,求无向图中到该顶点距离为K的所有点 【Question】 给定无向图和它一个顶点,求无向图中到该顶点距离为K的所有点,各个相邻顶点间距离为1
--- ### 91. 输出所有根节点到叶子节点的路径 【Question】 比如: 1 2 3 4 5 6 7 8 输出: 1,2,4 1,2,5 1,3,6 1,3,7,8【Answer】
方案:对图进行K步的BFS, 注意处理被重复走过的点;--- ### 92. 找出旋转有序数组中的最小值 【Question】 假设原数组为1,2,3,4,5那4,5,1,2,3就是旋转有序的数组 注:数组无重复元素【Answer】
二叉树遍历--- ### 93. 搜索二维矩阵 【Question】 给定二维m * n矩阵matrix,满足一定特性: 1. 每行从左到右递增 2. 每列从上到下递增 给定目标元素num,判断num是否在矩阵中存在 例如: ``` python matrix = [ [1, 3, 5, 10], [2, 4, 6, 11], [7, 9, 12, 20], ] ``` num = 4存在;num = 13不存在【Answer】
暴力可以O(n),但没用到旋转有序的特征。 本题可以用二分的思想降低时间复杂度 定义左右两个指针left、right指向头尾元素 如果a[left] < a[right]则没有移位,直接输出a[left]即可,反之二分搜索。 如果a[left] > a[mid] 则要搜索右半段,因为a[left]也大于a[right];反之搜索左半段。 核心代码 ``` python def find_min(arr): left, right = 0, len(arr) - 1 if arr[left] < arr[right]: return arr[left] while left != right - 1: mid = left + (right - left) / 2 if arr[left] < arr[mid]: left = mid else: right = mid return min(arr[left], arr[right])--- ### 94. 扑克牌的堆栈、队列操作 【Question】 我手中有一堆扑克牌, 但是观众不知道它的顺序。 * 第一步, 我从牌顶拿出一张牌, 放到桌子上。 * 第二步, 我从牌顶再拿一张牌, 放在手上牌的底部。 * 第三步, 重复第一步的操作, 直到我手中所有的牌都放到了桌子上。 最后, 观众可以看到桌子上牌的顺序是:13\12\11\10\9\8\7\6\5\4\3\2\1 请问, 我刚开始拿在手里的牌的顺序是什么?【Answer】
结合数组定义,观察例子,有两个特殊位置很特殊:左下角和右上角。 左下角的7往上所有的数变小,往右所有的数变大。 那么我们就可以将目标数字num和左下角比较,比目标小就往右搜,比目标大就往上搜。 如此往复可以判断num是否在matrix中。 ``` python def search(matrix, num): if not matrix: return False rows, cols = len(matrix), len(matrix[0]) if num < matrix[0][0] or num > matrix[rows - 1][cols - 1]: return False i, j = rows - 1, 0 # 左下角 while i >= 0 and j < cols: if matrix[i][j] < num: j += 1 elif matrix[i][j] > num: i -= 1 else: return True return False ```--- ### 95. 用js实现一个binarySearch二分查找 【Question】 定一个一个binarySearch的函数,传参能支持四个参数,分别是: >1. arr: 一个数组, >1. key: 一个需要查找的目标值, >1. low: 左边界 >1. high: 右边界 >1. 如果能知道则访问素组的位置,否则返回-1 ```javascript function binarySearch(){ // 补全代码 } var arr = [86,1,3,2,4,5,6,7,8,9,10,11,23,44]; var result = binary_search2(arr, 5, 0, 6); console.log(result); // 4 ```【Answer】
解法一: 这道题候选人容易出现折半的思路, 其实考虑的复杂了。 本质是将一个队列和栈做了两个操作 1. 出队、入栈 2. 出队、入队(队尾) 因为是看得到结果, 看不到初始顺序, 那么这个操作就是一个逆操作。 1. 出栈、入队 2. 出队(队尾)、入队(队首) 答案: 输入: 1,2,3,4,5,6,7,8,9,10,11,12,13, 输出: 1,12,2,8,3,11,4,9,5,13,6,10,7, 代码如下 ``` C++ 再改改 int doTheWork(std::deque * pQ, std::stack * pS) { if(NULL == pQ or NULL == pS) return -1; while(pS->size() > 0) { int val = pS->top(); pS->pop(); if (pQ->size() > 0) { int tmp = pQ->back(); pQ->pop_back(); pQ->push_front(tmp); pQ->push_front(val); } else { pQ->push_front(val); } } return 0; } ``` 解法二: 对手上牌按照a,b,c...进行编码,直接按顺序操作,输出结果和桌上实际结果对应,即为原手上牌的顺序。--- ### 96. 通配符匹配 【Question】 给定字符串s和模式串p,实现函数match(s, p),判断模式串p是否能完全匹配s 模式串中有两个特殊字符'?'和'*' '?'匹配任意1个字符;'* '匹配任意r个字符,包括空 例如: ``` python match('a', 'a') = True match('aa', 'a') = False match('a', '?') = True match('aa', '*') = True match('abc', '?*') = True ```【Answer】
二分法查找,也称折半查找,是一种在有序数组中查找特定元素的搜索算法。查找过程可以分为以下步骤: >1. 首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。 >1. 如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。 >1. 如果某一步数组为空,则表示找不到目标元素。 ```javascript // 递归实现的js代码 function binary_search2(arr, key, low, high) { if(low > high) { return -1; } var mid = parseInt((high + low) / 2); if(arr[mid] == key) { return mid; } else if(arr[mid] > key) { high =mid -1; return binary_search2(arr, key, low, high); } else if(arr[mid] < key) { low = mid +1; return binary_search2(arr, key, low, high); } } var arr = [86,1,3,2,4,5,6,7,8,9,10,11,23,44]; var arrSorted = arr.sort(function (a,b) { return a-b; }) var result = binary_search2(arrSorted, 5, 0, 10); console.log(result); // 4 ```--- ### 97. 原子计数问题 【Question】 给出一个分子式,比如: HMg2(H2O(N3Ag)2)3N2 计算这个分子式中每个原子出现了多少次,输出一个 map,比如上面的分子式中: map[string]int {"H": 7, "Mg": 2,"Ag": 6, ...} 分子式的规则: 1. 都以大写字母开头,后面跟 0 个或者 1 个小写字母,比如 Mg, H 2. 单个原子后面跟 0 个或者 1 个数字表示它出现的次数,比如 Mg2 表示 Mg 出现 2 次,数字范围 [2-1000] 3. 分子式中可能有括号,括号后面可能跟 0 个或者 1 个数字表示整个括号内的原子出现的次数,比如 (N3Ag)2 表示 N出现 6 次,Ag 出现 2 次 4. 括号可以嵌套 输入是合法的【Answer】
本题可以用DP算法来解答。 用dp[i, j]表示串s, p这两个串分别到i和j位置它们是否匹配。那么我们得到递推关系: 如果p[j] != '* ', dp[i, j] = dp[i-1, j-1] and (s[i] == p[j] || p[j] == '?') 如果p[j] == '* ', 那么dp[i][j] = dp[i - 1][j] || dp[i][j - 1] ``` python def match(s, p): m, n = len(s), len(p) dp = [[False] * (n + 1) for _ in range(m + 1)] dp[0][0] = True for j in range(1, n + 1): dp[0][j] = dp[0][j - 1] and p[j - 1] == '*' for i in range(1, m + 1): for j in range(1, n + 1): if p[j - 1] in [s[i - 1], '?']: dp[i][j] = dp[i-1][j-1] elif p[j - 1] == '*': dp[i][j] = dp[i - 1][j] or dp[i][j - 1] return dp[m][n] ``` 另外,本题也可以用贪心算法--- ### 98. 安卓解锁密码数 【Question】 安卓系统采用9个点连线构成图案密码。 要求连接以下9个点中**至少4个点**构成一条路径,从而得到1个密码。 1 2 3 4 5 6 7 8 9 任意点间均可连线,都有如下附加限制: 1. 在一个密码路径里每个点只能用1次; 2. 如果2号点没有被连,则1不能直接连3号点,即:1->3路径是非法的。类似情况还有1、7;2、8;1、9等等; 3. 如果2点已经连过了,则1可以连到3,3也可以连到1,即:2->1->3路径是合法的。 4. 1和6是可以直接连线的,因为二者之间没有点。 本问题求所有的密码数,也就是路径数,包含4个点、5个点。。。9个点的所有路径数。【Answer】
1. 最简单的方法是递归,碰到 ( 就开始递归 2. 担心爆栈就把递归改成非递归 3. 可以用正则表达式来做,这里不展开了,如果候选人懂的话让他解释--- ### 99. 矩阵中的最长递增路径 【Question】 给定m * n矩阵matrix,可以从任意位置开始,向上、向下、向左、向右移动,但要求下一个位置上的元素要大于当前元素。 找出最长的递增路径长度。【Answer】
本题目如果数据结构算法比较熟悉,会很快想到DFS。 难点是路径不能直达问题如何解决? 用visit记录某个点是否已经在路径中 ``` python visit = [[0, 0 ,0], [0, 0, 0], [0, 0, 0]] ``` 判断两个点能否连同,等价于判断两个点是否存在中间点问题,如果不存在可以直接连,如果存在要判断中间点是否已经访问过了。 中间点坐标为:|i1 - i2| / 2, |j1 - j2| / 2 另外,本题考虑到对称性会大大降低运算量 1、3、7、9点对称 2、4、6、8点对称--- ### 100. 升序数组求平方数不同的个数 【Question】 给定一个升序数组1,元素有重复,对每个元素算一下平方后得到新的数组2,问数组2中不相同的元素共有多少个?给出算法和空间复杂度,要求尽量优化。 举例: 数组1 [-13,-10,-9,-6,-5,-1,3,4,6,7,10,11,15,21,42] 平方后得到 数组2 [169,100,81,36,25,1,9,16,36,49,100,121,225,441,1764] 其中不相同的元素个数为13个。【Answer】
本题很容易想到dfs,但问题是每个点开始递归搜索,重复计算很多,结合DP提升效率。 用dp[i][j]表示从(i,j)开始的最长递增路径长度,当递归调用时,如果dp[i][j]不为0,直接返回dp[i][j]。 ``` python def long_increase_path_len(matrix): if not matrix: return 0 res = 1 m, n = len(martix), len(matrix[0]) dp = [[0] * n for _ in range(m)] for i in range(m): for j in range(n): res = max(res, dfs(matrix, dp, i, j, m, n)) return res def dfs(matrix, dp, i, j, m, n): if dp[i][j]: return dp[i][j] tmp_max = 1 dirs = [[0, -1], [-1, 0], [0, 1], [1, 0]]: for ni, nj in dirs: ii, jj = i + ni, j + nj if ii < 0 or ii >= m or jj < 0 or jj >= n or matrix[ii][jj] <= matrix[i][j]: continue tmp_max = max(tmp_max, 1 + dfs(matrix, dp, ii, jj, m, n) dp[i][j] = tmp_max return dp[i][j] ```--- ### 101. URL反转 【Question】 给定形如 `www.toutiao.com` 的 URL,将其转换成 `com.toutiao.www` 的形式,要求必须原地操作【Answer】
常规解法,按题目思路,先平方算好,再将结果插入hashset,最后输出hashset大小。 优化1,平方没必要算,其实就是绝对值。 优化2,注意到数组2其实是以0分隔的一个降序和一个升序数组。反序遍历降序数组,正序遍历升序数组,即可合并成一个升序数组,合并时做一下排重,最后输出合并后数组的元素个数。--- ### 102. 判断单向链表是否有环 【Question】【Answer】
1. 原地全部翻转一遍; 2. 遍历遇到".",继续翻转该部分字符串; 该题目重点考察编码,需要保证代码简洁,要不不允许使用字符串库函数判断一个链表中是否有环
例如:A->B->C->D->B->C->D
D指向B形成环
要求:在空间复杂度O(1)的情况下,时间复杂度最小
--- ### 103. 给定两个链表,求它们交叉节点 【Question】 1. 已知两个链表, 从某个节点开始就交叉了 2. 已知这两个链表的头节点, 求出交叉的节点【Answer】
创建两个指针slow,fast,同时指向这个链表的头节点。
然后让两个指针开始循环移动
slow每次向下移动一个节点,fast每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环
--- ### 104. 判断一个IP是否是国内 【Question】 如何快速判断一个ip地址是否属于国内?已知db中有几十万个国内ip地址段【Answer】
1. 依次遍历两个链表分别得到链表的长度M和N 2. 然后让长的那个一个从头开始走|M-N|的步数; 3. 两个指针同时走, 直到碰头--- ### 105. 用户在线峰值 【Question】 已知一天内用户登录登出的日志(数据量较大),求这一天用户在线的最大峰值和持续时间段 - 日志包含字段(userid, login_time, logout_time) - 登录登出时间精确到秒【Answer】
1. 将ip地址通过位移运算转成int 2. 对ip地址进行排序(可以考察任意一种排序算法) 3. 二分查找--- ### 106. 股票买卖问题 【Question】 给定一个数组代表股票每天的价格,请问只能买卖一次的情况下,最大化利润是多少?日期不重叠的情况下,可以买卖多次呢? 输入: {100, 80, 120, 130, 70, 60, 100, 125} 只能买一次:65(60 买进,125 卖出) 可以买卖多次: 115(80买进,130卖出;60 买进,126卖出) 提示:不用输出买卖的序列,只需要得到最大利润【Answer】
可以将这一天看成0-24*3600的离散的时间点,构造一个dict 每个用户在login_time对应value+1,logout_time对应value-1 得到用户在线数量的变化趋势,然后再遍历此dict求和 难点: - 想不到先求变化趋势 - 峰值时间段可能存在多个 ``` def get_max(logs): log_count = {} for log in logs: login_time = log['login_time'] logout_time = log['logout_time'] log_count[login_time] = log_count.setdefault(login_time, 0) + 1 log_count[logout_time] = log_count.setdefault(logout_time, 0) - 1 max, current_users, start, end, is_max, timeline = (0, 0, 0, 0, False, []) keys = log_count.keys() keys.sort(lambda a, b: a - b) for time_node in keys: current_users = current_users + log_count[time_node] if current_users > max: max = current_users start = time_node is_max = True elif current_users < max: if is_max: end = time_node is_max = False else: if is_max: end = time_node else: timeline.append((start, end)) start = time_node is_max = True timeline.append((start, end)) return max, timeline ```--- ### 107. 输出给定数字下一个比它大的数字 【Question】 比如数字:1234, 输出 1243 比如 1243,则输出 1324【Answer】
1. 对于只能买一次的情况: ``` public static int maximumProfit(int[] stockPrices) { int profit = 0; int minimumPrice = Integer.MAX_VALUE; /* * 对于给定的一天,最大利润等于 - * max(昨天为止的最大利润, 当天的价格 - 之前的最小价格) */ for(int i = 0; i < stockPrices.length; i++) { profit = Math.max(profit, stockPrices[i] - minimumPrice); minimumPrice = Math.min(stockPrices[i], minimumPrice); } return profit; } ``` 2. 对于可以买卖多次的情况,累积递增序列的差就可以了: ``` public static int maximumProfit2(int[] stockPrices) { int totalProfit = 0; for(int i=1; i 0){ totalProfit += currentProfit; } } return totalProfit; } ```--- ### 108. Path Sum 【Question】 给定一个二叉树和一个数字n,判断二叉树中是否有一个路径上的数字之和等于给定的数字n For example: Given the below binary tree and sum = 22, 5 / \ 4 8 / / \ 11 13 4 / \ \ 7 2 1 return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.【Answer】
用 1243 为例: 1. 从右向左扫描,找到第一个不是升序的数字,比如 2 2. 在 2 的右边,找到比它大的最小的数,是 3 3. 交换 2和 3,得到 1342 4. 把现在 3 右边的所有数字从大到小排序,得到 1324 (如果是排序则是 O(nlogn), 其实逆序就行了)--- ### 109. 单链表每隔k个元素做一次反转 【Question】 给定一个链表,每隔k个元素做一次反转 Example: Inputs: 1->2->3->4->5->6->7->8->NULL and k = 3 Output: 3->2->1->6->5->4->8->7->NULL. Inputs: 1->2->3->4->5->6->7->8->NULL and k = 5 Output: 5->4->3->2->1->8->7->6->NULL.【Answer】
public class Solution { public boolean hasPathSum(TreeNode root, int sum) { if(root == null) return false; int left = sum - root.val; if(root.left == null && root.right == null && left == 0) { return true; } return hasPathSum(root.left, left) || hasPathSum(root.right, left); } } 可以进一步问,输出所有和等于给定数字n的path--- ### 110. 用两个栈实现一个队列 【Question】 用两个堆栈模拟队列的功能,实现push,pop,count三个方法【Answer】
Node* rollback(Node *&head, int k) { Node *pre = NULL; Node *next = NULL; Node *curr = head; int count = 0; while (curr != NULL&&countnext; curr->next = pre; pre = curr; curr = next; count++; } if (curr != NULL) { head->next = rollback(next, k); } return pre; }--- ### 111. 赛马求最快N匹 【Question】【Answer】
简单的做法:栈s1和s2,始终维护s1作为存储空间,以s2作为临时缓冲区,push直接进s1,pop时s1导入s2,栈顶出栈,导回s1 优化做法:入队时,将元素压入s1,出队时,判断s2是否为空,如不为空,则直接弹出顶元素;如为空,则将s1的元素逐个“倒入”s2,把最后一个元素弹出并出队条件:
1.64匹马
2.8个赛道
3.每次比赛只能知道比赛结果名次,不能知道具体时间
求:
用最少的比赛次数,找出最快的4匹
--- ### 112. LRU cache 【Question】 实现一个LRU过期算法的KV cache, 所有KV过期间隔相同, 满足如下性质: 1. 最多存储n对KV; 2. 如果大于n个, 则随意剔除一个已经过期的KV; 3. 如果没有过期的KV, 则按照LRU的规则剔除一个KV; 4. 查询时如果已经过期, 则返回空;【Answer】
1.每次比赛至少能淘汰4匹(树形淘汰算法),因此淘汰60匹,至少需要15次比赛,回答15,是最差答案
2.如果能回答出12次的(经过加速之后的简单排序),为次优解:
1)先每8个一组,一共8组比8次
2)从第9次开始
*1.先取每组第一名,每次比赛,找出最快1匹,取出
*2.将最快这匹对应的组里次快的加入比赛,一共4次,找出最快4匹
3.如果能答出特定情况下10次,最差11次,为最优解(剪枝算法):
1)先每8个一组,一共8组比8次
2) 第9次,先取每组第一名,进行比赛,找出前四名
3) 第10次,将前4名对应的组中的第2名加入比赛,一共8匹,比赛一次,如果对应的前四名没发生变化,说明前4名就是最快4名
4)第11次
*1.假设有一个组里的第2名,进入前四名:
①有一组的第1名被挤出前4名,该组所有的候选马无法进入前4名
②另外两组第2名之后无法进入前4名
因此,4(第2名在第10次进入前4名的组对应的前4名)+2(还在前4名的另外两个第1名)=6匹马进行比赛,决出前4名,即为最终答案
*2假设有两个组的第2名,进入前四名:
①则另外两组都无法进入前4名,而还在前4名的两组的前4名4+4=8,跑一次,最终能得到最快4名
--- ### 113. 求数组的最大区间和 【Question】 # 输出一个 int 型数组的最大连续子数组(所有元素加和最大)各个元素之和 # 保证数组中至少有一个正数 例: 输入:{1,2,5,-7,8,-10} 输出:9 (子数组为: {1,2,5,-7,8})【Answer】
用一个map来维护K->V索引; 用一个双向链表list来维护K, 保证越靠前的K时间越早; 用一个map来维护K->list.node, 该map用于删除时使用 剔除时, 从list取出最老的K, 从map中剔除即可; 最老的那个, 肯定最先过期; 如果都没有过期, 也只能剔除最老的那个; 因此在解决剔除与过期时, 完全同上; 为了解决过期返回空, 有比较简单的办法是在每次查询前先过期一次; 解法大致同上; 为了实现LRU, 可在每次get后, 将该K从cache中删除并重新插入一遍;--- ### 114. 最长无重复子串 【Question】【Answer】
复杂度 `O(n)` 的算法 ``` int _tmain(int A[], _TCHAR* argv[]) { int array_length = sizeof(A) / sizeof(A[0]); int sum = 0; int thisSum = 0; for (int i = 0; i < array_length; i++) { thisSum += A[i]; if (thisSum > sum) { sum = thisSum; } else if (thisSum < 0) { thisSum = 0; } } printf("%d",sum); return 0; } ```无重复子串指:子串中每个字符都不相同 例如:s = 'aaabcdddd' 最长的无重复子串为'abcd'长度为4
--- ### 115. 服务循环依赖检测 【Question】【Answer】
用两个指针left、right指向子串的起止位置 通过set记录是否有重复元素,只要没有重复都可以移动right,更新最长子串长度 如果有重复,移动left,并从set里移除对应的字符
def max_sub_len(s): existed = set() res = 0 left, right = 0, 0 while right < len(s): if s[right] not in existed: existed.add(s[right]) res = max(res, len(existed)) right += 1 else: t.remove(s[left]) left += 1 return res
在微服务的架构下,公司内部会有非常多的独立服务。
服务之间可以相互调用,往往大型应用调用链条很长,如果出现循环依赖将出现非常恶劣的影响。
对于一个具体应用,已知各个服务的调用关系(即依赖关系),请判断是否存在循环调用。
输入:
一组服务依赖关系list,('A', 'B') 表示 A 会调用 B 服务
service_relations = [('A', 'B'), ('A', 'C'), ('B', 'D'), ('D', 'A')]
输出:
由于存在 A - B - D - A 故存在循环依赖,返回True;反之如果不存在,返回False
Follow up:
1. 如果有多个环,请都检测出来
2. 返回每个环中的服务名
--- ### 116. 比较版本号 【Question】【Answer】
可以采用拓扑排序 或者 DFS思路解决
比较两个版本号 version1 和 version2。
如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1, 除此之外返回 0。
你可以假设版本字符串非空,并且只包含数字和 . 字符。
. 字符不代表小数点,而是用于分隔数字序列。
例如,2.5 不是“两个半”,也不是“差一半到三”,而是第二版中的第五个小版本。
你可以假设版本号的每一级的默认修订版号为 0。例如,版本号 3.4 的第一级(大版本)和第二级(小版本)修订号分别为 3 和 4。其第三级和第四级修订号均为 0。
示例 1:
输入: version1 = "0.1", version2 = "1.1"
输出: -1
示例 2:
输入: version1 = "1.0.1", version2 = "1"
输出: 1
示例 3:
输入: version1 = "7.5.2.4", version2 = "7.5.3"
输出: -1
示例 4:
输入:version1 = "1.01", version2 = "1.001"
输出:0
解释:忽略前导零,“01” 和 “001” 表示相同的数字 “1”。
示例 5:
输入:version1 = "1.0", version2 = "1.0.0"
输出:0
解释:version1 没有第三级修订号,这意味着它的第三级修订号默认为 “0”。
--- ### 117. 单链表(奇数位升序,偶数位降序)的排序 【Question】 单链表,奇数位升序,偶数位降序,现在要求整体排成全局升序 输入:1->200->10->120->30->8->88->4 输出:1->4->8->10->30->88->120->200【Answer】
方法1:分割+解析,两次遍历,线性空间
方法2:双指针,一次遍历,常数空间
--- ### 118. 蛇形打印二叉树 【Question】 输入一棵二叉树,比如: ``` 0 1 2 3 4 5 6 7 8 9 ``` 将它蛇形输出,结果如下: 0,1,2,6,5,4,3,7,8,9【Answer】
思路:链表可以随便拆、组合 先把奇数和偶数拆开,形成两个链表,一个升序和一个降序 1->10->30->88 200->120->8->4 然后将降序的反转,再合并成一个列表--- ### 119. 冒泡排序和快速排序的复杂度区别是什么,如何实现? 【Question】 ```javascript /** * Quick Sort **/ function quickSort(arr) { // 补全代码 } console.log(quickSort([1, 3, 10, 6, 2, 8, 7])) // output: [1, 2, 3, 6, 7, 8, 10] /** * BubbleSort **/ function bubbleSort(arr) { // 补全代码 } console.log(bubbleSort([1, 3, 10, 6, 2, 8, 7])) // output: [1, 2, 3, 6, 7, 8, 10] ```【Answer】
1. 依赖栈记录每层节点值:层次便利,按偶数层(根是 0 层)从右到左,奇数层从左到右输出,时间空间复杂度都是 O(n),n 是节点数 2. 不依赖栈,递归:d 是层数,for i from 0 to d, 如果 i 是偶数,后序遍历二叉树的 0到i层,输出第i层的节点;如果i是奇数,先序遍历 0到 i 层,也只输出第i层节点。空间复杂度是 O(d) 即递归深度,时间复杂度是 o(d^2) 因为 0层节点会被访问 d 次,1 层节点 d-1 次,以此递推。--- ### 120. 岛屿数量 【Question】【Answer】
>1. 快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 >1. 冒泡排序算法的运作如下:(从后往前)比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 ```javascript /** * Quick Sort O(NLogN) **/ function quickSort(arr) { const res = []; if (arr.length <= 1) { return arr; } const leftArr = []; const rightArr = []; const q = arr[0]; for (let i = 1, l = arr.length; i < l; i++) { if (arr[i] > q) { rightArr.push(arr[i]); } else { leftArr.push(arr[i]); } } return res.concat(quickSort(leftArr), [q], quickSort(rightArr)); } /** * BubbleSort O(N*N) **/ function bubbleSort(arr) { for (let i = 0, l = arr.length; i < l - 1; i++) { for (let j = i + 1; j < l; j++) { if (arr[i] > arr[j]) { let tem = arr[i]; arr[i] = arr[j]; arr[j] = tem; } } } return arr; } ```给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。
--- ### 131. 二路归并 【Question】【Answer】
我们可以将二维网格看成一个无向图,竖直或水平相邻的 1 之间有边相连。
为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0
最终岛屿的数量就是我们进行深度优先搜索的次数
class Solution { private: void dfs(vector<vector
>& grid, int r, int c) { int nr = grid.size(); int nc = grid[0].size(); grid[r][c] = '0'; if (r - 1 >= 0 && grid[r-1][c] == '1') dfs(grid, r - 1, c); if (r + 1 < nr && grid[r+1][c] == '1') dfs(grid, r + 1, c); if (c - 1 >= 0 && grid[r][c-1] == '1') dfs(grid, r, c - 1); if (c + 1 < nc && grid[r][c+1] == '1') dfs(grid, r, c + 1); } public: int numIslands(vector<vector<char>>& grid) { int nr = grid.size(); if (!nr) return 0; int nc = grid[0].size(); int num_islands = 0; for (int r = 0; r < nr; ++r) { for (int c = 0; c < nc; ++c) { if (grid[r][c] == '1') { ++num_islands; dfs(grid, r, c); } } } return num_islands; } }; </code></pre> </pre> </details> --- ### 121. 把中文数字转成int数字 【Question】
在中文页面解析、中文数据处理中,常常遇到用中文表示的数字,例如:五千三百万零五百零一。
我们一般需要把它转化成int型整数,进行实际存储和使用。 请完成一亿(不含)以内的中文数字到int整数的转换
--- ### 122. Hash表设计 【Question】 常规的hash表设计及变通。问题由浅入深递进 1. 基本的原理,负载因子,扩容的原理 2. 假设内存受限4G,hash表已经占用3G内存,怎么使用剩下的那一个G的内存空间 3. 怎么在文件中设计类似于hash表的结构,能够在文件中快速查找一个key/value对【Answer】
// 递归解法 def cn2digital(number): name2val = {u'一': 1, u'二': 2, u'三': 3, u'四': 4, u'五': 5, u'六': 6, u'七': 7, u'八': 8, u'九': 9} unit2count = {u'十': 10, u'百': 100, u'千': 1000, u'万': 10000} for unit in [u'万', u'千', u'百', u'十']: if unit in number: n1, n2 = number.split(unit) return cn2digital(n1) * unit2count.get(unit) + cn2digital(n2) if not number: return 0 for c in number: if c == u'零': continue return name2val.get(c) // 非递归解法 def cn2digital(number): name2val = {u'一': '1', u'二': '2', u'三': '3', u'四': '4', u'五': '5', u'六': '6', u'七': '7', u'八': '8', u'九': '9'} unit2count = {u'十': 1, u'百': 2, u'千': 3, u'万': 4} res = [] base_count = 0 for num in number[::-1]: if num in name2val: res.append(name2val.get(num)) continue zero_count = 0 if num in unit2count: zero_count = max(0, unit2count.get(num) + base_count - len(res)) if num == u'万': base_count += 4 for _ in range(zero_count): res.append('0') return 0 if not res else int(''.join(res[::-1])) assert cn2digital(u'一万零一') == 10001 assert cn2digital(u'三千五百万') == 35000000 assert cn2digital(u'三千五百一十万') == 35100000 assert cn2digital(u'三千五百零一万') == 35010000 assert cn2digital(u'三千五百零一万零五百') == 35010500 assert cn2digital(u'三千五百零一万五千五百五十五') == 35015555 assert cn2digital(u'一百万') == 1000000 assert cn2digital(u'二百三十四万三千四百九十三') == 2343493
--- ### 123. 1-n数字字典序第k大 【Question】 给你一个数字n(n < 1e9), 再给你一个数字k(k < n), 要求你找到1, 2, 3, ... n按照字典序排序后, 第k大的数字; 如, n = 15, k = 7; 那1 ~ 15按照字典序排序为: 1, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5, 6, 7, 8, 9; 则答案为15;【Answer】
1. 扩容时注意关注 rehash 过程 2. 类似于多个Hash组成一个List 3. 类似于leveldb的思路--- ### 124. 在数组中找出和值为给定值的两个数 【Question】 输入一维数组array和n,找出和值为0的任意两个元素。例如: ``` python array = [2, 3, 1, 10, 4, 30] n = 31 ``` 则结果应该输出[1, 30] 顺序不重要 如果有多个满足条件的,返回任意一对即可【Answer】
利用字典树的思想; 我们假设有这么一棵树, 每个节点都要10个儿子, 10条到儿子的边分别对应数据0~9; 那么我们在这棵树上, 对边按照0~9的顺序进行DFS, 当走到第k个节点时, 该节点对应的数字既为我们的第k大字典序数字;--- ### 125. 老虎吃羊问题 【Question】 在岛上有100只老虎和1只羊,老虎可以吃草,但他们更愿意吃羊。 假设: A:每次只有一只老虎可以吃样,而且一旦他吃了羊,他自己就变成羊。 B:所有的老虎都是聪明而且完全理性的,他们的第一要务是生存。 问最后这只羊会不会被吃?如果是n只老虎和一只羊呢?【Answer】
解法1: 本题容易想到用哈希表,迭代一次边建表边查找n - array[i]是否在hash_table中即可。 该方法空间开销比较大 解法2:先对数组做排序,然后首尾两个指针求和,如果小于n则左指针右移,如果大于n则右指针左移。 该方法时间复杂度O(nlogn) 推荐考察解法2,附带做了排序算法的考察--- ### 126. 爬虫url去重-多线程并发 【Question】 用爬虫抓取网页时, 一个较为重要的问题, 就是对爬去的网页去重; 请你详细的设计一种数据结构, 用来检验某个URL之前是否已经被爬取过; 并给出每次检验的复杂度, 以及整体的空间复杂度; 一般爬虫在实现时, 都会利用多线程并发的爬取; 现在需要你升级一下之前的实现, 以保证并发安全;【Answer】
思路:先simplify。 - 1只老虎,肯定吃。 - 2只老虎肯定不吃,否则就被另一只吃了。 - 3只老虎,如果一只老虎吃掉了羊,问题就转换为2只老虎和1只羊的情况,显然另外两种老虎不敢轻举妄动。所以羊会被吃。 - 4只老虎,如果某一只老虎吃了羊,问题转化为3只老虎和1只羊的问题,它肯定会被接下来的某一只吃掉,然后其他两只只能等着。所以4只老虎,大家都不敢吃羊。 这样归纳,我们就可以发现如果老虎数目是奇数,那么羊肯定被吃,如果是偶数,那么羊肯定不会被吃。--- ### 127. 蓄水问题, 1维 【Question】 给定一个一维数组用于描述一个海拔,相邻的海拔高度不同,则下雨后低洼海拔的洼地会有积水,假设雨水足够,能够填满所有低洼地段,计算下雨后所有低洼地段总蓄水量。 例如给定数组为: 5, 2, 1, 4, 3 则:所有低洼地段蓄水为量为 5【Answer】
通常来说, 不需要改变数据结构本身, 只需要在其外围包裹一些简单的操作, 就能大大提高其并发度; 比如可以根据URL的后几位, 进行hash分桶; 注意这里选取URL的后几位, 而不是前几位, 是为了让hash更加均匀, 因为同个域名下的前缀大多是相同的; 然后每个桶内维护一个上述实现的去重的数据结构;--- ### 128. 轮流抛硬币问题 【Question】 # A和B玩抛硬币游戏,AB轮流抛一枚硬币,谁先抛到正面谁就获胜并结束游戏,硬币两面均匀。A先抛,请问A获胜的概率是多少?【Answer】
定义左极高点: 该点左边最高的那个点; 定义右极高点: 该点右边最高的那个点; 于是每个点的蓄水高度为: min(左极高点高度, 右极高点高度) - 该点高度,累加每个点的高度即可;所有点的左右极点可以分别通过一次向右和向左的扫描得到; 算法复杂度为 O(n)--- ### 129. 查找第一个缺失的正整数 【Question】 查找第一个缺失的正整数。 时间复杂度O(n) ,空间复杂度 O(1) Example 1: Input: [1,2,0] Output: 3 Example 2: Input: [3,4,-1,1] Output: 2 Example 3: Input: [7,8,9,11,12] Output: 1【Answer】
将A和B的获胜情况罗列,可以看到规律。A第一次抛获胜概率是1/2, A不获胜情况下B第一次获胜概率1/2*1/2=1/4。 所以A获胜概率是:1/2+1/8+1/32+...=2/3。B获胜的概率是:1/4+1/16+...=1/3--- ### 130. 微信跳一跳 【Question】【Answer】
1 排序之后查找 2 把出现的数值放到与下标一致的位置,再判断什么位置最先出现不连续的数值,就是答案了。 3 和2差不多,把出现过的数值的下标位置做好标识,如果没有出现过的数组的下标就没有标识,那么这个就是答案从起点开始接下来有 100 个方块,相邻方块间的距离都为 1,每个方块上有增加体力的食用蘑菇或减少体力的毒蘑菇,蘑菇带来的体力改变是已知的。一个人初始体力为 m,每次可以往前跳任意个方块,体力耗尽就会死掉。
- 每跳一次消耗的体力与跳的距离成正比,比例为 1。问这个人能否跳到终点,如果能,求可能剩余的最大体力。
- 每跳一次消耗的体力是跳的距离的平方。问这个人能否跳到终点,如果能,求可能剩余的最大体力。
- 每跳一次消耗的体力是跳的距离的平方,每跳一个方块加 1 分。问这个人能否跳到终点,如果能,求可能得到的最高分数。
【Answer】
- 第 1 问,贪心算法,时间复杂度 O(n)
def flip1(m, array): """ 微信跳一跳第一问 :param m: 初始体力 :param array: 蘑菇带来的体力改变 :return: 可能剩余的最大体力 """ for n in array: m -= 1 # 消耗的体力与跳的距离成正比 if m <= 0: # 体力不足死掉 return -1 if n > 0: # 只跳加体力的格子 m += n if array[-1] < 0: # 终点的蘑菇必须吃 m += array[-1] return m if m > 0 else -1
- 第 2 问,动态规划,时间复杂度 O(n^2)
def flip2(m, array): """ 微信跳一跳第二问 :param m: :param array: :return: 可能剩余的最大体力 """ # powers 表示在每个格子可能剩余的最大体力 powers = [m] + [0] * len(array) for i in range(1, len(array) + 1): for j in range(i): if powers[j] > (i - j) ** 2: powers[i] = max(powers[i], powers[j] - (i - j) ** 2 + array[i - 1]) return powers[-1] if powers[-1] > 0 else -1
- 第 3 问,动态规划,时间复杂度 O(n^3)
def flip3(m, array): """ 微信跳一跳第三问 :param m: :param array: :return: 可能跳的最多格子数 """ # scores 表示在每个格子得到不同分数时可能剩余的最大体力 scores = [{0: m}] + [{} for _ in range(len(array))] for i in range(1, len(array) + 1): for j in range(i): for score, power in scores[j].items(): left = power - (i - j) ** 2 if left > 0 and left + array[i - 1] > 0: scores[i][score + 1] = max(scores[i].get(score + 1, 0), left + array[i - 1]) return max(scores[-1].keys()) if scores[-1].keys() else 0
实现一个merge函数,功能是将两个有序数组,将它们合并成一个有序数组,如:
let arr1 = [1, 2] let arr2 = [-1, 2, 8, 9] merge(arr1,arr2) // 返回 [-1, 1, 2, 2, 8, 9]
--- ### 132. 拆分字符串 【Question】 输入一个列表的单词,及一个长字符串,判断字符串可否由列表中的单词组成。比如: 输入: 单词列表 dict:I, love, byte, bytedance 字符串 s:Ilovebytedance 输出: True【Answer】
function merge(arr1, arr2) { // 可以判断一下arr1、arr2是否是数组 if(!(Array.isArray(arr1) && Array.isArray(arr2))) throw '...' let i = 0, j=0, t=0 let temp = []; while (i < arr1.length && j < arr2.length) { temp[t++] = arr1[i] <= arr2[j] ? arr1[i++] : arr2[j++]; } while (i < arr1.length) { // 数组1有剩余 temp[t++] = arr1[i++]; } while (j < arr2.length) { // 数组2有剩余 temp[t++] = arr2[j++]; } return temp }
--- ### 133. 给定单链表,求离终点距离为 k 的节点 【Question】 给定单链表,求离终点距离为 k 的节点,要求只扫一次且空间复杂度为O(1)【Answer】
1. 用一个数组 validWords[] 记录字符串当前位置之前的字符串是否可以用 dict 组成,validWords[i]=True 可以,否则不可以。默认不可以 2. for 循环 i 从 0 到 s.length: a. 如果 s[0-i] 在 dict 中,设置 validWords[i]=True b. 如果validWords[i]=True,for 循环 j 从 i+1 到 s.length-1,判断 s[i+1 到 j] 是否在 dict 中,如果是,设置 validWords[j]=True 3. 如果 validWords[s.length-1] = True, return True--- ### 134. 数组0值筛选 【Question】 给定一个非负数组,要求你对它进行操作, 使得所有的0 放在数组左边, 大于 0 的值位于数组右边, 要求空间为O(1), 时间为O(n);【Answer】
两个指针, 第一个先向后走k步; 然后两个一起走; 当第一个指针到达链表尾部时另一个指针指向的就是距离终点距离为 k 的节点。--- ### 135. 区间合并 【Question】 给定一堆左右闭合的区间,要求对重叠的区间进行合并,返回合并后的区间段。 例如:[9, 10], [1,4], [3,6], [8, 12] 那么合并后的区间段为:[1, 6], [8, 12]【Answer】
从右往左扫描,同时维护另一个指针指向出现在最右侧的 0,每次扫描到非 0 数字则和它交换--- ### 136. 爬楼梯问题 【Question】【Answer】
由于输入的区间段,不一定按照起点排好序,所以先按照起点坐下排序利于后续合并。 ``` python def merge(intervals): res = [] intervals = sorted(intervals, key=lambda x:x.start) pre = None for interval in intervals: if not pre: pre = interval continue if interval.start <= pre.end: if interval.end >= pre.end: pre.end = interval.end else: res.append(pre) pre = interval if pre: res.append(pre) return res ```
- 爬楼梯问题:爬楼梯时,每一步会有两个选择:爬一个台阶和爬两个台阶,问:楼梯总台阶数为n,则一共有多少种爬法,写一个函数f,使得:总的爬法= f(n)。举例:n=3时,则共有:(1,1,1)、(1,2) 、(2,1)三种爬法,则f(3)=3。
--- ### 137. 找零钱问题 【Question】 有1,2,5,10等不同零钱,问给N元,有多少种不同的组合方式?【Answer】
斐波拉契:f(n)=f(n-1)+f(n-2)
代码需要判断边界
--- ### 138. 把二叉树压成单链表 【Question】 对于输入的二叉树,舍弃left指针,用right指针按照先根顺序串成单链表。例如: ``` 1 2 5 3 4 ``` 转为单链表为1 -> 2 -> 3 -> 4 -> 5 要求in-place【Answer】
假设有m种零钱,具体面值存在arr中,要找的钱为n。 使用m种零钱去找零n元,可以拆分为: 完全不用第m种零钱 和 至少用一次第m种零钱 ``` python def zhaolin(arr, n, m): if n == 0: return 1 if n < 0: return 0 if m <= 0: return 0 return zhaolin(arr, n, m - 1) \ # 不用第m-1号零钱 + zhaolin(arr, n - arr[m - 1], m) # 至少使用1次m-1号零钱 ```--- ### 139. 最短子数组之和 【Question】 给定1个正整数数组array和1个正整数n,从array中寻找和值**大于等于n**的最短子数组。 如果存在,则返回最短子数组长度;如果不存在返回0。 例如:array = [1, 3, 4, 3, 9, 1], n = 12, 那么子数组[3, 9]满足条件且长度最短为2。【Answer】
本题利用dfs递归比较好解,关键点是要把左子树的最后一个节点和右子树头接上。 关键代码: ``` python def flatten(root): if not root: return if root.left: flatten(root.left) if root.right: flatten(root.right) tmp = root.right root.right = root.left root.left = None while root.right: root = root->right root.right = tmp ```--- ### 140. 二叉树最大宽度 【Question】【Answer】
本题用两个指针的思路解决,时间复杂度O(n) 指针left和right记录子数组的左右边界位置, 让right向右移,直到子数组和>=n或到达数组末尾,更新最短距离, 将left像右移一位,然后在和值中减去移去的值, 重复上面的步骤,直到right到达末尾,且left也无法再右移 ``` python def min_sub_len(arrary, n): res = sys.maxsize left, cur_sum = 0, 0 for i in range(0, len(array)): cur_sum += array[i] while left <= i && cur_sum >= n: res = min(res, i - left + 1) cur_sum -= array[left] left += 1 return 0 if res == sys.maxsize else res ```给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度(每一层的节点可能为空)。
--- ### 141. 二叉树中序遍历打印节点信息 【Question】 实现一个二叉树中序遍历函数,打印所有节点信息。 typedef string DataType; typedef struct BinTreeNode { DataType data; struct BinTreeNode* leftChild; struct BinTreeNode* rightChild; } BinTreeNode; typedef BinTreeNode* BinaryTree; void MidOrderPrint(BinaryTree tree) { //打印二叉树节点信息 }【Answer】
每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。
思路:主要想法是给每个节点一个 position 值,如果我们走向左子树,那么 position -> position * 2,如果我们走向右子树,那么 position -> positon * 2 + 1。当我们在看同一层深度的位置值 L 和 R 的时候,宽度就是 R - L + 1。
方法 1:深度优先搜索
方法 2:宽度优先搜索
--- ### 142. 从日志文件中抽取指定分钟的行 【Question】 某个很大(假设有几十T)的日志文件,每行的前两列为日期和时间(时间相等或递增),之后为日志内容,日志内容长度不一。例如: 2015-01-01 00:00:01 this is the first line 2015-01-01 00:00:03 this is another line ... ... 2017-12-31 12:34:45 this is the last line 需要将这个日志文件的某一分钟(例如2017-10-01 10:02这一分钟)的日志保存到另一个文件中。【Answer】
二叉树的遍历按照根节点位置的不同,分为前序遍历、中序遍历、后序遍历。 前序遍历:根节点->左子树->右子树 中序遍历:左子树->根节点->右子树 后序遍历:左子树->右子树->根节点 例如二叉树结构如下 ........... a ........../.....\ ........b........c ....../....\ ....d........e .....\ ...../ ......f....g 前序遍历:abdfegc 中序遍历:dfbgeac 后序遍历:fdgebca 解法一:递归方式 void MidOrderPrint(BinaryTree tree) { if(NULL == tree) { return; } MidOrderPrint(tree->leftChild); printf("%s ", tree->data.c_str()); MidOrderPrint(tree->rightChild); } | | 解法二:非递归方式 //假设 Stack 为已经实现的堆栈类型,支持push和pop方法 class Stack { void push(BinTreeNode *); BinTreeNode *pop(); BinTreeNode *top(); }; void MidOrderPrint(BinaryTree tree){ if(NULL == tree) { printf("this tree is empty!\n"); return; } Stack s; BinTreeNode *curNode = tree; while(curNode != NULL || s.top() != NULL) { while(curNode != NULL) { s.push(curNode); curNode = curNode->leftChild; } curNode = s.pop(); printf("%s ",curNode->data); curNode = curNode ->rightChild; } } _--- ### 143. 概率问题:赌徒获胜胜率计算 【Question】 有两个技巧相当的赌徒 A 和 B(即两人赌博胜率各为0.5),现在设定这样的获胜规则: 1. A只要赢了2局或以上就获胜 2. B要赢3局或以上才能获胜。 问双方胜率各为多少?【Answer】
使用二分法寻找位置,seek到文件的该位置读取下一行的内容来判断需要寻找的行。 本题考察二分查找和对文件操作的了解。需要考虑seek到一行中间的情况。--- ### 144. 旋转链表 【Question】 给定单链表,要求返回向右移k位后的新链表,例如: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> NULL k = 3,则返回:4 -> 5 -> 6 -> 1 -> 2 ->3 -> null【Answer】
如果直接列举所有情况也是可以得出答案的,但有简单方法可以剪枝。 我们用a表示A取胜,用b表示B取胜。 只要意识到,**无论结果如何,最多4局就可分出胜负**,这样就好计算了。 可以列举所有可能的情形如下: aaaa aaab abba bbab \ baaa baba abab babb \ abaa bbaa aabb abbb \ aaba baab bbba bbbb 也可以计算B获胜的情况 1(bbbb)+ 4(abbb babb bbab bbba) 所以A获胜概率是\dfrac{11}{16},B获胜概率是\dfrac{5}{16}--- ### 145. 链表求和 【Question】 给定一个链表`L1`、`L2`,每个元素是为10以内的正整数,链表表示一个数字,表头为高位。 求两个链表之和,以链表形式返回 如: ``` L1 5 -> 6 -> 2 -> 3 -> 7 L2 1 -> 7 -> 0 -> 9 -> 2 和为: 56237+17092=73329 ``` 拓展1: 表头改为低位 拓展2: 两表内数字不重复,优化 拓展3:【Answer】
用一快一慢两个指针fast,slow,快指针提前走k步 然后快慢一起走,直到fast.next == NULL 这是slow->next即为新head,将fast.next指向head,并从slow处断开 本题要注意参数处理: 空链表 k大于链表长度--- ### 146. 多叉树最大高度 【Question】【Answer】
```java /** * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { /** * @param l1: the first list * @param l2: the second list * @return: the sum list of l1 and l2 */ public ListNode addLists(ListNode l1, ListNode l2) { if(l1 == null && l2 == null) return null; ListNode head = new ListNode(0); ListNode tail = head; int carry = 0; while(l1 != null && l2 != null){ int value = carry + l1.val + l2.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l1 = l1.next; l2 = l2.next; } while(l1 != null){ int value = carry + l1.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l1 = l1.next; } while(l2 != null){ int value = carry + l2.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l2 = l2.next; } if(carry > 0){ ListNode node = new ListNode(carry); tail.next = node; tail = tail.next; } return head.next; } } ``` 拓展1 翻转 拓展2 set/bitmap要求:
- 自定义多叉树节点node结构(只需要定义节点结构即可,无需构建树)
- 求多叉树的最大高度 getDepth(node)
--- ### 147. 单链表对折 【Question】 对输入的链表做对折操作 例如:有n个节点的单链表:1 -> 2 -> 3 -> ... -> n-2 -> n-1 -> n -> NULL 处理后的链表为1 -> n > 2 -> n-1 -> 3 -> n-2 ... 要求在原链表基础上操作。【Answer】
节点信息
// 定义树节点 Node { ... // 属性 children:[], // 子节点 }
getDepth实现
function getDepth(node){ if (!node) return 0 if (node.children && node.children.length){ let depth=0; for(let e of node.children){ depth=Math.max(getDepth(e),depth) } return depth+1 }else{ return 1 } }
--- ### 148. 螺旋打印二维数组 【Question】 输入m * n的二维矩阵,要求从(0,0)开始螺旋向内完成打印输出。 具体打印方法: 1. ➡️先从左向右打印第一行; 2. ⤵️再从上向下打印最后一列; 3. ⬅️然后从右向左打印最后一行; 4. ⤴️最后从下向上打印第一列。 如此往复,完成所有元素打印。 例如: ``` python input = [ [ 1, 2, 3, 4, 5], [14, 15, 16, 17, 6], [13, 20, 19, 18, 7], [12, 11, 10, 9, 8], ] output = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ``` 附件要求:不允许再开辟O(mn)的存储,直接打印输出。【Answer】
解题思路 1. 先找到中点,将原链表1分为2,即为l_1, l_2; 2. 然后翻转l_2; 3. 最后对l_1和l_2归并。 本题主要考查候选人的编程功底以及好的编程习惯。 比较好的答案应该是4个函数: * def fold(head): # 对折入口函数 * def find_middle(head): # 找中点函数,用一快一慢两指针 * def reverse(head): # 链表翻转 * def merger(l1, l2): # 链表合并--- ### 149. 字符串相似度-编辑距离 【Question】 百科定义:编辑距离,又称Levenshtein距离(也叫做Edit Distance),是指两个字串之间,由一个转成另一个所需的最少编辑操作次数,如果它们的距离越大,说明它们越是不同。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。 例如将kitten一字转成sitting: sitten (k→s) sittin (e→i) sitting (→g)【Answer】
``` python def print_martix(matrix): ''' : type martix: list[list] ''' if not matrix: return start_row, start_col, end_row, end_col = 0, 0, len(matrix) - 1, len(matrix[0]) - 1 while start_row <= end_row and start_col <= end_col: for k in xrange(start_col, end_col + 1): print matrix[start_row][k] start_row += 1 for k in xrange(start_row, end_row + 1): print matrix[k][end_col] end_col -= 1 if start_row > end_row or start_col > end_col: break for k in xrange(end_col, start_col - 1, -1): print matrix[end_row][k] end_row -= 1 for k in xrange(end_row, start_row - 1, -1): print matrix[k][start_col] start_col += 1 ``` 测试用例 ``` python input0 = [[]] input1 = [[1]] input2 = [[1, 2]] input3 = [[1], [2]] ```--- ### 150. 找旋转数组的中位数 【Question】 有一个有序数组如:1,2,3,4,5,6,7 随机选一个点比如5反转变成:5,6,7,1,2,3,4 对于上面翻转后的数组,求它的中位数。【Answer】
本题用DP思路来解决,递推关系: 若str1[i] == str2[j],temp=0,否则temp=1 d[i][j] = min([i-1][j] + 1, d[i][j-1] + 1, d[i - 1, j - 1] + temp)--- ### 151. 求二叉树的最长路径 【Question】 给定一棵二叉树,求其中的最长路径,所谓路径是指:连通两个节点的最小边数。【Answer】
1. 最简单的,排序,复杂度最高; 2. 遍历整个数组,找到最小的数字比如 1,然后 (index+n/2)%n 3. 二分,找到最小的数字就能找到中位数。淘汰哪一半?--- ### 152. 精简文件路径 【Question】 对输入的unix风格的文件路径做精简。 例如:/a/b/.././ 精简为 /a【Answer】
使用后序遍历O(n)。遍历过程中计算以当前节点为根的最长路径,返回当前节点的高度。--- ### 153. 绝对众数 【Question】【Answer】
为了处理..,容易想到stack解决 ``` python def simplify_path(ppath): segs = path.split('/') stack = [] for seg in segs: if not seg or seg == '.': continue elif seg == '..': if len(stack): stack.pop() else: stack.append(seg) return '/' + '/'.join(stack) ``` 特殊case:/../; ////foo/https://leetcode-cn.com/problems/majority-element/
定义:给定N个数,称出现次数最多的数为众数,若某数出现的次数大于N/2称为绝对众数。如
A={1, 2, 1, 3, 2}中,1和2都是众数,但都不是绝对众数
如A={1,2,1,3,1}中,1是绝对众数。
--- ### 154. 环节点的走法数 【Question】 一个环上有10个点,编号为0-9, 从0点出发,每步可以顺时针到下一个点,也可以逆时针到上一个点, 求:经过n步又回到0点有多少种不同的走法? 举例: 如果n = 1,则从0出发只能到1或者9,不可能回到0,共0种走法 如果n = 2,则从0出发有4条路径:0->1->2, 0->1->0, 0->9->8, 0->9->0,其中有两条回到了0点,故一共有2中走法【Answer】
解题思路:任意删除2个不相同的数,不改变绝对众数
class Solution { public int majorityElement(int[] nums) { int m = nums[0]; // 绝对众数 int count = 0; // 计数 for(int i = 0; i< nums.length; i++) { if(count == 0) { m = nums[i]; count++; }else if(m == nums[i]) { count++; } else { count--; } } return m; } }
--- ### 155. 单词搜索 【Question】【Answer】
DP问题,令F(k, i)表示从0点出发经过k步到达i点的走法数,题目所求为F(n, 0) F(k, i) = F(k - 1, (i + 1) % 10) + F(k - 1, ((i - 1) + 10) % 10) 初始状态:f[1, 0] = 0, f[1, 1] = 1, f[1, 2] = 0, ... f[1, 9] = 1给定1个二维字符数组cmap和单词1个word,搜索word是否在map中。
搜索的定义是从cmap的任意位置开始,可以上下左右移动,依次和word每个字符匹配,如果word能匹配完,则存在,否则不存在。
注:cmap中的每个位置只能被访问1次
a c d z x t r o f i w o
例如上面的cmap,则'zoo'能搜索到,'wto'不能搜索到
--- ### 156. 寻找数组任一峰值 【Question】 峰值定义:比前后元素都大;数组可能存在多个峰值,返回任一个就行 例如:1 2 3 2 1 4 3 可以返回3或者4 可以默认前提: 1 任意元素不等于相邻元素 2 首、尾元素只要比右、左一个元素大即可认为是峰值【Answer】
比较容易想到用DFS解决,如果候选没思路,可以提示属于哪类问题,如果想到图就好办了。
def search(cmap, word): if not cmap or not word: return False visited = [[False] * len(cmap[0]) for _ in range(len(cmap))] for i in range(len(cmap)): for j in range(len(cmap[0])): if dfs(cmap, word, 0, i, j, visited): return True def dfs(cmap, word, pos, i, j, visited): if pos == len(word): return True if i < 0 or i >= len(cmap) or j < 0 or j > len(cmap[0]) or visited[i][j] or cmap[i][j] != word[pos]: return False find = False visited[i][j] = True for (ii, jj) in [(-1, 0), (1, 0), (0, -1), (0, 1)]: if dfs(cmap, word, pos + 1, i + ii, j + jj, visited): find = True break visited[i][j] = False # 易错点 return find
--- ### 157. 对称树判断 【Question】 判断一棵二叉树,是否是(左右)对称树: 对称例子: ``` For example, this binary tree is symmetric: 1 / \ 2 2 / \ / \ 3 4 4 3 ``` 不对称例子: ``` 1 / \ 2 2 \ \ 3 3 ```【Answer】
O(n)肯定能解决问题,但本题结合二分能优化时间复杂度 如果a[mid] < a[mid + 1]说明峰值后半段,否则在前半段 核心代码 ``` python def find_peak(a): left, right = 0, len(a) - 1 while left < right: mid = left + (right - left) / 2 if a[mid] < a[mid + 1]: left = mid + 1 else; right = mid return right--- ### 158. 给定链表,将其中的某一部分翻转 【Question】 给定链表,将其中的某一部分翻转, 要求空间复杂度为O(1);【Answer】
```java /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ public class Solution { public boolean isSymmetric(TreeNode root) { if (root == null) return true; return solve (root.left, root.right); } public boolean solve(TreeNode t1, TreeNode t2) { if (t1 == null && t2 == null) return true; if (t1 == null && t2 != null || t1 != null && t2 == null || t1.val != t2.val) return false; return solve(t1.left, t2.right) && solve(t1.right, t2.left); } } ```--- ### 159. 带TTL的N-kv cache 【Question】 "实现一个带过期的KV cache, 所有KV过期间隔相同, 满足如下性质: 1. 最多存储n对KV; 2. 如果大于n个, 则随意剔除一个已经过期的KV; 3. 如果没有过期的KV, 则将最老的那一对KV给剔除; 4. 查询时如果已经过期, 则返回空; 5. 所有操作复杂度都为O(1)"【Answer】
指针a与d指向要翻转部分的第一个节点,指针b指向a的上一个节点; 指针c指向a,并将a指向下一个节点,再将c插入到b的后面,重复执行该操作直到a走出要翻转的区间; 最后将a接在d后面,完成翻转。--- ### 160. 二叉树的镜像 【Question】 实现一个函数,完成输入一个二叉树,输出该二叉树的镜像。 二叉树结点的定义如下: ``` struct BinaryTreeNode { int data; BinaryTreeNode *Left; BinaryTreeNode *Right; }; ```【Answer】
用一个map来维护K->V索引; 用一个双向链表list来维护K, 保证越靠前的K时间越早; 用一个map来维护K->list.node, 该map用于删除时使用 剔除时, 从list取出最老的K, 从map中剔除即可; 最老的那个, 肯定最先过期; 如果都没有过期, 也只能剔除最老的那个; 因此在解决剔除与过期时, 完全同上; 为了解决过期返回空, 有比较简单的办法是在每次查询前先过期一次;--- ### 161. 输出二叉树左视角能看到的节点 【Question】 给定一颗二叉树: ``` 1 2 3 4 5 6 7 8 ``` 从左边看,输出能看到的 1,2,4,8 这四个节点,顺序无所谓。【Answer】
思路:先序遍历树的每个结点,若遍历到的结点有子结点,则交换它的两个子结点。 有两种实现方法: 1.递归实现: ``` void MirroRecursively(BinaryTreeNode *pNode) { if(NULL == pNode) return; if(NULL == pNode->Left && NULL == pNode->Right) return; BinaryTreeNode *pTemp = pNode->Left; pNode->Left = pNode->Right; pNode->Right = pTemp; if(pNode->Left) MirroRecursively(pNode->Left); if(pNode->Right) MirroRecursively(pNode->Right); } ``` 2.非递归实现,即使用循环实现: ``` void MirrorNonRecurively(BinaryTreeNode *pNode) { if(NULL == pNode) return; stack stackTreeNode; stackTreeNode.push(pNode); while(stackTreeNode.size()) { BinaryTreeNode *pNode = stackTreeNode.top(); stackTreeNode.pop(); if(NULL != pNode->Left || NULL != pNode->Right) { BinaryTreeNode *pTemp = pNode->Left; pNode->Left = pNode->Right; pNode->Right = pTemp; } if(NULL != pNode->Left) stackTreeNode.push(pNode->Left); if(NULL != pNode->Right) stackTreeNode.push(pNode->Right); } } ```--- ### 162. 日志提取 【Question】 你有一份非常大的日志文件; 日志的格式为: timestamp: content; 如 2017-01-01 20:00:00: hello hello hello; 需要你提取日志中指定时间内的内容; 如提取2017-01-01 20:00:00 ~ 2017-01-01 22:30:00的日志数据【Answer】
递归或者非递归都可以--- ### 163. 最多有两个不同字符的最长子串 【Question】 给定字符串s,返回最多包含两个不同字符的最长子串长度。 例如:s = 'abbcceefffffg' 最长子串为'eefffff'长度为5【Answer】
由于时间是递增的, 可以先二分到指定时间, 然后再进行读取;--- ### 164. 生成n阶螺旋数组 【Question】 本题是http://marvel.byted.org/#/question/detail/?id=816&nocontri=true的反向问题。 ``` python input = 1 output = [[1]] input = 2 output = [[1, 2], [4, 3]] input = 3 output = [[ 1, 2, 3], [ 8, 9, 4], [ 7, 6, 5]] ```【Answer】
本题采用两个指针left、right指针指向子串的起始和结束位置。 right不断前行,对left和right内的字符存入dict并计数,当dict的keys()超过2个时,向右移动left。 如此往复,不断更新最长子串长度。 ``` python def max_sub_len(s): max_len, left = 0, 0 count = collections.Counter() for right in xrange(len(s)): count[s[right]] += 1 while len(count) > 2: count[s[left]] -= 1 if count[s[left]] == 0: count.pop(s[left]) left += 1 max_len = max(max_len, right - left + 1) return max_len--- ### 165. 给定长度为n的整形数组,给定小一点的正数k,找到数组中出现次数大于 n/k 的数字 【Question】 举几个例子: 1. 假设 n=100,k=2,那么需要找到数组中出现次数大于 50 的数。 1. 假设 n=100,k=10,那么需要找到数组中出现次数大于 10 的数。【Answer】
``` python def gen_matrix(n): if n <= 0: return None matrix = [[0] * n for _ in xrange(n)] val = 1 start_row, start_col, end_row, end_col = 0, 0, n - 1, n - 1 while val <= n * n: for k in xrange(start_col, end_col + 1): matrix[start_row][k] = val val += 1 start_row += 1 for k in xrange(start_row, end_row + 1): matrix[k][end_col] = val val += 1 end_col -= 1 for k in xrange(end_col, start_col - 1, -1): matrix[end_row][k] = val val += 1 end_row -= 1 for k in xrange(end_row, start_row - 1, -1): matrix[k][start_col] = val val += 1 start_col += 1 return matrix ```--- ### 166. 平均延迟最大的调用链 【Question】【Answer】
1. 方案1:使用hashmap,遍历一次数组,将数组的数字当做key放入hashmap中,并将出现的次数作为value。之后再遍历hashmap将次数大于 n/k 的数打印出来即可 2. 方案2:假设要求空间复杂度为O(K),时间复杂度尽量低该怎么做? - 可以给一点提示:换一个思路,每次都从数组中删除K个互不相同的数,那么删除 n/k 次后,还在数组中的数的出现次数,应该至少都大于 n/k - 最终答案:申请一个K空间的hashmap,按照方案1的思路遍历数组并插入hashmap,每当hashmap有K个值时,就将hashmap里的value减1,如果为0,就从hashmap中删除该key。当数组遍历完,hashmap中剩余的key/value对,就基本是我们要找的数(还需要再次遍历hashmap检查一下次数)在微服务的架构下,公司内部会有非常多的独立服务。
服务之间可以相互调用,往往大型应用调用链条很多也很长,我们需要找出耗时最大的链条进行优化。(假设服务同时调用其依赖的下游服务)
例如:
A服务依赖B服务,平均调用延迟100ms,记为(A, B, 100)
其他依赖和延迟如下:
(A, C, 200)
(A, F, 100)
(B, D, 100)
(D, E, 50)
(C, G, 300)
那么服务A有三条调用链:A-B-D-E,A-C-G,A-F,平均延迟250,500,100
延迟最大的调用链是A-C-G,延迟为500ms
输入:
[(A, B, 100), (A, C, 200), (A, F, 100), (B, D, 100), (D, E, 50), (C, G, 300)]
输出:
500
Follow up:
1. 能否输出延迟最大的调用链,如果存在多条,都输出
--- ### 167. 最大能连续观看剧集数 【Question】 小明常常用BT下载连载电视剧、综艺观看。 因为种子、网络等诸多因素,可能不是所有的剧集都能现在下来,且现在的顺序也不一定按照从第一集到第n集排列。 请问:已知小明已下载的某部电视剧的剧集列表,求小明最多能连续观看多少集? 例如:episodes = [10, 1, 3, 4, 7, 6, 20, 5, 13, 23, 14] 那么小明做多能连续看[3, 4, 5, 6, 7]共5集 希望时间复杂度O(N)【Answer】
可以采用搜索的思路解决,例如DFS
--- ### 168. 根据访问日志统计出头条每日最火的n篇文章 【Question】 每日的访问日志记录有文章id,简化起见,访问的文章id存在输入数组中,给定n,要求返回查看次数最多的文章id 例如:visit_log = [ 10001, 1002, 10001, 20032, 302, 302] 如果n = 2,则根据上面的访问日志,不难看出[10001、302]是最火的2篇文章【Answer】
我们看一般的case寻找连续剧集,其实就看当前剧集i的前一集i-1下载了没,或者后一集i+1下载了没 也就是往前查查、往后查查,为了方便查,我们可以用hash,例如python里的set。 知道这个原理后,我们可以比较容易的写出代码。 ``` python def long_episode_count(episodes): remains = set(episodes) long_count = 0 for i in episodes: if i not in remains: continue remains.remove(i) while pre in remains: remains.remove(pre) pre -= 1 while next in remains: remains.remove(next) next += 1 long_count = max(long_count, next - pre - 1) return long_count ```--- ### 169. 实现字典树 【Question】 字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。 它有3个基本性质:根节点不包含字符,除根节点外每一个节点都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。 请实现字典树Class,并完成单词插入和查找函数。【Answer】
首先对访问日志的文章id做计数,然后根据计数做排序得到top n。 在排序方法选择方面,top n适合用堆来实现。 ``` python def top_article_id(visit_log, n): article_cnt = collections.Counter() for article_id in visit_log: article_cnt[article_id] += 1 heap = article_cnt.keys() def _heap_adjust(heap, parent, heap_size, article_cnt): while parent < heap_size: left, right = parent * 2 + 1, parent * 2 + 2 swap_pos = parent if left < heap_size and article_cnt[heap[left]] < article_cnt[heap[parent]]: swap_pos = left if right < heap_size and article_cnt[heap[right]] < article_cnt[heap[swap_pos]]: swap_pos = right if swap_pos != parent: heap[parent], heap[swap_pos] = heap[swap_pos], heap[parent] parent = swap_pos else: break for i in range(int(math.ceil(n / 2) - 1), -1, -1): heap_adjust(heap, i, n, article_cnt) for i in range(n, len(heap)): if article_cnt[heap[0]] < article_cnt[heap[i]]: heap[0] = heap[i] heap_adjust(heap, 0, n, article_cnt) return heap[:n]--- ### 170. 36进制正整数加法 【Question】 36进制由0-9,a-z,共36个字符表示,最小为'0' '0'~'9'对应十进制的0~9,'a'~'z'对应十进制的10~35 例如:'1b' 换算成10进制等于 1 * 36^1 + 11 * 36^0 = 36 + 11 = 47 要求按照加法规则计算出任意两个36进制正整数的和 如:按照加法规则,计算'1b' + '2x' = '48' 要求:**不允许把36进制数字整体转为10进制数字,计算出10进制累加结果再转回为36进制** 本题可任意改变进制,如加入大写字母变为62进制。【Answer】
参考实现代码: ``` python class TrieNode(object): def __init__(self): self.is_leaf = False self.children = [None] * 26 class Trie(object): def __init__(self): self.root = TrieNode() def insert(self, word): if not word: return cur_node = self.root for char in word: index = ord(char) - ord('a') if not cur_node.children[index]: cur_node.children[index] = TrieNode() cur_node = cur_node.children[index] cur_node.is_leaf = True def search(self, word): if not word: return False cur_node = self.root for char in word: index = ord(char) - ord('a') if not cur_node.children[index]: return False cur_node = cur_node.children[index] return cur_node.is_leaf ```--- ### 171. 数列(顺序为: 一堆负数 + 一堆 0 + 一堆正数),求最后一个出现的负数和第一个出现的正数 【Question】 数列(顺序为: 一堆负数 + 一堆 0 + 一堆正数),求最后一个出现的负数和第一个出现的正数的位置,从 1 开始计数。例如: -2,-1,0,0,0,0,4,5 最后一个负数出现的位置为:2,第一个正数出现的位置为7。【Answer】
``` python def add(num1, num2): ''' >>> add('', '') '' >>> add('', '1') '1' >>> add('ab', '') 'ab' >>> add('1b', '2x') '49' >>> add('0', '2x') '2x' >>> add('zz', '1') '100' ''' def _get_value(num): if ord('0') <= ord(num) <= ord('9'): return ord(num) - ord('0') return ord(num) - ord('a') + 10 def _to_chr(num): if 0 <= num <= 9: return str(num) return chr(ord('a') + num - 10) def _add(n1, n2, carry): result = _get_value(n1) + _get_value(n2) + carry carry = 1 if result >= 36 else 0 result %= 36 return _to_chr(result), carry len1, len2 = len(num1), len(num2) if len1 > len2: # 取巧:把两个数字长度对齐 num2 = '0' * (len1 - len2) + num2 elif len2 > len1: num1 = '0' * (len2 - len1) + num1 res = [] carry = 0 for i in xrange(max(len1, len2) - 1, -1, -1): tmp, carry = _add(num1[i], num2[i], carry) res.append(tmp) if carry: # 易错点:很容易遗漏 res.append('1') return ''.join(res[::-1]) # 易错点:需要翻转 ```--- ### 172. 青蛙跳石子 【Question】 在长度为m的地面格子上,每个格子里面有一些石子;有一只青蛙, 从格子开始处起跳,每次可以跳3到5个格子,求青蛙跳出格子最少需要踩几个石子;【Answer】
1. 二分查找 0 的位置; 2. 注意边界位置 0 的处理,在二分条件上需要做一些处理,如果只是找到 0 的位置然后遍历找到第一个和最后一个 0 的话, 复杂度为恶化; 3. 整体复杂度为 O(lg(n));--- ### 173. 给定一个字符串如下,请统计字符串中出现最多的字母和次数 【Question】 ```javascript function findMaxDuplicateChar(str) { let maxChar = '', maxValue = 1; // 补全代码..... return { maxChar, maxValue }; } const str = 'this is a fe test at toutiao on September'; findMaxDuplicateChar(str) // output: { maxChar:"t", maxValue:7 } ```【Answer】
简单DP dp[i]表示调到第i的位置最少需要踩几个石子; 于是dp[i]可由dp[i-3], dp[i-4], dp[i-5]得来;--- ### 174. 数组排序 【Question】【Answer】
```javascript function findMaxDuplicateChar(str) { if (str.length === 1) { return str; } const charObj = {}; let maxChar = '', maxValue = 1; for(let i = 0; i < str.length; i++) { if (str[i].trim() !== '') { if (!charObj[str.charAt(i)]) { charObj[str.charAt(i)] = 1; } else { charObj[str.charAt(i)] += 1; } } } for (const k in charObj) { if (charObj[k] >= maxValue) { maxChar = k; maxValue = charObj[k]; } } return { maxChar, maxValue }; } const str = 'this is a fe test at toutiao on September'; findMaxDuplicateChar(str) // output: { maxChar:"t", maxValue:7 } ```输入数组nums,要求输出升序排序后的结果。已知数组元素为非负整数,且当数组长度为n时,所有元素的值都小于n;
例:
[3, 2, 1, 3, 5, 0] -> [0, 1, 2, 3, 3, 5]
[0] -> [0]
--- ### 175. 二叉树转换成双向链表 【Question】 转换结果满足下面条件: 1. 不要新建一个链表空间,要原地做;直接使用 left 和 right 指针 2. 转换后的双向链表节点顺序要与中序遍历的结果一致 3. 二叉树最左边的节点作为双向链表的头节点 例如: 二叉树: 1 2 3 4 5 6 7 双向链表: 4 <-> 2 <-> 5 <-> 1 <-> 6 <-> 3 <-> 7【Answer】
O(n2)的排序算法:冒泡排序,插入排序,选择排序 等;
O(nlog(n))的排序算法:归并排序,快排 等;
O(N)的排序算法:空间换时间,利用计数实现,需要O(n)空间复杂度;
--- ### 176. 翻转单词 【Question】 给定一个字符串,逐个翻转字符串中的每个单词。【Answer】
二叉树遍历;递归;链表操作 原理上看,对根节点、根的左子树、根的右子树分别作处理: 1. 比如根节点1, 对于左子树,找到它的最右节点 5, 把 5 和 1 连接起来; 2. 对于 1 的右子树,找到它的最左节点 6,把 6 和 1 连接起来。 实现上,假设已经把左子树变做双向链表了,让指针一直向右走,就能找到最右节点和根连接; 右子树同理。 返回结果的时候,找到双向链表的最左节点就可以了。 ``` private Node convertToDoublyLinkedList(Node root) { if (root.getLeft() != null) { Node left = convertToDoublyLinkedList(root.getLeft()); while (left.getRight() != null) { left = left.getRight(); } left.setRight(root); root.setLeft(left); } if (root.getRight() != null) { Node right = convertToDoublyLinkedList(root.getRight()); while (right.getLeft() != null) { right = right.getLeft(); } right.setLeft(root); root.setRight(right); } return root; } ```--- ### 177. 两个数的和相加 【Question】【Answer】
根据情况,选择栈和队列即可 栈必须,队列可选给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
--- ### 178. 二叉树所有根到叶子路径组成的数字之和 【Question】 二叉树每个节点的value范围是1-9 例如: ``` 1 2 3 4 5 ``` 从根到叶子共3条:1->2->4, 1->2->5, 1->3 构成的数字为124,125,13,求和124 + 125 + 13 = 262即为所求【Answer】
--- ### 179. 三门问题/蒙蒂大厅难题 【Question】 你面前有三扇门,每个门后都有一个奖品,其中一个是一辆车,另外两个是山羊。 你首先挑选一扇门,姑且称之为A,其他两个门分别是B和C。 在打开你选择的门前,我先从B和C中选择一个没有车的门打开。 给你一个选择,你是坚持最开始的选择还是换到剩下未打开的门上? 如果我打开门的策略从随机选择,变成总是优先选择门B,只有在迫不得已的时候选择C。 问结果有变化么?【Answer】
``` python def tree_path_sum(root, val): if not root: return 0 val = val * 10 + root.val if not root.left and not root.right: return val return tree_path_sum(root.left, val) + tree_path_sum(root.right, val) ```--- ### 180. 海盗分金问题 【Question】 5个海盗要分100块金币,分配的协议是:按顺序一个一个来,轮到当前的海盗,他提出一个分配方案,如果包括他在内至少有50%的人同意,那么方案通过,否则这个海盗就会被喂鲨鱼,下一个海盗继续提出分配方案。 假设海盗都是纯理性而且冷血的,他们的第一原则是生存,第二原则就是拿到尽可能多的金子,第三原则是如果给的金币一样,他们倾向于选择有更少的海盗的分配方案。【Answer】
使用贝叶斯公式求解,p(a|b)*p(b) = p(a)*p(b|a) 三种假设,即假设车在门A,门B,门C后面。 D表示打开了门B但是车不在门后。 假设A:先验概率=1/3,似然度=1/2,后验概率=(1/3)*(1/2)/(1/2)=1/3 假设B:先验概率=1/3,似然度=0, 后验概率=0 假设C:先验概率=1/3,似然度=1,后验概率=2/3 变化之后概率相同。--- ### 181. 牛过河 【Question】【Answer】
思路: 5个人太多了,首先把问题简化。从1个人到5个人逐个分析。 * 1个人,显然自己全拿 * 2个人。1,2。2号海盗显然可以全部拿走,因为他自己的一票保证了50%。 * 3个人。1,2,3。首先,如果3号的方案没通过,那么1号将什么都得不到,而2号必定要除3号而后快。因此3号必须征得1号的支持,但又不能完全不给1号任何金币(第三原则)。因此分配方案是1 2 3 = 1 0 99。这样3号的方案能够得到1和3的支持。 * 4个人。1,2,3,4。首先,如果4号的方案没通过,那么2号将什么都得不到。因此4号只需要分配1 2 3 4 = 0 1 0 99 * 5个人。1,2,3,4,5。5号清楚的知道一旦他的方案不通过,1和3将什么都得不到,因而他只需要分配1 2 3 4 5 = 1 0 1 0 98,这样即足以保证。有一堆牛要过河,河的长度是l,河中间有n个石头,牛只能踩着石头过河,问去掉m个石头后(去掉这m个石头的方式是随机的)的每种情况牛能走的石头间距最小值中,最大的那一个是多少
--- ### 182. 求二叉树是否存在和值为N的路径 【Question】 从二叉树的根到叶子节点称为一条路径 路径上每个节点的value之和为路径和值 本题要求所有的路径中是否存在一条和值为N的。 follow-up:可以要求返回所有符合条件的路径【Answer】
二分最大值最小化问题
--- ### 183. special trim 【Question】 对输入的字符串,去除其中的字符'b'以及连续出现的'a'和'c' 例如: 'aacbd' -> 'ad' 'aabcd' -> 'ad' 'aaabbccc' -> '' 不允许使用类似string.replace函数。要求时间、空间复杂度尽量优化【Answer】
要求自己给出二叉树数类定义 本题很容易用递归解决,基本思路如下: ``` def path_exists(root, n): if not root: return False if not root.left and not root.right and root.value == n: return True return path_exists(root.left, n - root.value) or path_exists(root.right, n - root.value) ```--- ### 184. 约瑟夫问题 【Question】 假设有n个人,标号为1-n。 从第一个人开始计数,到第k个人则出列,随后从第k+1个人重新计数,到第k再出列。 直至剩下最后一个人。问最后剩下的人的编号?【Answer】
本题不好处理的是aaabccc,即:c和a相遇就要都去掉。 比较好的思路是用栈的思路解决。 ``` python def special_trim(s): if not s: return s res = [] for c in s: if c == 'b': continue if c == 'c' and res and res[-1] == 'a': res.pop() continue res.append(c) return ''.join(res) ```--- ### 185. 二叉搜索树中的第K小的元素 【Question】 给定二叉搜索树,求所有元素中第k小的。k<=节点总数【Answer】
考察链表使用。最简单直接的方法是使用循环链表。 具体思路是: 1. 构建循环链表,初始化数据; 2. 每到第k-1个结点,便p->next = p->next->next。 3. 循环结束条件为p = p->next,即只有一个结点,该结点所对应的值即为最后剩下的人。--- ### 186. 序列化和反序列化二叉树 【Question】 将一棵二叉树序列化为字符串,并能字符串反序列为一棵树。【Answer】
结合BST的性质,用递归很好解决,按照中序遍历即可 核心代码: ``` python def find_k_small(root, k): def _find(root): if not root: return -1 val = _find(root) if not k: return val k -= 1 if not k: return root.val return _find(root) _find(root) ```--- ### 187. 带过期和最大对象数限制的LRU-CACHE 【Question】 设计一个对象cache, 他支持下列两个基本操作: set(id, object), 根据id设置对象; get(id): 根据id得到一个对象; 同时它有下面几个性质: 1: x秒自动过期, 如果cache内的对象, x秒内没有被get或者set过, 则会自动过期; 2: 对象数限制, 该cache可以设置一个n, 表示cache最多能存储的对象数; 3: LRU置换, 当进行set操作时, 如果此时cache内对象数已经到达了n个, 则cache自动将最久未被使用过的那个对象剔除, 腾出空间放置新对象; 请你设计这样一个cache;【Answer】
本题解法应该有不少,大体思路是按照某种遍历的顺序记录下每个节点,叶子节点的空指针可以用特殊字符表示 例如用先根遍历解决: ``` python def serialize(root): def pre_order(node): if node: vals.append(str(node.val)) pre_order(node.left) pre_order(node.right) else: vals.append('#') vals = [] pre_order(root) return ' '.join(vals) def deserialize(data): def pre_order(): val = next(vals) if val == '#': return None node = TreeNode(int(val)) node.left = pre_order() node.right = pre_order() return node vals = iter(data.split()) return pre_order()--- ### 188. n sum 【Question】【Answer】
通过组合一些基本的数据结构, 来实现一些更高级的性质; 内部维护一个链表, list, 其元素为一个三元组(ID, timestamp, obj), 分别为对象ID, 上次被访问时间, 和对象内容; 在维护该list时, 需要保持一个性质, 越靠后的元素越新, 既timestamp越大; 内部再维护一个map, 该map表示一个ID到list节点的索引, 格式为map(ID, node); 对于get(id)操作: 1: 先在map中查找ID对应的list node; 2: 将node从list中取出, 即list.Remove(node); 3: 检查node.timestamp, 如果过期, 则返回null, 表示无数据, 并将ID从map中删除; 4: 如果未过期, 设置node.timestamp = now(), 并将node添加到list尾部, 即list.Append(node); 5: 返回node.obj; 对于set(id, obj)操作: 1: 同get(id)的1~3步操作, 删除对应的ID; 2: 如果此时空间满了, 既对象数为n, 则将list中表头的那个元素删除; 3: 更新list和map: node = new(ID, now(), obj), list.Append(node), map[ID] = node;输入一维数组array和n,找出和值为sum的n个元素即可,不用找出所有组合。
array = [2, 3, 1, 10, 4, 30] n = 2, sum = 31 result = find(array, n, sum) // result = [1, 30]
--- ### 189. 多叉树广度优先遍历查找 【Question】【Answer】
基础解法供参考
function find(arr, n, sum, shouldSort = true) { let sorted = arr; if (shouldSort) { sorted = arr.sort(); } const length = sorted.length; if (n === 2) { let front = 0; let back = length - 1; while(front < back) { const value = sorted[front] + sorted[back]; if (value === sum) { return [sorted[front], sorted[back]]; } else if (value > sum) { back -= 1; } else { front += 1; } } return null; } for(let i = 0; i < length; i += 1) { const val = sorted[i]; const result = find(sorted.slice([i + 1]), n - 1, sum - val, false); if (!result) { return null; } else { return [val, ...result]; } } }
要求:
- 自定义多叉树节点node结构(只需要定义节点结构即可,无需构建树)
- 按照广度优先查找符合要求的节点(没有符合要求的节点返回null),比如查找电话号码为 phone的用户信息,调用如下:
let node = wideTraversal(node,(e)=>e.phone===phone)
【Answer】
节点定义:
// 定义树节点 Node { ... // 属性 children:[], // 子节点 }
实现:
wideTraversal(node: Node, predict): Node | null { if (!node) return null let queue: Array
= []; queue.push(node) while (queue.length) { let cur = queue.shift() if (!cur) continue if (predict(cur)) return cur if (cur.children&& cur.children.length) { queue.push(...cur.children) } } return null }</code></pre> </pre> </details> --- ### 190. 给定无向图和它一个顶点,求无向图中到该顶点距离为K的所有点 【Question】 给定无向图和它一个顶点,求无向图中到该顶点距离为K的所有点,各个相邻顶点间距离为1
--- ### 191. 输出所有根节点到叶子节点的路径 【Question】 比如: 1 2 3 4 5 6 7 8 输出: 1,2,4 1,2,5 1,3,6 1,3,7,8【Answer】
方案:对图进行K步的BFS, 注意处理被重复走过的点;--- ### 192. 找出旋转有序数组中的最小值 【Question】 假设原数组为1,2,3,4,5那4,5,1,2,3就是旋转有序的数组 注:数组无重复元素【Answer】
二叉树遍历--- ### 193. 搜索二维矩阵 【Question】 给定二维m * n矩阵matrix,满足一定特性: 1. 每行从左到右递增 2. 每列从上到下递增 给定目标元素num,判断num是否在矩阵中存在 例如: ``` python matrix = [ [1, 3, 5, 10], [2, 4, 6, 11], [7, 9, 12, 20], ] ``` num = 4存在;num = 13不存在【Answer】
暴力可以O(n),但没用到旋转有序的特征。 本题可以用二分的思想降低时间复杂度 定义左右两个指针left、right指向头尾元素 如果a[left] < a[right]则没有移位,直接输出a[left]即可,反之二分搜索。 如果a[left] > a[mid] 则要搜索右半段,因为a[left]也大于a[right];反之搜索左半段。 核心代码 ``` python def find_min(arr): left, right = 0, len(arr) - 1 if arr[left] < arr[right]: return arr[left] while left != right - 1: mid = left + (right - left) / 2 if arr[left] < arr[mid]: left = mid else: right = mid return min(arr[left], arr[right])--- ### 194. 扑克牌的堆栈、队列操作 【Question】 我手中有一堆扑克牌, 但是观众不知道它的顺序。 * 第一步, 我从牌顶拿出一张牌, 放到桌子上。 * 第二步, 我从牌顶再拿一张牌, 放在手上牌的底部。 * 第三步, 重复第一步的操作, 直到我手中所有的牌都放到了桌子上。 最后, 观众可以看到桌子上牌的顺序是:13\12\11\10\9\8\7\6\5\4\3\2\1 请问, 我刚开始拿在手里的牌的顺序是什么?【Answer】
结合数组定义,观察例子,有两个特殊位置很特殊:左下角和右上角。 左下角的7往上所有的数变小,往右所有的数变大。 那么我们就可以将目标数字num和左下角比较,比目标小就往右搜,比目标大就往上搜。 如此往复可以判断num是否在matrix中。 ``` python def search(matrix, num): if not matrix: return False rows, cols = len(matrix), len(matrix[0]) if num < matrix[0][0] or num > matrix[rows - 1][cols - 1]: return False i, j = rows - 1, 0 # 左下角 while i >= 0 and j < cols: if matrix[i][j] < num: j += 1 elif matrix[i][j] > num: i -= 1 else: return True return False ```--- ### 195. 用js实现一个binarySearch二分查找 【Question】 定一个一个binarySearch的函数,传参能支持四个参数,分别是: >1. arr: 一个数组, >1. key: 一个需要查找的目标值, >1. low: 左边界 >1. high: 右边界 >1. 如果能知道则访问素组的位置,否则返回-1 ```javascript function binarySearch(){ // 补全代码 } var arr = [86,1,3,2,4,5,6,7,8,9,10,11,23,44]; var result = binary_search2(arr, 5, 0, 6); console.log(result); // 4 ```【Answer】
解法一: 这道题候选人容易出现折半的思路, 其实考虑的复杂了。 本质是将一个队列和栈做了两个操作 1. 出队、入栈 2. 出队、入队(队尾) 因为是看得到结果, 看不到初始顺序, 那么这个操作就是一个逆操作。 1. 出栈、入队 2. 出队(队尾)、入队(队首) 答案: 输入: 1,2,3,4,5,6,7,8,9,10,11,12,13, 输出: 1,12,2,8,3,11,4,9,5,13,6,10,7, 代码如下 ``` C++ 再改改 int doTheWork(std::deque * pQ, std::stack * pS) { if(NULL == pQ or NULL == pS) return -1; while(pS->size() > 0) { int val = pS->top(); pS->pop(); if (pQ->size() > 0) { int tmp = pQ->back(); pQ->pop_back(); pQ->push_front(tmp); pQ->push_front(val); } else { pQ->push_front(val); } } return 0; } ``` 解法二: 对手上牌按照a,b,c...进行编码,直接按顺序操作,输出结果和桌上实际结果对应,即为原手上牌的顺序。--- ### 196. 通配符匹配 【Question】 给定字符串s和模式串p,实现函数match(s, p),判断模式串p是否能完全匹配s 模式串中有两个特殊字符'?'和'*' '?'匹配任意1个字符;'* '匹配任意r个字符,包括空 例如: ``` python match('a', 'a') = True match('aa', 'a') = False match('a', '?') = True match('aa', '*') = True match('abc', '?*') = True ```【Answer】
二分法查找,也称折半查找,是一种在有序数组中查找特定元素的搜索算法。查找过程可以分为以下步骤: >1. 首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。 >1. 如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。 >1. 如果某一步数组为空,则表示找不到目标元素。 ```javascript // 递归实现的js代码 function binary_search2(arr, key, low, high) { if(low > high) { return -1; } var mid = parseInt((high + low) / 2); if(arr[mid] == key) { return mid; } else if(arr[mid] > key) { high =mid -1; return binary_search2(arr, key, low, high); } else if(arr[mid] < key) { low = mid +1; return binary_search2(arr, key, low, high); } } var arr = [86,1,3,2,4,5,6,7,8,9,10,11,23,44]; var arrSorted = arr.sort(function (a,b) { return a-b; }) var result = binary_search2(arrSorted, 5, 0, 10); console.log(result); // 4 ```--- ### 197. 原子计数问题 【Question】 给出一个分子式,比如: HMg2(H2O(N3Ag)2)3N2 计算这个分子式中每个原子出现了多少次,输出一个 map,比如上面的分子式中: map[string]int {"H": 7, "Mg": 2,"Ag": 6, ...} 分子式的规则: 1. 都以大写字母开头,后面跟 0 个或者 1 个小写字母,比如 Mg, H 2. 单个原子后面跟 0 个或者 1 个数字表示它出现的次数,比如 Mg2 表示 Mg 出现 2 次,数字范围 [2-1000] 3. 分子式中可能有括号,括号后面可能跟 0 个或者 1 个数字表示整个括号内的原子出现的次数,比如 (N3Ag)2 表示 N出现 6 次,Ag 出现 2 次 4. 括号可以嵌套 输入是合法的【Answer】
本题可以用DP算法来解答。 用dp[i, j]表示串s, p这两个串分别到i和j位置它们是否匹配。那么我们得到递推关系: 如果p[j] != '* ', dp[i, j] = dp[i-1, j-1] and (s[i] == p[j] || p[j] == '?') 如果p[j] == '* ', 那么dp[i][j] = dp[i - 1][j] || dp[i][j - 1] ``` python def match(s, p): m, n = len(s), len(p) dp = [[False] * (n + 1) for _ in range(m + 1)] dp[0][0] = True for j in range(1, n + 1): dp[0][j] = dp[0][j - 1] and p[j - 1] == '*' for i in range(1, m + 1): for j in range(1, n + 1): if p[j - 1] in [s[i - 1], '?']: dp[i][j] = dp[i-1][j-1] elif p[j - 1] == '*': dp[i][j] = dp[i - 1][j] or dp[i][j - 1] return dp[m][n] ``` 另外,本题也可以用贪心算法--- ### 198. 安卓解锁密码数 【Question】 安卓系统采用9个点连线构成图案密码。 要求连接以下9个点中**至少4个点**构成一条路径,从而得到1个密码。 1 2 3 4 5 6 7 8 9 任意点间均可连线,都有如下附加限制: 1. 在一个密码路径里每个点只能用1次; 2. 如果2号点没有被连,则1不能直接连3号点,即:1->3路径是非法的。类似情况还有1、7;2、8;1、9等等; 3. 如果2点已经连过了,则1可以连到3,3也可以连到1,即:2->1->3路径是合法的。 4. 1和6是可以直接连线的,因为二者之间没有点。 本问题求所有的密码数,也就是路径数,包含4个点、5个点。。。9个点的所有路径数。【Answer】
1. 最简单的方法是递归,碰到 ( 就开始递归 2. 担心爆栈就把递归改成非递归 3. 可以用正则表达式来做,这里不展开了,如果候选人懂的话让他解释--- ### 199. 矩阵中的最长递增路径 【Question】 给定m * n矩阵matrix,可以从任意位置开始,向上、向下、向左、向右移动,但要求下一个位置上的元素要大于当前元素。 找出最长的递增路径长度。【Answer】
本题目如果数据结构算法比较熟悉,会很快想到DFS。 难点是路径不能直达问题如何解决? 用visit记录某个点是否已经在路径中 ``` python visit = [[0, 0 ,0], [0, 0, 0], [0, 0, 0]] ``` 判断两个点能否连同,等价于判断两个点是否存在中间点问题,如果不存在可以直接连,如果存在要判断中间点是否已经访问过了。 中间点坐标为:|i1 - i2| / 2, |j1 - j2| / 2 另外,本题考虑到对称性会大大降低运算量 1、3、7、9点对称 2、4、6、8点对称--- ### 200. 升序数组求平方数不同的个数 【Question】 给定一个升序数组1,元素有重复,对每个元素算一下平方后得到新的数组2,问数组2中不相同的元素共有多少个?给出算法和空间复杂度,要求尽量优化。 举例: 数组1 [-13,-10,-9,-6,-5,-1,3,4,6,7,10,11,15,21,42] 平方后得到 数组2 [169,100,81,36,25,1,9,16,36,49,100,121,225,441,1764] 其中不相同的元素个数为13个。【Answer】
本题很容易想到dfs,但问题是每个点开始递归搜索,重复计算很多,结合DP提升效率。 用dp[i][j]表示从(i,j)开始的最长递增路径长度,当递归调用时,如果dp[i][j]不为0,直接返回dp[i][j]。 ``` python def long_increase_path_len(matrix): if not matrix: return 0 res = 1 m, n = len(martix), len(matrix[0]) dp = [[0] * n for _ in range(m)] for i in range(m): for j in range(n): res = max(res, dfs(matrix, dp, i, j, m, n)) return res def dfs(matrix, dp, i, j, m, n): if dp[i][j]: return dp[i][j] tmp_max = 1 dirs = [[0, -1], [-1, 0], [0, 1], [1, 0]]: for ni, nj in dirs: ii, jj = i + ni, j + nj if ii < 0 or ii >= m or jj < 0 or jj >= n or matrix[ii][jj] <= matrix[i][j]: continue tmp_max = max(tmp_max, 1 + dfs(matrix, dp, ii, jj, m, n) dp[i][j] = tmp_max return dp[i][j] ```--- ### 201. URL反转 【Question】 给定形如 `www.toutiao.com` 的 URL,将其转换成 `com.toutiao.www` 的形式,要求必须原地操作【Answer】
常规解法,按题目思路,先平方算好,再将结果插入hashset,最后输出hashset大小。 优化1,平方没必要算,其实就是绝对值。 优化2,注意到数组2其实是以0分隔的一个降序和一个升序数组。反序遍历降序数组,正序遍历升序数组,即可合并成一个升序数组,合并时做一下排重,最后输出合并后数组的元素个数。--- ### 202. 判断单向链表是否有环 【Question】【Answer】
1. 原地全部翻转一遍; 2. 遍历遇到".",继续翻转该部分字符串; 该题目重点考察编码,需要保证代码简洁,要不不允许使用字符串库函数判断一个链表中是否有环
例如:A->B->C->D->B->C->D
D指向B形成环
要求:在空间复杂度O(1)的情况下,时间复杂度最小
--- ### 203. 给定两个链表,求它们交叉节点 【Question】 1. 已知两个链表, 从某个节点开始就交叉了 2. 已知这两个链表的头节点, 求出交叉的节点【Answer】
创建两个指针slow,fast,同时指向这个链表的头节点。
然后让两个指针开始循环移动
slow每次向下移动一个节点,fast每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环
--- ### 204. 判断一个IP是否是国内 【Question】 如何快速判断一个ip地址是否属于国内?已知db中有几十万个国内ip地址段【Answer】
1. 依次遍历两个链表分别得到链表的长度M和N 2. 然后让长的那个一个从头开始走|M-N|的步数; 3. 两个指针同时走, 直到碰头--- ### 205. 用户在线峰值 【Question】 已知一天内用户登录登出的日志(数据量较大),求这一天用户在线的最大峰值和持续时间段 - 日志包含字段(userid, login_time, logout_time) - 登录登出时间精确到秒【Answer】
1. 将ip地址通过位移运算转成int 2. 对ip地址进行排序(可以考察任意一种排序算法) 3. 二分查找--- ### 206. 股票买卖问题 【Question】 给定一个数组代表股票每天的价格,请问只能买卖一次的情况下,最大化利润是多少?日期不重叠的情况下,可以买卖多次呢? 输入: {100, 80, 120, 130, 70, 60, 100, 125} 只能买一次:65(60 买进,125 卖出) 可以买卖多次: 115(80买进,130卖出;60 买进,126卖出) 提示:不用输出买卖的序列,只需要得到最大利润【Answer】
可以将这一天看成0-24*3600的离散的时间点,构造一个dict 每个用户在login_time对应value+1,logout_time对应value-1 得到用户在线数量的变化趋势,然后再遍历此dict求和 难点: - 想不到先求变化趋势 - 峰值时间段可能存在多个 ``` def get_max(logs): log_count = {} for log in logs: login_time = log['login_time'] logout_time = log['logout_time'] log_count[login_time] = log_count.setdefault(login_time, 0) + 1 log_count[logout_time] = log_count.setdefault(logout_time, 0) - 1 max, current_users, start, end, is_max, timeline = (0, 0, 0, 0, False, []) keys = log_count.keys() keys.sort(lambda a, b: a - b) for time_node in keys: current_users = current_users + log_count[time_node] if current_users > max: max = current_users start = time_node is_max = True elif current_users < max: if is_max: end = time_node is_max = False else: if is_max: end = time_node else: timeline.append((start, end)) start = time_node is_max = True timeline.append((start, end)) return max, timeline ```--- ### 207. 输出给定数字下一个比它大的数字 【Question】 比如数字:1234, 输出 1243 比如 1243,则输出 1324【Answer】
1. 对于只能买一次的情况: ``` public static int maximumProfit(int[] stockPrices) { int profit = 0; int minimumPrice = Integer.MAX_VALUE; /* * 对于给定的一天,最大利润等于 - * max(昨天为止的最大利润, 当天的价格 - 之前的最小价格) */ for(int i = 0; i < stockPrices.length; i++) { profit = Math.max(profit, stockPrices[i] - minimumPrice); minimumPrice = Math.min(stockPrices[i], minimumPrice); } return profit; } ``` 2. 对于可以买卖多次的情况,累积递增序列的差就可以了: ``` public static int maximumProfit2(int[] stockPrices) { int totalProfit = 0; for(int i=1; i 0){ totalProfit += currentProfit; } } return totalProfit; } ```--- ### 208. Path Sum 【Question】 给定一个二叉树和一个数字n,判断二叉树中是否有一个路径上的数字之和等于给定的数字n For example: Given the below binary tree and sum = 22, 5 / \ 4 8 / / \ 11 13 4 / \ \ 7 2 1 return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.【Answer】
用 1243 为例: 1. 从右向左扫描,找到第一个不是升序的数字,比如 2 2. 在 2 的右边,找到比它大的最小的数,是 3 3. 交换 2和 3,得到 1342 4. 把现在 3 右边的所有数字从大到小排序,得到 1324 (如果是排序则是 O(nlogn), 其实逆序就行了)--- ### 209. 单链表每隔k个元素做一次反转 【Question】 给定一个链表,每隔k个元素做一次反转 Example: Inputs: 1->2->3->4->5->6->7->8->NULL and k = 3 Output: 3->2->1->6->5->4->8->7->NULL. Inputs: 1->2->3->4->5->6->7->8->NULL and k = 5 Output: 5->4->3->2->1->8->7->6->NULL.【Answer】
public class Solution { public boolean hasPathSum(TreeNode root, int sum) { if(root == null) return false; int left = sum - root.val; if(root.left == null && root.right == null && left == 0) { return true; } return hasPathSum(root.left, left) || hasPathSum(root.right, left); } } 可以进一步问,输出所有和等于给定数字n的path--- ### 210. 用两个栈实现一个队列 【Question】 用两个堆栈模拟队列的功能,实现push,pop,count三个方法【Answer】
Node* rollback(Node *&head, int k) { Node *pre = NULL; Node *next = NULL; Node *curr = head; int count = 0; while (curr != NULL&&countnext; curr->next = pre; pre = curr; curr = next; count++; } if (curr != NULL) { head->next = rollback(next, k); } return pre; }--- ### 211. 赛马求最快N匹 【Question】【Answer】
简单的做法:栈s1和s2,始终维护s1作为存储空间,以s2作为临时缓冲区,push直接进s1,pop时s1导入s2,栈顶出栈,导回s1 优化做法:入队时,将元素压入s1,出队时,判断s2是否为空,如不为空,则直接弹出顶元素;如为空,则将s1的元素逐个“倒入”s2,把最后一个元素弹出并出队条件:
1.64匹马
2.8个赛道
3.每次比赛只能知道比赛结果名次,不能知道具体时间
求:
用最少的比赛次数,找出最快的4匹
--- ### 212. LRU cache 【Question】 实现一个LRU过期算法的KV cache, 所有KV过期间隔相同, 满足如下性质: 1. 最多存储n对KV; 2. 如果大于n个, 则随意剔除一个已经过期的KV; 3. 如果没有过期的KV, 则按照LRU的规则剔除一个KV; 4. 查询时如果已经过期, 则返回空;【Answer】
1.每次比赛至少能淘汰4匹(树形淘汰算法),因此淘汰60匹,至少需要15次比赛,回答15,是最差答案
2.如果能回答出12次的(经过加速之后的简单排序),为次优解:
1)先每8个一组,一共8组比8次
2)从第9次开始
*1.先取每组第一名,每次比赛,找出最快1匹,取出
*2.将最快这匹对应的组里次快的加入比赛,一共4次,找出最快4匹
3.如果能答出特定情况下10次,最差11次,为最优解(剪枝算法):
1)先每8个一组,一共8组比8次
2) 第9次,先取每组第一名,进行比赛,找出前四名
3) 第10次,将前4名对应的组中的第2名加入比赛,一共8匹,比赛一次,如果对应的前四名没发生变化,说明前4名就是最快4名
4)第11次
*1.假设有一个组里的第2名,进入前四名:
①有一组的第1名被挤出前4名,该组所有的候选马无法进入前4名
②另外两组第2名之后无法进入前4名
因此,4(第2名在第10次进入前4名的组对应的前4名)+2(还在前4名的另外两个第1名)=6匹马进行比赛,决出前4名,即为最终答案
*2假设有两个组的第2名,进入前四名:
①则另外两组都无法进入前4名,而还在前4名的两组的前4名4+4=8,跑一次,最终能得到最快4名
--- ### 213. 求数组的最大区间和 【Question】 # 输出一个 int 型数组的最大连续子数组(所有元素加和最大)各个元素之和 # 保证数组中至少有一个正数 例: 输入:{1,2,5,-7,8,-10} 输出:9 (子数组为: {1,2,5,-7,8})【Answer】
用一个map来维护K->V索引; 用一个双向链表list来维护K, 保证越靠前的K时间越早; 用一个map来维护K->list.node, 该map用于删除时使用 剔除时, 从list取出最老的K, 从map中剔除即可; 最老的那个, 肯定最先过期; 如果都没有过期, 也只能剔除最老的那个; 因此在解决剔除与过期时, 完全同上; 为了解决过期返回空, 有比较简单的办法是在每次查询前先过期一次; 解法大致同上; 为了实现LRU, 可在每次get后, 将该K从cache中删除并重新插入一遍;--- ### 214. 最长无重复子串 【Question】【Answer】
复杂度 `O(n)` 的算法 ``` int _tmain(int A[], _TCHAR* argv[]) { int array_length = sizeof(A) / sizeof(A[0]); int sum = 0; int thisSum = 0; for (int i = 0; i < array_length; i++) { thisSum += A[i]; if (thisSum > sum) { sum = thisSum; } else if (thisSum < 0) { thisSum = 0; } } printf("%d",sum); return 0; } ```无重复子串指:子串中每个字符都不相同 例如:s = 'aaabcdddd' 最长的无重复子串为'abcd'长度为4
--- ### 215. 服务循环依赖检测 【Question】【Answer】
用两个指针left、right指向子串的起止位置 通过set记录是否有重复元素,只要没有重复都可以移动right,更新最长子串长度 如果有重复,移动left,并从set里移除对应的字符
def max_sub_len(s): existed = set() res = 0 left, right = 0, 0 while right < len(s): if s[right] not in existed: existed.add(s[right]) res = max(res, len(existed)) right += 1 else: t.remove(s[left]) left += 1 return res
在微服务的架构下,公司内部会有非常多的独立服务。
服务之间可以相互调用,往往大型应用调用链条很长,如果出现循环依赖将出现非常恶劣的影响。
对于一个具体应用,已知各个服务的调用关系(即依赖关系),请判断是否存在循环调用。
输入:
一组服务依赖关系list,('A', 'B') 表示 A 会调用 B 服务
service_relations = [('A', 'B'), ('A', 'C'), ('B', 'D'), ('D', 'A')]
输出:
由于存在 A - B - D - A 故存在循环依赖,返回True;反之如果不存在,返回False
Follow up:
1. 如果有多个环,请都检测出来
2. 返回每个环中的服务名
--- ### 216. 比较版本号 【Question】【Answer】
可以采用拓扑排序 或者 DFS思路解决
比较两个版本号 version1 和 version2。
如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1, 除此之外返回 0。
你可以假设版本字符串非空,并且只包含数字和 . 字符。
. 字符不代表小数点,而是用于分隔数字序列。
例如,2.5 不是“两个半”,也不是“差一半到三”,而是第二版中的第五个小版本。
你可以假设版本号的每一级的默认修订版号为 0。例如,版本号 3.4 的第一级(大版本)和第二级(小版本)修订号分别为 3 和 4。其第三级和第四级修订号均为 0。
示例 1:
输入: version1 = "0.1", version2 = "1.1"
输出: -1
示例 2:
输入: version1 = "1.0.1", version2 = "1"
输出: 1
示例 3:
输入: version1 = "7.5.2.4", version2 = "7.5.3"
输出: -1
示例 4:
输入:version1 = "1.01", version2 = "1.001"
输出:0
解释:忽略前导零,“01” 和 “001” 表示相同的数字 “1”。
示例 5:
输入:version1 = "1.0", version2 = "1.0.0"
输出:0
解释:version1 没有第三级修订号,这意味着它的第三级修订号默认为 “0”。
--- ### 217. 单链表(奇数位升序,偶数位降序)的排序 【Question】 单链表,奇数位升序,偶数位降序,现在要求整体排成全局升序 输入:1->200->10->120->30->8->88->4 输出:1->4->8->10->30->88->120->200【Answer】
方法1:分割+解析,两次遍历,线性空间
方法2:双指针,一次遍历,常数空间
--- ### 218. 蛇形打印二叉树 【Question】 输入一棵二叉树,比如: ``` 0 1 2 3 4 5 6 7 8 9 ``` 将它蛇形输出,结果如下: 0,1,2,6,5,4,3,7,8,9【Answer】
思路:链表可以随便拆、组合 先把奇数和偶数拆开,形成两个链表,一个升序和一个降序 1->10->30->88 200->120->8->4 然后将降序的反转,再合并成一个列表--- ### 219. 冒泡排序和快速排序的复杂度区别是什么,如何实现? 【Question】 ```javascript /** * Quick Sort **/ function quickSort(arr) { // 补全代码 } console.log(quickSort([1, 3, 10, 6, 2, 8, 7])) // output: [1, 2, 3, 6, 7, 8, 10] /** * BubbleSort **/ function bubbleSort(arr) { // 补全代码 } console.log(bubbleSort([1, 3, 10, 6, 2, 8, 7])) // output: [1, 2, 3, 6, 7, 8, 10] ```【Answer】
1. 依赖栈记录每层节点值:层次便利,按偶数层(根是 0 层)从右到左,奇数层从左到右输出,时间空间复杂度都是 O(n),n 是节点数 2. 不依赖栈,递归:d 是层数,for i from 0 to d, 如果 i 是偶数,后序遍历二叉树的 0到i层,输出第i层的节点;如果i是奇数,先序遍历 0到 i 层,也只输出第i层节点。空间复杂度是 O(d) 即递归深度,时间复杂度是 o(d^2) 因为 0层节点会被访问 d 次,1 层节点 d-1 次,以此递推。--- ### 220. 岛屿数量 【Question】【Answer】
>1. 快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 >1. 冒泡排序算法的运作如下:(从后往前)比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 ```javascript /** * Quick Sort O(NLogN) **/ function quickSort(arr) { const res = []; if (arr.length <= 1) { return arr; } const leftArr = []; const rightArr = []; const q = arr[0]; for (let i = 1, l = arr.length; i < l; i++) { if (arr[i] > q) { rightArr.push(arr[i]); } else { leftArr.push(arr[i]); } } return res.concat(quickSort(leftArr), [q], quickSort(rightArr)); } /** * BubbleSort O(N*N) **/ function bubbleSort(arr) { for (let i = 0, l = arr.length; i < l - 1; i++) { for (let j = i + 1; j < l; j++) { if (arr[i] > arr[j]) { let tem = arr[i]; arr[i] = arr[j]; arr[j] = tem; } } } return arr; } ```给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。
--- ### 231. 二路归并 【Question】【Answer】
我们可以将二维网格看成一个无向图,竖直或水平相邻的 1 之间有边相连。
为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0
最终岛屿的数量就是我们进行深度优先搜索的次数
class Solution { private: void dfs(vector<vector
>& grid, int r, int c) { int nr = grid.size(); int nc = grid[0].size(); grid[r][c] = '0'; if (r - 1 >= 0 && grid[r-1][c] == '1') dfs(grid, r - 1, c); if (r + 1 < nr && grid[r+1][c] == '1') dfs(grid, r + 1, c); if (c - 1 >= 0 && grid[r][c-1] == '1') dfs(grid, r, c - 1); if (c + 1 < nc && grid[r][c+1] == '1') dfs(grid, r, c + 1); } public: int numIslands(vector<vector<char>>& grid) { int nr = grid.size(); if (!nr) return 0; int nc = grid[0].size(); int num_islands = 0; for (int r = 0; r < nr; ++r) { for (int c = 0; c < nc; ++c) { if (grid[r][c] == '1') { ++num_islands; dfs(grid, r, c); } } } return num_islands; } }; </code></pre> </pre> </details> --- ### 221. 把中文数字转成int数字 【Question】
在中文页面解析、中文数据处理中,常常遇到用中文表示的数字,例如:五千三百万零五百零一。
我们一般需要把它转化成int型整数,进行实际存储和使用。 请完成一亿(不含)以内的中文数字到int整数的转换
--- ### 222. Hash表设计 【Question】 常规的hash表设计及变通。问题由浅入深递进 1. 基本的原理,负载因子,扩容的原理 2. 假设内存受限4G,hash表已经占用3G内存,怎么使用剩下的那一个G的内存空间 3. 怎么在文件中设计类似于hash表的结构,能够在文件中快速查找一个key/value对【Answer】
// 递归解法 def cn2digital(number): name2val = {u'一': 1, u'二': 2, u'三': 3, u'四': 4, u'五': 5, u'六': 6, u'七': 7, u'八': 8, u'九': 9} unit2count = {u'十': 10, u'百': 100, u'千': 1000, u'万': 10000} for unit in [u'万', u'千', u'百', u'十']: if unit in number: n1, n2 = number.split(unit) return cn2digital(n1) * unit2count.get(unit) + cn2digital(n2) if not number: return 0 for c in number: if c == u'零': continue return name2val.get(c) // 非递归解法 def cn2digital(number): name2val = {u'一': '1', u'二': '2', u'三': '3', u'四': '4', u'五': '5', u'六': '6', u'七': '7', u'八': '8', u'九': '9'} unit2count = {u'十': 1, u'百': 2, u'千': 3, u'万': 4} res = [] base_count = 0 for num in number[::-1]: if num in name2val: res.append(name2val.get(num)) continue zero_count = 0 if num in unit2count: zero_count = max(0, unit2count.get(num) + base_count - len(res)) if num == u'万': base_count += 4 for _ in range(zero_count): res.append('0') return 0 if not res else int(''.join(res[::-1])) assert cn2digital(u'一万零一') == 10001 assert cn2digital(u'三千五百万') == 35000000 assert cn2digital(u'三千五百一十万') == 35100000 assert cn2digital(u'三千五百零一万') == 35010000 assert cn2digital(u'三千五百零一万零五百') == 35010500 assert cn2digital(u'三千五百零一万五千五百五十五') == 35015555 assert cn2digital(u'一百万') == 1000000 assert cn2digital(u'二百三十四万三千四百九十三') == 2343493
--- ### 223. 1-n数字字典序第k大 【Question】 给你一个数字n(n < 1e9), 再给你一个数字k(k < n), 要求你找到1, 2, 3, ... n按照字典序排序后, 第k大的数字; 如, n = 15, k = 7; 那1 ~ 15按照字典序排序为: 1, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5, 6, 7, 8, 9; 则答案为15;【Answer】
1. 扩容时注意关注 rehash 过程 2. 类似于多个Hash组成一个List 3. 类似于leveldb的思路--- ### 224. 在数组中找出和值为给定值的两个数 【Question】 输入一维数组array和n,找出和值为0的任意两个元素。例如: ``` python array = [2, 3, 1, 10, 4, 30] n = 31 ``` 则结果应该输出[1, 30] 顺序不重要 如果有多个满足条件的,返回任意一对即可【Answer】
利用字典树的思想; 我们假设有这么一棵树, 每个节点都要10个儿子, 10条到儿子的边分别对应数据0~9; 那么我们在这棵树上, 对边按照0~9的顺序进行DFS, 当走到第k个节点时, 该节点对应的数字既为我们的第k大字典序数字;--- ### 225. 老虎吃羊问题 【Question】 在岛上有100只老虎和1只羊,老虎可以吃草,但他们更愿意吃羊。 假设: A:每次只有一只老虎可以吃样,而且一旦他吃了羊,他自己就变成羊。 B:所有的老虎都是聪明而且完全理性的,他们的第一要务是生存。 问最后这只羊会不会被吃?如果是n只老虎和一只羊呢?【Answer】
解法1: 本题容易想到用哈希表,迭代一次边建表边查找n - array[i]是否在hash_table中即可。 该方法空间开销比较大 解法2:先对数组做排序,然后首尾两个指针求和,如果小于n则左指针右移,如果大于n则右指针左移。 该方法时间复杂度O(nlogn) 推荐考察解法2,附带做了排序算法的考察--- ### 226. 爬虫url去重-多线程并发 【Question】 用爬虫抓取网页时, 一个较为重要的问题, 就是对爬去的网页去重; 请你详细的设计一种数据结构, 用来检验某个URL之前是否已经被爬取过; 并给出每次检验的复杂度, 以及整体的空间复杂度; 一般爬虫在实现时, 都会利用多线程并发的爬取; 现在需要你升级一下之前的实现, 以保证并发安全;【Answer】
思路:先simplify。 - 1只老虎,肯定吃。 - 2只老虎肯定不吃,否则就被另一只吃了。 - 3只老虎,如果一只老虎吃掉了羊,问题就转换为2只老虎和1只羊的情况,显然另外两种老虎不敢轻举妄动。所以羊会被吃。 - 4只老虎,如果某一只老虎吃了羊,问题转化为3只老虎和1只羊的问题,它肯定会被接下来的某一只吃掉,然后其他两只只能等着。所以4只老虎,大家都不敢吃羊。 这样归纳,我们就可以发现如果老虎数目是奇数,那么羊肯定被吃,如果是偶数,那么羊肯定不会被吃。--- ### 227. 蓄水问题, 1维 【Question】 给定一个一维数组用于描述一个海拔,相邻的海拔高度不同,则下雨后低洼海拔的洼地会有积水,假设雨水足够,能够填满所有低洼地段,计算下雨后所有低洼地段总蓄水量。 例如给定数组为: 5, 2, 1, 4, 3 则:所有低洼地段蓄水为量为 5【Answer】
通常来说, 不需要改变数据结构本身, 只需要在其外围包裹一些简单的操作, 就能大大提高其并发度; 比如可以根据URL的后几位, 进行hash分桶; 注意这里选取URL的后几位, 而不是前几位, 是为了让hash更加均匀, 因为同个域名下的前缀大多是相同的; 然后每个桶内维护一个上述实现的去重的数据结构;--- ### 228. 轮流抛硬币问题 【Question】 # A和B玩抛硬币游戏,AB轮流抛一枚硬币,谁先抛到正面谁就获胜并结束游戏,硬币两面均匀。A先抛,请问A获胜的概率是多少?【Answer】
定义左极高点: 该点左边最高的那个点; 定义右极高点: 该点右边最高的那个点; 于是每个点的蓄水高度为: min(左极高点高度, 右极高点高度) - 该点高度,累加每个点的高度即可;所有点的左右极点可以分别通过一次向右和向左的扫描得到; 算法复杂度为 O(n)--- ### 229. 查找第一个缺失的正整数 【Question】 查找第一个缺失的正整数。 时间复杂度O(n) ,空间复杂度 O(1) Example 1: Input: [1,2,0] Output: 3 Example 2: Input: [3,4,-1,1] Output: 2 Example 3: Input: [7,8,9,11,12] Output: 1【Answer】
将A和B的获胜情况罗列,可以看到规律。A第一次抛获胜概率是1/2, A不获胜情况下B第一次获胜概率1/2*1/2=1/4。 所以A获胜概率是:1/2+1/8+1/32+...=2/3。B获胜的概率是:1/4+1/16+...=1/3--- ### 230. 微信跳一跳 【Question】【Answer】
1 排序之后查找 2 把出现的数值放到与下标一致的位置,再判断什么位置最先出现不连续的数值,就是答案了。 3 和2差不多,把出现过的数值的下标位置做好标识,如果没有出现过的数组的下标就没有标识,那么这个就是答案从起点开始接下来有 100 个方块,相邻方块间的距离都为 1,每个方块上有增加体力的食用蘑菇或减少体力的毒蘑菇,蘑菇带来的体力改变是已知的。一个人初始体力为 m,每次可以往前跳任意个方块,体力耗尽就会死掉。
- 每跳一次消耗的体力与跳的距离成正比,比例为 1。问这个人能否跳到终点,如果能,求可能剩余的最大体力。
- 每跳一次消耗的体力是跳的距离的平方。问这个人能否跳到终点,如果能,求可能剩余的最大体力。
- 每跳一次消耗的体力是跳的距离的平方,每跳一个方块加 1 分。问这个人能否跳到终点,如果能,求可能得到的最高分数。
【Answer】
- 第 1 问,贪心算法,时间复杂度 O(n)
def flip1(m, array): """ 微信跳一跳第一问 :param m: 初始体力 :param array: 蘑菇带来的体力改变 :return: 可能剩余的最大体力 """ for n in array: m -= 1 # 消耗的体力与跳的距离成正比 if m <= 0: # 体力不足死掉 return -1 if n > 0: # 只跳加体力的格子 m += n if array[-1] < 0: # 终点的蘑菇必须吃 m += array[-1] return m if m > 0 else -1
- 第 2 问,动态规划,时间复杂度 O(n^2)
def flip2(m, array): """ 微信跳一跳第二问 :param m: :param array: :return: 可能剩余的最大体力 """ # powers 表示在每个格子可能剩余的最大体力 powers = [m] + [0] * len(array) for i in range(1, len(array) + 1): for j in range(i): if powers[j] > (i - j) ** 2: powers[i] = max(powers[i], powers[j] - (i - j) ** 2 + array[i - 1]) return powers[-1] if powers[-1] > 0 else -1
- 第 3 问,动态规划,时间复杂度 O(n^3)
def flip3(m, array): """ 微信跳一跳第三问 :param m: :param array: :return: 可能跳的最多格子数 """ # scores 表示在每个格子得到不同分数时可能剩余的最大体力 scores = [{0: m}] + [{} for _ in range(len(array))] for i in range(1, len(array) + 1): for j in range(i): for score, power in scores[j].items(): left = power - (i - j) ** 2 if left > 0 and left + array[i - 1] > 0: scores[i][score + 1] = max(scores[i].get(score + 1, 0), left + array[i - 1]) return max(scores[-1].keys()) if scores[-1].keys() else 0
实现一个merge函数,功能是将两个有序数组,将它们合并成一个有序数组,如:
let arr1 = [1, 2] let arr2 = [-1, 2, 8, 9] merge(arr1,arr2) // 返回 [-1, 1, 2, 2, 8, 9]
--- ### 232. 拆分字符串 【Question】 输入一个列表的单词,及一个长字符串,判断字符串可否由列表中的单词组成。比如: 输入: 单词列表 dict:I, love, byte, bytedance 字符串 s:Ilovebytedance 输出: True【Answer】
function merge(arr1, arr2) { // 可以判断一下arr1、arr2是否是数组 if(!(Array.isArray(arr1) && Array.isArray(arr2))) throw '...' let i = 0, j=0, t=0 let temp = []; while (i < arr1.length && j < arr2.length) { temp[t++] = arr1[i] <= arr2[j] ? arr1[i++] : arr2[j++]; } while (i < arr1.length) { // 数组1有剩余 temp[t++] = arr1[i++]; } while (j < arr2.length) { // 数组2有剩余 temp[t++] = arr2[j++]; } return temp }
--- ### 233. 给定单链表,求离终点距离为 k 的节点 【Question】 给定单链表,求离终点距离为 k 的节点,要求只扫一次且空间复杂度为O(1)【Answer】
1. 用一个数组 validWords[] 记录字符串当前位置之前的字符串是否可以用 dict 组成,validWords[i]=True 可以,否则不可以。默认不可以 2. for 循环 i 从 0 到 s.length: a. 如果 s[0-i] 在 dict 中,设置 validWords[i]=True b. 如果validWords[i]=True,for 循环 j 从 i+1 到 s.length-1,判断 s[i+1 到 j] 是否在 dict 中,如果是,设置 validWords[j]=True 3. 如果 validWords[s.length-1] = True, return True--- ### 234. 数组0值筛选 【Question】 给定一个非负数组,要求你对它进行操作, 使得所有的0 放在数组左边, 大于 0 的值位于数组右边, 要求空间为O(1), 时间为O(n);【Answer】
两个指针, 第一个先向后走k步; 然后两个一起走; 当第一个指针到达链表尾部时另一个指针指向的就是距离终点距离为 k 的节点。--- ### 235. 区间合并 【Question】 给定一堆左右闭合的区间,要求对重叠的区间进行合并,返回合并后的区间段。 例如:[9, 10], [1,4], [3,6], [8, 12] 那么合并后的区间段为:[1, 6], [8, 12]【Answer】
从右往左扫描,同时维护另一个指针指向出现在最右侧的 0,每次扫描到非 0 数字则和它交换--- ### 236. 爬楼梯问题 【Question】【Answer】
由于输入的区间段,不一定按照起点排好序,所以先按照起点坐下排序利于后续合并。 ``` python def merge(intervals): res = [] intervals = sorted(intervals, key=lambda x:x.start) pre = None for interval in intervals: if not pre: pre = interval continue if interval.start <= pre.end: if interval.end >= pre.end: pre.end = interval.end else: res.append(pre) pre = interval if pre: res.append(pre) return res ```
- 爬楼梯问题:爬楼梯时,每一步会有两个选择:爬一个台阶和爬两个台阶,问:楼梯总台阶数为n,则一共有多少种爬法,写一个函数f,使得:总的爬法= f(n)。举例:n=3时,则共有:(1,1,1)、(1,2) 、(2,1)三种爬法,则f(3)=3。
--- ### 237. 找零钱问题 【Question】 有1,2,5,10等不同零钱,问给N元,有多少种不同的组合方式?【Answer】
斐波拉契:f(n)=f(n-1)+f(n-2)
代码需要判断边界
--- ### 238. 把二叉树压成单链表 【Question】 对于输入的二叉树,舍弃left指针,用right指针按照先根顺序串成单链表。例如: ``` 1 2 5 3 4 ``` 转为单链表为1 -> 2 -> 3 -> 4 -> 5 要求in-place【Answer】
假设有m种零钱,具体面值存在arr中,要找的钱为n。 使用m种零钱去找零n元,可以拆分为: 完全不用第m种零钱 和 至少用一次第m种零钱 ``` python def zhaolin(arr, n, m): if n == 0: return 1 if n < 0: return 0 if m <= 0: return 0 return zhaolin(arr, n, m - 1) \ # 不用第m-1号零钱 + zhaolin(arr, n - arr[m - 1], m) # 至少使用1次m-1号零钱 ```--- ### 239. 最短子数组之和 【Question】 给定1个正整数数组array和1个正整数n,从array中寻找和值**大于等于n**的最短子数组。 如果存在,则返回最短子数组长度;如果不存在返回0。 例如:array = [1, 3, 4, 3, 9, 1], n = 12, 那么子数组[3, 9]满足条件且长度最短为2。【Answer】
本题利用dfs递归比较好解,关键点是要把左子树的最后一个节点和右子树头接上。 关键代码: ``` python def flatten(root): if not root: return if root.left: flatten(root.left) if root.right: flatten(root.right) tmp = root.right root.right = root.left root.left = None while root.right: root = root->right root.right = tmp ```--- ### 240. 二叉树最大宽度 【Question】【Answer】
本题用两个指针的思路解决,时间复杂度O(n) 指针left和right记录子数组的左右边界位置, 让right向右移,直到子数组和>=n或到达数组末尾,更新最短距离, 将left像右移一位,然后在和值中减去移去的值, 重复上面的步骤,直到right到达末尾,且left也无法再右移 ``` python def min_sub_len(arrary, n): res = sys.maxsize left, cur_sum = 0, 0 for i in range(0, len(array)): cur_sum += array[i] while left <= i && cur_sum >= n: res = min(res, i - left + 1) cur_sum -= array[left] left += 1 return 0 if res == sys.maxsize else res ```给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度(每一层的节点可能为空)。
--- ### 241. 二叉树中序遍历打印节点信息 【Question】 实现一个二叉树中序遍历函数,打印所有节点信息。 typedef string DataType; typedef struct BinTreeNode { DataType data; struct BinTreeNode* leftChild; struct BinTreeNode* rightChild; } BinTreeNode; typedef BinTreeNode* BinaryTree; void MidOrderPrint(BinaryTree tree) { //打印二叉树节点信息 }【Answer】
每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。
思路:主要想法是给每个节点一个 position 值,如果我们走向左子树,那么 position -> position * 2,如果我们走向右子树,那么 position -> positon * 2 + 1。当我们在看同一层深度的位置值 L 和 R 的时候,宽度就是 R - L + 1。
方法 1:深度优先搜索
方法 2:宽度优先搜索
--- ### 242. 从日志文件中抽取指定分钟的行 【Question】 某个很大(假设有几十T)的日志文件,每行的前两列为日期和时间(时间相等或递增),之后为日志内容,日志内容长度不一。例如: 2015-01-01 00:00:01 this is the first line 2015-01-01 00:00:03 this is another line ... ... 2017-12-31 12:34:45 this is the last line 需要将这个日志文件的某一分钟(例如2017-10-01 10:02这一分钟)的日志保存到另一个文件中。【Answer】
二叉树的遍历按照根节点位置的不同,分为前序遍历、中序遍历、后序遍历。 前序遍历:根节点->左子树->右子树 中序遍历:左子树->根节点->右子树 后序遍历:左子树->右子树->根节点 例如二叉树结构如下 ........... a ........../.....\ ........b........c ....../....\ ....d........e .....\ ...../ ......f....g 前序遍历:abdfegc 中序遍历:dfbgeac 后序遍历:fdgebca 解法一:递归方式 void MidOrderPrint(BinaryTree tree) { if(NULL == tree) { return; } MidOrderPrint(tree->leftChild); printf("%s ", tree->data.c_str()); MidOrderPrint(tree->rightChild); } | | 解法二:非递归方式 //假设 Stack 为已经实现的堆栈类型,支持push和pop方法 class Stack { void push(BinTreeNode *); BinTreeNode *pop(); BinTreeNode *top(); }; void MidOrderPrint(BinaryTree tree){ if(NULL == tree) { printf("this tree is empty!\n"); return; } Stack s; BinTreeNode *curNode = tree; while(curNode != NULL || s.top() != NULL) { while(curNode != NULL) { s.push(curNode); curNode = curNode->leftChild; } curNode = s.pop(); printf("%s ",curNode->data); curNode = curNode ->rightChild; } } _--- ### 243. 概率问题:赌徒获胜胜率计算 【Question】 有两个技巧相当的赌徒 A 和 B(即两人赌博胜率各为0.5),现在设定这样的获胜规则: 1. A只要赢了2局或以上就获胜 2. B要赢3局或以上才能获胜。 问双方胜率各为多少?【Answer】
使用二分法寻找位置,seek到文件的该位置读取下一行的内容来判断需要寻找的行。 本题考察二分查找和对文件操作的了解。需要考虑seek到一行中间的情况。--- ### 244. 旋转链表 【Question】 给定单链表,要求返回向右移k位后的新链表,例如: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> NULL k = 3,则返回:4 -> 5 -> 6 -> 1 -> 2 ->3 -> null【Answer】
如果直接列举所有情况也是可以得出答案的,但有简单方法可以剪枝。 我们用a表示A取胜,用b表示B取胜。 只要意识到,**无论结果如何,最多4局就可分出胜负**,这样就好计算了。 可以列举所有可能的情形如下: aaaa aaab abba bbab \ baaa baba abab babb \ abaa bbaa aabb abbb \ aaba baab bbba bbbb 也可以计算B获胜的情况 1(bbbb)+ 4(abbb babb bbab bbba) 所以A获胜概率是\dfrac{11}{16},B获胜概率是\dfrac{5}{16}--- ### 245. 链表求和 【Question】 给定一个链表`L1`、`L2`,每个元素是为10以内的正整数,链表表示一个数字,表头为高位。 求两个链表之和,以链表形式返回 如: ``` L1 5 -> 6 -> 2 -> 3 -> 7 L2 1 -> 7 -> 0 -> 9 -> 2 和为: 56237+17092=73329 ``` 拓展1: 表头改为低位 拓展2: 两表内数字不重复,优化 拓展3:【Answer】
用一快一慢两个指针fast,slow,快指针提前走k步 然后快慢一起走,直到fast.next == NULL 这是slow->next即为新head,将fast.next指向head,并从slow处断开 本题要注意参数处理: 空链表 k大于链表长度--- ### 246. 多叉树最大高度 【Question】【Answer】
```java /** * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { /** * @param l1: the first list * @param l2: the second list * @return: the sum list of l1 and l2 */ public ListNode addLists(ListNode l1, ListNode l2) { if(l1 == null && l2 == null) return null; ListNode head = new ListNode(0); ListNode tail = head; int carry = 0; while(l1 != null && l2 != null){ int value = carry + l1.val + l2.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l1 = l1.next; l2 = l2.next; } while(l1 != null){ int value = carry + l1.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l1 = l1.next; } while(l2 != null){ int value = carry + l2.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l2 = l2.next; } if(carry > 0){ ListNode node = new ListNode(carry); tail.next = node; tail = tail.next; } return head.next; } } ``` 拓展1 翻转 拓展2 set/bitmap要求:
- 自定义多叉树节点node结构(只需要定义节点结构即可,无需构建树)
- 求多叉树的最大高度 getDepth(node)
--- ### 247. 单链表对折 【Question】 对输入的链表做对折操作 例如:有n个节点的单链表:1 -> 2 -> 3 -> ... -> n-2 -> n-1 -> n -> NULL 处理后的链表为1 -> n > 2 -> n-1 -> 3 -> n-2 ... 要求在原链表基础上操作。【Answer】
节点信息
// 定义树节点 Node { ... // 属性 children:[], // 子节点 }
getDepth实现
function getDepth(node){ if (!node) return 0 if (node.children && node.children.length){ let depth=0; for(let e of node.children){ depth=Math.max(getDepth(e),depth) } return depth+1 }else{ return 1 } }
--- ### 248. 螺旋打印二维数组 【Question】 输入m * n的二维矩阵,要求从(0,0)开始螺旋向内完成打印输出。 具体打印方法: 1. ➡️先从左向右打印第一行; 2. ⤵️再从上向下打印最后一列; 3. ⬅️然后从右向左打印最后一行; 4. ⤴️最后从下向上打印第一列。 如此往复,完成所有元素打印。 例如: ``` python input = [ [ 1, 2, 3, 4, 5], [14, 15, 16, 17, 6], [13, 20, 19, 18, 7], [12, 11, 10, 9, 8], ] output = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ``` 附件要求:不允许再开辟O(mn)的存储,直接打印输出。【Answer】
解题思路 1. 先找到中点,将原链表1分为2,即为l_1, l_2; 2. 然后翻转l_2; 3. 最后对l_1和l_2归并。 本题主要考查候选人的编程功底以及好的编程习惯。 比较好的答案应该是4个函数: * def fold(head): # 对折入口函数 * def find_middle(head): # 找中点函数,用一快一慢两指针 * def reverse(head): # 链表翻转 * def merger(l1, l2): # 链表合并--- ### 249. 字符串相似度-编辑距离 【Question】 百科定义:编辑距离,又称Levenshtein距离(也叫做Edit Distance),是指两个字串之间,由一个转成另一个所需的最少编辑操作次数,如果它们的距离越大,说明它们越是不同。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。 例如将kitten一字转成sitting: sitten (k→s) sittin (e→i) sitting (→g)【Answer】
``` python def print_martix(matrix): ''' : type martix: list[list] ''' if not matrix: return start_row, start_col, end_row, end_col = 0, 0, len(matrix) - 1, len(matrix[0]) - 1 while start_row <= end_row and start_col <= end_col: for k in xrange(start_col, end_col + 1): print matrix[start_row][k] start_row += 1 for k in xrange(start_row, end_row + 1): print matrix[k][end_col] end_col -= 1 if start_row > end_row or start_col > end_col: break for k in xrange(end_col, start_col - 1, -1): print matrix[end_row][k] end_row -= 1 for k in xrange(end_row, start_row - 1, -1): print matrix[k][start_col] start_col += 1 ``` 测试用例 ``` python input0 = [[]] input1 = [[1]] input2 = [[1, 2]] input3 = [[1], [2]] ```--- ### 250. 找旋转数组的中位数 【Question】 有一个有序数组如:1,2,3,4,5,6,7 随机选一个点比如5反转变成:5,6,7,1,2,3,4 对于上面翻转后的数组,求它的中位数。【Answer】
本题用DP思路来解决,递推关系: 若str1[i] == str2[j],temp=0,否则temp=1 d[i][j] = min([i-1][j] + 1, d[i][j-1] + 1, d[i - 1, j - 1] + temp)--- ### 251. 求二叉树的最长路径 【Question】 给定一棵二叉树,求其中的最长路径,所谓路径是指:连通两个节点的最小边数。【Answer】
1. 最简单的,排序,复杂度最高; 2. 遍历整个数组,找到最小的数字比如 1,然后 (index+n/2)%n 3. 二分,找到最小的数字就能找到中位数。淘汰哪一半?--- ### 252. 精简文件路径 【Question】 对输入的unix风格的文件路径做精简。 例如:/a/b/.././ 精简为 /a【Answer】
使用后序遍历O(n)。遍历过程中计算以当前节点为根的最长路径,返回当前节点的高度。--- ### 253. 绝对众数 【Question】【Answer】
为了处理..,容易想到stack解决 ``` python def simplify_path(ppath): segs = path.split('/') stack = [] for seg in segs: if not seg or seg == '.': continue elif seg == '..': if len(stack): stack.pop() else: stack.append(seg) return '/' + '/'.join(stack) ``` 特殊case:/../; ////foo/https://leetcode-cn.com/problems/majority-element/
定义:给定N个数,称出现次数最多的数为众数,若某数出现的次数大于N/2称为绝对众数。如
A={1, 2, 1, 3, 2}中,1和2都是众数,但都不是绝对众数
如A={1,2,1,3,1}中,1是绝对众数。
--- ### 254. 环节点的走法数 【Question】 一个环上有10个点,编号为0-9, 从0点出发,每步可以顺时针到下一个点,也可以逆时针到上一个点, 求:经过n步又回到0点有多少种不同的走法? 举例: 如果n = 1,则从0出发只能到1或者9,不可能回到0,共0种走法 如果n = 2,则从0出发有4条路径:0->1->2, 0->1->0, 0->9->8, 0->9->0,其中有两条回到了0点,故一共有2中走法【Answer】
解题思路:任意删除2个不相同的数,不改变绝对众数
class Solution { public int majorityElement(int[] nums) { int m = nums[0]; // 绝对众数 int count = 0; // 计数 for(int i = 0; i< nums.length; i++) { if(count == 0) { m = nums[i]; count++; }else if(m == nums[i]) { count++; } else { count--; } } return m; } }
--- ### 255. 单词搜索 【Question】【Answer】
DP问题,令F(k, i)表示从0点出发经过k步到达i点的走法数,题目所求为F(n, 0) F(k, i) = F(k - 1, (i + 1) % 10) + F(k - 1, ((i - 1) + 10) % 10) 初始状态:f[1, 0] = 0, f[1, 1] = 1, f[1, 2] = 0, ... f[1, 9] = 1给定1个二维字符数组cmap和单词1个word,搜索word是否在map中。
搜索的定义是从cmap的任意位置开始,可以上下左右移动,依次和word每个字符匹配,如果word能匹配完,则存在,否则不存在。
注:cmap中的每个位置只能被访问1次
a c d z x t r o f i w o
例如上面的cmap,则'zoo'能搜索到,'wto'不能搜索到
--- ### 256. 寻找数组任一峰值 【Question】 峰值定义:比前后元素都大;数组可能存在多个峰值,返回任一个就行 例如:1 2 3 2 1 4 3 可以返回3或者4 可以默认前提: 1 任意元素不等于相邻元素 2 首、尾元素只要比右、左一个元素大即可认为是峰值【Answer】
比较容易想到用DFS解决,如果候选没思路,可以提示属于哪类问题,如果想到图就好办了。
def search(cmap, word): if not cmap or not word: return False visited = [[False] * len(cmap[0]) for _ in range(len(cmap))] for i in range(len(cmap)): for j in range(len(cmap[0])): if dfs(cmap, word, 0, i, j, visited): return True def dfs(cmap, word, pos, i, j, visited): if pos == len(word): return True if i < 0 or i >= len(cmap) or j < 0 or j > len(cmap[0]) or visited[i][j] or cmap[i][j] != word[pos]: return False find = False visited[i][j] = True for (ii, jj) in [(-1, 0), (1, 0), (0, -1), (0, 1)]: if dfs(cmap, word, pos + 1, i + ii, j + jj, visited): find = True break visited[i][j] = False # 易错点 return find
--- ### 257. 对称树判断 【Question】 判断一棵二叉树,是否是(左右)对称树: 对称例子: ``` For example, this binary tree is symmetric: 1 / \ 2 2 / \ / \ 3 4 4 3 ``` 不对称例子: ``` 1 / \ 2 2 \ \ 3 3 ```【Answer】
O(n)肯定能解决问题,但本题结合二分能优化时间复杂度 如果a[mid] < a[mid + 1]说明峰值后半段,否则在前半段 核心代码 ``` python def find_peak(a): left, right = 0, len(a) - 1 while left < right: mid = left + (right - left) / 2 if a[mid] < a[mid + 1]: left = mid + 1 else; right = mid return right--- ### 258. 给定链表,将其中的某一部分翻转 【Question】 给定链表,将其中的某一部分翻转, 要求空间复杂度为O(1);【Answer】
```java /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ public class Solution { public boolean isSymmetric(TreeNode root) { if (root == null) return true; return solve (root.left, root.right); } public boolean solve(TreeNode t1, TreeNode t2) { if (t1 == null && t2 == null) return true; if (t1 == null && t2 != null || t1 != null && t2 == null || t1.val != t2.val) return false; return solve(t1.left, t2.right) && solve(t1.right, t2.left); } } ```--- ### 259. 带TTL的N-kv cache 【Question】 "实现一个带过期的KV cache, 所有KV过期间隔相同, 满足如下性质: 1. 最多存储n对KV; 2. 如果大于n个, 则随意剔除一个已经过期的KV; 3. 如果没有过期的KV, 则将最老的那一对KV给剔除; 4. 查询时如果已经过期, 则返回空; 5. 所有操作复杂度都为O(1)"【Answer】
指针a与d指向要翻转部分的第一个节点,指针b指向a的上一个节点; 指针c指向a,并将a指向下一个节点,再将c插入到b的后面,重复执行该操作直到a走出要翻转的区间; 最后将a接在d后面,完成翻转。--- ### 260. 二叉树的镜像 【Question】 实现一个函数,完成输入一个二叉树,输出该二叉树的镜像。 二叉树结点的定义如下: ``` struct BinaryTreeNode { int data; BinaryTreeNode *Left; BinaryTreeNode *Right; }; ```【Answer】
用一个map来维护K->V索引; 用一个双向链表list来维护K, 保证越靠前的K时间越早; 用一个map来维护K->list.node, 该map用于删除时使用 剔除时, 从list取出最老的K, 从map中剔除即可; 最老的那个, 肯定最先过期; 如果都没有过期, 也只能剔除最老的那个; 因此在解决剔除与过期时, 完全同上; 为了解决过期返回空, 有比较简单的办法是在每次查询前先过期一次;--- ### 261. 输出二叉树左视角能看到的节点 【Question】 给定一颗二叉树: ``` 1 2 3 4 5 6 7 8 ``` 从左边看,输出能看到的 1,2,4,8 这四个节点,顺序无所谓。【Answer】
思路:先序遍历树的每个结点,若遍历到的结点有子结点,则交换它的两个子结点。 有两种实现方法: 1.递归实现: ``` void MirroRecursively(BinaryTreeNode *pNode) { if(NULL == pNode) return; if(NULL == pNode->Left && NULL == pNode->Right) return; BinaryTreeNode *pTemp = pNode->Left; pNode->Left = pNode->Right; pNode->Right = pTemp; if(pNode->Left) MirroRecursively(pNode->Left); if(pNode->Right) MirroRecursively(pNode->Right); } ``` 2.非递归实现,即使用循环实现: ``` void MirrorNonRecurively(BinaryTreeNode *pNode) { if(NULL == pNode) return; stack stackTreeNode; stackTreeNode.push(pNode); while(stackTreeNode.size()) { BinaryTreeNode *pNode = stackTreeNode.top(); stackTreeNode.pop(); if(NULL != pNode->Left || NULL != pNode->Right) { BinaryTreeNode *pTemp = pNode->Left; pNode->Left = pNode->Right; pNode->Right = pTemp; } if(NULL != pNode->Left) stackTreeNode.push(pNode->Left); if(NULL != pNode->Right) stackTreeNode.push(pNode->Right); } } ```--- ### 262. 日志提取 【Question】 你有一份非常大的日志文件; 日志的格式为: timestamp: content; 如 2017-01-01 20:00:00: hello hello hello; 需要你提取日志中指定时间内的内容; 如提取2017-01-01 20:00:00 ~ 2017-01-01 22:30:00的日志数据【Answer】
递归或者非递归都可以--- ### 263. 最多有两个不同字符的最长子串 【Question】 给定字符串s,返回最多包含两个不同字符的最长子串长度。 例如:s = 'abbcceefffffg' 最长子串为'eefffff'长度为5【Answer】
由于时间是递增的, 可以先二分到指定时间, 然后再进行读取;--- ### 264. 生成n阶螺旋数组 【Question】 本题是http://marvel.byted.org/#/question/detail/?id=816&nocontri=true的反向问题。 ``` python input = 1 output = [[1]] input = 2 output = [[1, 2], [4, 3]] input = 3 output = [[ 1, 2, 3], [ 8, 9, 4], [ 7, 6, 5]] ```【Answer】
本题采用两个指针left、right指针指向子串的起始和结束位置。 right不断前行,对left和right内的字符存入dict并计数,当dict的keys()超过2个时,向右移动left。 如此往复,不断更新最长子串长度。 ``` python def max_sub_len(s): max_len, left = 0, 0 count = collections.Counter() for right in xrange(len(s)): count[s[right]] += 1 while len(count) > 2: count[s[left]] -= 1 if count[s[left]] == 0: count.pop(s[left]) left += 1 max_len = max(max_len, right - left + 1) return max_len--- ### 265. 给定长度为n的整形数组,给定小一点的正数k,找到数组中出现次数大于 n/k 的数字 【Question】 举几个例子: 1. 假设 n=100,k=2,那么需要找到数组中出现次数大于 50 的数。 1. 假设 n=100,k=10,那么需要找到数组中出现次数大于 10 的数。【Answer】
``` python def gen_matrix(n): if n <= 0: return None matrix = [[0] * n for _ in xrange(n)] val = 1 start_row, start_col, end_row, end_col = 0, 0, n - 1, n - 1 while val <= n * n: for k in xrange(start_col, end_col + 1): matrix[start_row][k] = val val += 1 start_row += 1 for k in xrange(start_row, end_row + 1): matrix[k][end_col] = val val += 1 end_col -= 1 for k in xrange(end_col, start_col - 1, -1): matrix[end_row][k] = val val += 1 end_row -= 1 for k in xrange(end_row, start_row - 1, -1): matrix[k][start_col] = val val += 1 start_col += 1 return matrix ```--- ### 266. 平均延迟最大的调用链 【Question】【Answer】
1. 方案1:使用hashmap,遍历一次数组,将数组的数字当做key放入hashmap中,并将出现的次数作为value。之后再遍历hashmap将次数大于 n/k 的数打印出来即可 2. 方案2:假设要求空间复杂度为O(K),时间复杂度尽量低该怎么做? - 可以给一点提示:换一个思路,每次都从数组中删除K个互不相同的数,那么删除 n/k 次后,还在数组中的数的出现次数,应该至少都大于 n/k - 最终答案:申请一个K空间的hashmap,按照方案1的思路遍历数组并插入hashmap,每当hashmap有K个值时,就将hashmap里的value减1,如果为0,就从hashmap中删除该key。当数组遍历完,hashmap中剩余的key/value对,就基本是我们要找的数(还需要再次遍历hashmap检查一下次数)在微服务的架构下,公司内部会有非常多的独立服务。
服务之间可以相互调用,往往大型应用调用链条很多也很长,我们需要找出耗时最大的链条进行优化。(假设服务同时调用其依赖的下游服务)
例如:
A服务依赖B服务,平均调用延迟100ms,记为(A, B, 100)
其他依赖和延迟如下:
(A, C, 200)
(A, F, 100)
(B, D, 100)
(D, E, 50)
(C, G, 300)
那么服务A有三条调用链:A-B-D-E,A-C-G,A-F,平均延迟250,500,100
延迟最大的调用链是A-C-G,延迟为500ms
输入:
[(A, B, 100), (A, C, 200), (A, F, 100), (B, D, 100), (D, E, 50), (C, G, 300)]
输出:
500
Follow up:
1. 能否输出延迟最大的调用链,如果存在多条,都输出
--- ### 267. 最大能连续观看剧集数 【Question】 小明常常用BT下载连载电视剧、综艺观看。 因为种子、网络等诸多因素,可能不是所有的剧集都能现在下来,且现在的顺序也不一定按照从第一集到第n集排列。 请问:已知小明已下载的某部电视剧的剧集列表,求小明最多能连续观看多少集? 例如:episodes = [10, 1, 3, 4, 7, 6, 20, 5, 13, 23, 14] 那么小明做多能连续看[3, 4, 5, 6, 7]共5集 希望时间复杂度O(N)【Answer】
可以采用搜索的思路解决,例如DFS
--- ### 268. 根据访问日志统计出头条每日最火的n篇文章 【Question】 每日的访问日志记录有文章id,简化起见,访问的文章id存在输入数组中,给定n,要求返回查看次数最多的文章id 例如:visit_log = [ 10001, 1002, 10001, 20032, 302, 302] 如果n = 2,则根据上面的访问日志,不难看出[10001、302]是最火的2篇文章【Answer】
我们看一般的case寻找连续剧集,其实就看当前剧集i的前一集i-1下载了没,或者后一集i+1下载了没 也就是往前查查、往后查查,为了方便查,我们可以用hash,例如python里的set。 知道这个原理后,我们可以比较容易的写出代码。 ``` python def long_episode_count(episodes): remains = set(episodes) long_count = 0 for i in episodes: if i not in remains: continue remains.remove(i) while pre in remains: remains.remove(pre) pre -= 1 while next in remains: remains.remove(next) next += 1 long_count = max(long_count, next - pre - 1) return long_count ```--- ### 269. 实现字典树 【Question】 字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。 它有3个基本性质:根节点不包含字符,除根节点外每一个节点都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。 请实现字典树Class,并完成单词插入和查找函数。【Answer】
首先对访问日志的文章id做计数,然后根据计数做排序得到top n。 在排序方法选择方面,top n适合用堆来实现。 ``` python def top_article_id(visit_log, n): article_cnt = collections.Counter() for article_id in visit_log: article_cnt[article_id] += 1 heap = article_cnt.keys() def _heap_adjust(heap, parent, heap_size, article_cnt): while parent < heap_size: left, right = parent * 2 + 1, parent * 2 + 2 swap_pos = parent if left < heap_size and article_cnt[heap[left]] < article_cnt[heap[parent]]: swap_pos = left if right < heap_size and article_cnt[heap[right]] < article_cnt[heap[swap_pos]]: swap_pos = right if swap_pos != parent: heap[parent], heap[swap_pos] = heap[swap_pos], heap[parent] parent = swap_pos else: break for i in range(int(math.ceil(n / 2) - 1), -1, -1): heap_adjust(heap, i, n, article_cnt) for i in range(n, len(heap)): if article_cnt[heap[0]] < article_cnt[heap[i]]: heap[0] = heap[i] heap_adjust(heap, 0, n, article_cnt) return heap[:n]--- ### 270. 36进制正整数加法 【Question】 36进制由0-9,a-z,共36个字符表示,最小为'0' '0'~'9'对应十进制的0~9,'a'~'z'对应十进制的10~35 例如:'1b' 换算成10进制等于 1 * 36^1 + 11 * 36^0 = 36 + 11 = 47 要求按照加法规则计算出任意两个36进制正整数的和 如:按照加法规则,计算'1b' + '2x' = '48' 要求:**不允许把36进制数字整体转为10进制数字,计算出10进制累加结果再转回为36进制** 本题可任意改变进制,如加入大写字母变为62进制。【Answer】
参考实现代码: ``` python class TrieNode(object): def __init__(self): self.is_leaf = False self.children = [None] * 26 class Trie(object): def __init__(self): self.root = TrieNode() def insert(self, word): if not word: return cur_node = self.root for char in word: index = ord(char) - ord('a') if not cur_node.children[index]: cur_node.children[index] = TrieNode() cur_node = cur_node.children[index] cur_node.is_leaf = True def search(self, word): if not word: return False cur_node = self.root for char in word: index = ord(char) - ord('a') if not cur_node.children[index]: return False cur_node = cur_node.children[index] return cur_node.is_leaf ```--- ### 271. 数列(顺序为: 一堆负数 + 一堆 0 + 一堆正数),求最后一个出现的负数和第一个出现的正数 【Question】 数列(顺序为: 一堆负数 + 一堆 0 + 一堆正数),求最后一个出现的负数和第一个出现的正数的位置,从 1 开始计数。例如: -2,-1,0,0,0,0,4,5 最后一个负数出现的位置为:2,第一个正数出现的位置为7。【Answer】
``` python def add(num1, num2): ''' >>> add('', '') '' >>> add('', '1') '1' >>> add('ab', '') 'ab' >>> add('1b', '2x') '49' >>> add('0', '2x') '2x' >>> add('zz', '1') '100' ''' def _get_value(num): if ord('0') <= ord(num) <= ord('9'): return ord(num) - ord('0') return ord(num) - ord('a') + 10 def _to_chr(num): if 0 <= num <= 9: return str(num) return chr(ord('a') + num - 10) def _add(n1, n2, carry): result = _get_value(n1) + _get_value(n2) + carry carry = 1 if result >= 36 else 0 result %= 36 return _to_chr(result), carry len1, len2 = len(num1), len(num2) if len1 > len2: # 取巧:把两个数字长度对齐 num2 = '0' * (len1 - len2) + num2 elif len2 > len1: num1 = '0' * (len2 - len1) + num1 res = [] carry = 0 for i in xrange(max(len1, len2) - 1, -1, -1): tmp, carry = _add(num1[i], num2[i], carry) res.append(tmp) if carry: # 易错点:很容易遗漏 res.append('1') return ''.join(res[::-1]) # 易错点:需要翻转 ```--- ### 272. 青蛙跳石子 【Question】 在长度为m的地面格子上,每个格子里面有一些石子;有一只青蛙, 从格子开始处起跳,每次可以跳3到5个格子,求青蛙跳出格子最少需要踩几个石子;【Answer】
1. 二分查找 0 的位置; 2. 注意边界位置 0 的处理,在二分条件上需要做一些处理,如果只是找到 0 的位置然后遍历找到第一个和最后一个 0 的话, 复杂度为恶化; 3. 整体复杂度为 O(lg(n));--- ### 273. 给定一个字符串如下,请统计字符串中出现最多的字母和次数 【Question】 ```javascript function findMaxDuplicateChar(str) { let maxChar = '', maxValue = 1; // 补全代码..... return { maxChar, maxValue }; } const str = 'this is a fe test at toutiao on September'; findMaxDuplicateChar(str) // output: { maxChar:"t", maxValue:7 } ```【Answer】
简单DP dp[i]表示调到第i的位置最少需要踩几个石子; 于是dp[i]可由dp[i-3], dp[i-4], dp[i-5]得来;--- ### 274. 数组排序 【Question】【Answer】
```javascript function findMaxDuplicateChar(str) { if (str.length === 1) { return str; } const charObj = {}; let maxChar = '', maxValue = 1; for(let i = 0; i < str.length; i++) { if (str[i].trim() !== '') { if (!charObj[str.charAt(i)]) { charObj[str.charAt(i)] = 1; } else { charObj[str.charAt(i)] += 1; } } } for (const k in charObj) { if (charObj[k] >= maxValue) { maxChar = k; maxValue = charObj[k]; } } return { maxChar, maxValue }; } const str = 'this is a fe test at toutiao on September'; findMaxDuplicateChar(str) // output: { maxChar:"t", maxValue:7 } ```输入数组nums,要求输出升序排序后的结果。已知数组元素为非负整数,且当数组长度为n时,所有元素的值都小于n;
例:
[3, 2, 1, 3, 5, 0] -> [0, 1, 2, 3, 3, 5]
[0] -> [0]
--- ### 275. 二叉树转换成双向链表 【Question】 转换结果满足下面条件: 1. 不要新建一个链表空间,要原地做;直接使用 left 和 right 指针 2. 转换后的双向链表节点顺序要与中序遍历的结果一致 3. 二叉树最左边的节点作为双向链表的头节点 例如: 二叉树: 1 2 3 4 5 6 7 双向链表: 4 <-> 2 <-> 5 <-> 1 <-> 6 <-> 3 <-> 7【Answer】
O(n2)的排序算法:冒泡排序,插入排序,选择排序 等;
O(nlog(n))的排序算法:归并排序,快排 等;
O(N)的排序算法:空间换时间,利用计数实现,需要O(n)空间复杂度;
--- ### 276. 翻转单词 【Question】 给定一个字符串,逐个翻转字符串中的每个单词。【Answer】
二叉树遍历;递归;链表操作 原理上看,对根节点、根的左子树、根的右子树分别作处理: 1. 比如根节点1, 对于左子树,找到它的最右节点 5, 把 5 和 1 连接起来; 2. 对于 1 的右子树,找到它的最左节点 6,把 6 和 1 连接起来。 实现上,假设已经把左子树变做双向链表了,让指针一直向右走,就能找到最右节点和根连接; 右子树同理。 返回结果的时候,找到双向链表的最左节点就可以了。 ``` private Node convertToDoublyLinkedList(Node root) { if (root.getLeft() != null) { Node left = convertToDoublyLinkedList(root.getLeft()); while (left.getRight() != null) { left = left.getRight(); } left.setRight(root); root.setLeft(left); } if (root.getRight() != null) { Node right = convertToDoublyLinkedList(root.getRight()); while (right.getLeft() != null) { right = right.getLeft(); } right.setLeft(root); root.setRight(right); } return root; } ```--- ### 277. 两个数的和相加 【Question】【Answer】
根据情况,选择栈和队列即可 栈必须,队列可选给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
--- ### 278. 二叉树所有根到叶子路径组成的数字之和 【Question】 二叉树每个节点的value范围是1-9 例如: ``` 1 2 3 4 5 ``` 从根到叶子共3条:1->2->4, 1->2->5, 1->3 构成的数字为124,125,13,求和124 + 125 + 13 = 262即为所求【Answer】
--- ### 279. 三门问题/蒙蒂大厅难题 【Question】 你面前有三扇门,每个门后都有一个奖品,其中一个是一辆车,另外两个是山羊。 你首先挑选一扇门,姑且称之为A,其他两个门分别是B和C。 在打开你选择的门前,我先从B和C中选择一个没有车的门打开。 给你一个选择,你是坚持最开始的选择还是换到剩下未打开的门上? 如果我打开门的策略从随机选择,变成总是优先选择门B,只有在迫不得已的时候选择C。 问结果有变化么?【Answer】
``` python def tree_path_sum(root, val): if not root: return 0 val = val * 10 + root.val if not root.left and not root.right: return val return tree_path_sum(root.left, val) + tree_path_sum(root.right, val) ```--- ### 280. 海盗分金问题 【Question】 5个海盗要分100块金币,分配的协议是:按顺序一个一个来,轮到当前的海盗,他提出一个分配方案,如果包括他在内至少有50%的人同意,那么方案通过,否则这个海盗就会被喂鲨鱼,下一个海盗继续提出分配方案。 假设海盗都是纯理性而且冷血的,他们的第一原则是生存,第二原则就是拿到尽可能多的金子,第三原则是如果给的金币一样,他们倾向于选择有更少的海盗的分配方案。【Answer】
使用贝叶斯公式求解,p(a|b)*p(b) = p(a)*p(b|a) 三种假设,即假设车在门A,门B,门C后面。 D表示打开了门B但是车不在门后。 假设A:先验概率=1/3,似然度=1/2,后验概率=(1/3)*(1/2)/(1/2)=1/3 假设B:先验概率=1/3,似然度=0, 后验概率=0 假设C:先验概率=1/3,似然度=1,后验概率=2/3 变化之后概率相同。--- ### 281. 牛过河 【Question】【Answer】
思路: 5个人太多了,首先把问题简化。从1个人到5个人逐个分析。 * 1个人,显然自己全拿 * 2个人。1,2。2号海盗显然可以全部拿走,因为他自己的一票保证了50%。 * 3个人。1,2,3。首先,如果3号的方案没通过,那么1号将什么都得不到,而2号必定要除3号而后快。因此3号必须征得1号的支持,但又不能完全不给1号任何金币(第三原则)。因此分配方案是1 2 3 = 1 0 99。这样3号的方案能够得到1和3的支持。 * 4个人。1,2,3,4。首先,如果4号的方案没通过,那么2号将什么都得不到。因此4号只需要分配1 2 3 4 = 0 1 0 99 * 5个人。1,2,3,4,5。5号清楚的知道一旦他的方案不通过,1和3将什么都得不到,因而他只需要分配1 2 3 4 5 = 1 0 1 0 98,这样即足以保证。有一堆牛要过河,河的长度是l,河中间有n个石头,牛只能踩着石头过河,问去掉m个石头后(去掉这m个石头的方式是随机的)的每种情况牛能走的石头间距最小值中,最大的那一个是多少
--- ### 282. 求二叉树是否存在和值为N的路径 【Question】 从二叉树的根到叶子节点称为一条路径 路径上每个节点的value之和为路径和值 本题要求所有的路径中是否存在一条和值为N的。 follow-up:可以要求返回所有符合条件的路径【Answer】
二分最大值最小化问题
--- ### 283. special trim 【Question】 对输入的字符串,去除其中的字符'b'以及连续出现的'a'和'c' 例如: 'aacbd' -> 'ad' 'aabcd' -> 'ad' 'aaabbccc' -> '' 不允许使用类似string.replace函数。要求时间、空间复杂度尽量优化【Answer】
要求自己给出二叉树数类定义 本题很容易用递归解决,基本思路如下: ``` def path_exists(root, n): if not root: return False if not root.left and not root.right and root.value == n: return True return path_exists(root.left, n - root.value) or path_exists(root.right, n - root.value) ```--- ### 284. 约瑟夫问题 【Question】 假设有n个人,标号为1-n。 从第一个人开始计数,到第k个人则出列,随后从第k+1个人重新计数,到第k再出列。 直至剩下最后一个人。问最后剩下的人的编号?【Answer】
本题不好处理的是aaabccc,即:c和a相遇就要都去掉。 比较好的思路是用栈的思路解决。 ``` python def special_trim(s): if not s: return s res = [] for c in s: if c == 'b': continue if c == 'c' and res and res[-1] == 'a': res.pop() continue res.append(c) return ''.join(res) ```--- ### 285. 二叉搜索树中的第K小的元素 【Question】 给定二叉搜索树,求所有元素中第k小的。k<=节点总数【Answer】
考察链表使用。最简单直接的方法是使用循环链表。 具体思路是: 1. 构建循环链表,初始化数据; 2. 每到第k-1个结点,便p->next = p->next->next。 3. 循环结束条件为p = p->next,即只有一个结点,该结点所对应的值即为最后剩下的人。--- ### 286. 序列化和反序列化二叉树 【Question】 将一棵二叉树序列化为字符串,并能字符串反序列为一棵树。【Answer】
结合BST的性质,用递归很好解决,按照中序遍历即可 核心代码: ``` python def find_k_small(root, k): def _find(root): if not root: return -1 val = _find(root) if not k: return val k -= 1 if not k: return root.val return _find(root) _find(root) ```--- ### 287. 带过期和最大对象数限制的LRU-CACHE 【Question】 设计一个对象cache, 他支持下列两个基本操作: set(id, object), 根据id设置对象; get(id): 根据id得到一个对象; 同时它有下面几个性质: 1: x秒自动过期, 如果cache内的对象, x秒内没有被get或者set过, 则会自动过期; 2: 对象数限制, 该cache可以设置一个n, 表示cache最多能存储的对象数; 3: LRU置换, 当进行set操作时, 如果此时cache内对象数已经到达了n个, 则cache自动将最久未被使用过的那个对象剔除, 腾出空间放置新对象; 请你设计这样一个cache;【Answer】
本题解法应该有不少,大体思路是按照某种遍历的顺序记录下每个节点,叶子节点的空指针可以用特殊字符表示 例如用先根遍历解决: ``` python def serialize(root): def pre_order(node): if node: vals.append(str(node.val)) pre_order(node.left) pre_order(node.right) else: vals.append('#') vals = [] pre_order(root) return ' '.join(vals) def deserialize(data): def pre_order(): val = next(vals) if val == '#': return None node = TreeNode(int(val)) node.left = pre_order() node.right = pre_order() return node vals = iter(data.split()) return pre_order()--- ### 288. n sum 【Question】【Answer】
通过组合一些基本的数据结构, 来实现一些更高级的性质; 内部维护一个链表, list, 其元素为一个三元组(ID, timestamp, obj), 分别为对象ID, 上次被访问时间, 和对象内容; 在维护该list时, 需要保持一个性质, 越靠后的元素越新, 既timestamp越大; 内部再维护一个map, 该map表示一个ID到list节点的索引, 格式为map(ID, node); 对于get(id)操作: 1: 先在map中查找ID对应的list node; 2: 将node从list中取出, 即list.Remove(node); 3: 检查node.timestamp, 如果过期, 则返回null, 表示无数据, 并将ID从map中删除; 4: 如果未过期, 设置node.timestamp = now(), 并将node添加到list尾部, 即list.Append(node); 5: 返回node.obj; 对于set(id, obj)操作: 1: 同get(id)的1~3步操作, 删除对应的ID; 2: 如果此时空间满了, 既对象数为n, 则将list中表头的那个元素删除; 3: 更新list和map: node = new(ID, now(), obj), list.Append(node), map[ID] = node;输入一维数组array和n,找出和值为sum的n个元素即可,不用找出所有组合。
array = [2, 3, 1, 10, 4, 30] n = 2, sum = 31 result = find(array, n, sum) // result = [1, 30]
--- ### 289. 多叉树广度优先遍历查找 【Question】【Answer】
基础解法供参考
function find(arr, n, sum, shouldSort = true) { let sorted = arr; if (shouldSort) { sorted = arr.sort(); } const length = sorted.length; if (n === 2) { let front = 0; let back = length - 1; while(front < back) { const value = sorted[front] + sorted[back]; if (value === sum) { return [sorted[front], sorted[back]]; } else if (value > sum) { back -= 1; } else { front += 1; } } return null; } for(let i = 0; i < length; i += 1) { const val = sorted[i]; const result = find(sorted.slice([i + 1]), n - 1, sum - val, false); if (!result) { return null; } else { return [val, ...result]; } } }
要求:
- 自定义多叉树节点node结构(只需要定义节点结构即可,无需构建树)
- 按照广度优先查找符合要求的节点(没有符合要求的节点返回null),比如查找电话号码为 phone的用户信息,调用如下:
let node = wideTraversal(node,(e)=>e.phone===phone)
【Answer】
节点定义:
// 定义树节点 Node { ... // 属性 children:[], // 子节点 }
实现:
wideTraversal(node: Node, predict): Node | null { if (!node) return null let queue: Array
= []; queue.push(node) while (queue.length) { let cur = queue.shift() if (!cur) continue if (predict(cur)) return cur if (cur.children&& cur.children.length) { queue.push(...cur.children) } } return null }</code></pre> </pre> </details> --- ### 290. 给定无向图和它一个顶点,求无向图中到该顶点距离为K的所有点 【Question】 给定无向图和它一个顶点,求无向图中到该顶点距离为K的所有点,各个相邻顶点间距离为1
--- ### 291. 输出所有根节点到叶子节点的路径 【Question】 比如: 1 2 3 4 5 6 7 8 输出: 1,2,4 1,2,5 1,3,6 1,3,7,8【Answer】
方案:对图进行K步的BFS, 注意处理被重复走过的点;--- ### 292. 找出旋转有序数组中的最小值 【Question】 假设原数组为1,2,3,4,5那4,5,1,2,3就是旋转有序的数组 注:数组无重复元素【Answer】
二叉树遍历--- ### 293. 搜索二维矩阵 【Question】 给定二维m * n矩阵matrix,满足一定特性: 1. 每行从左到右递增 2. 每列从上到下递增 给定目标元素num,判断num是否在矩阵中存在 例如: ``` python matrix = [ [1, 3, 5, 10], [2, 4, 6, 11], [7, 9, 12, 20], ] ``` num = 4存在;num = 13不存在【Answer】
暴力可以O(n),但没用到旋转有序的特征。 本题可以用二分的思想降低时间复杂度 定义左右两个指针left、right指向头尾元素 如果a[left] < a[right]则没有移位,直接输出a[left]即可,反之二分搜索。 如果a[left] > a[mid] 则要搜索右半段,因为a[left]也大于a[right];反之搜索左半段。 核心代码 ``` python def find_min(arr): left, right = 0, len(arr) - 1 if arr[left] < arr[right]: return arr[left] while left != right - 1: mid = left + (right - left) / 2 if arr[left] < arr[mid]: left = mid else: right = mid return min(arr[left], arr[right])--- ### 294. 扑克牌的堆栈、队列操作 【Question】 我手中有一堆扑克牌, 但是观众不知道它的顺序。 * 第一步, 我从牌顶拿出一张牌, 放到桌子上。 * 第二步, 我从牌顶再拿一张牌, 放在手上牌的底部。 * 第三步, 重复第一步的操作, 直到我手中所有的牌都放到了桌子上。 最后, 观众可以看到桌子上牌的顺序是:13\12\11\10\9\8\7\6\5\4\3\2\1 请问, 我刚开始拿在手里的牌的顺序是什么?【Answer】
结合数组定义,观察例子,有两个特殊位置很特殊:左下角和右上角。 左下角的7往上所有的数变小,往右所有的数变大。 那么我们就可以将目标数字num和左下角比较,比目标小就往右搜,比目标大就往上搜。 如此往复可以判断num是否在matrix中。 ``` python def search(matrix, num): if not matrix: return False rows, cols = len(matrix), len(matrix[0]) if num < matrix[0][0] or num > matrix[rows - 1][cols - 1]: return False i, j = rows - 1, 0 # 左下角 while i >= 0 and j < cols: if matrix[i][j] < num: j += 1 elif matrix[i][j] > num: i -= 1 else: return True return False ```--- ### 295. 用js实现一个binarySearch二分查找 【Question】 定一个一个binarySearch的函数,传参能支持四个参数,分别是: >1. arr: 一个数组, >1. key: 一个需要查找的目标值, >1. low: 左边界 >1. high: 右边界 >1. 如果能知道则访问素组的位置,否则返回-1 ```javascript function binarySearch(){ // 补全代码 } var arr = [86,1,3,2,4,5,6,7,8,9,10,11,23,44]; var result = binary_search2(arr, 5, 0, 6); console.log(result); // 4 ```【Answer】
解法一: 这道题候选人容易出现折半的思路, 其实考虑的复杂了。 本质是将一个队列和栈做了两个操作 1. 出队、入栈 2. 出队、入队(队尾) 因为是看得到结果, 看不到初始顺序, 那么这个操作就是一个逆操作。 1. 出栈、入队 2. 出队(队尾)、入队(队首) 答案: 输入: 1,2,3,4,5,6,7,8,9,10,11,12,13, 输出: 1,12,2,8,3,11,4,9,5,13,6,10,7, 代码如下 ``` C++ 再改改 int doTheWork(std::deque * pQ, std::stack * pS) { if(NULL == pQ or NULL == pS) return -1; while(pS->size() > 0) { int val = pS->top(); pS->pop(); if (pQ->size() > 0) { int tmp = pQ->back(); pQ->pop_back(); pQ->push_front(tmp); pQ->push_front(val); } else { pQ->push_front(val); } } return 0; } ``` 解法二: 对手上牌按照a,b,c...进行编码,直接按顺序操作,输出结果和桌上实际结果对应,即为原手上牌的顺序。--- ### 296. 通配符匹配 【Question】 给定字符串s和模式串p,实现函数match(s, p),判断模式串p是否能完全匹配s 模式串中有两个特殊字符'?'和'*' '?'匹配任意1个字符;'* '匹配任意r个字符,包括空 例如: ``` python match('a', 'a') = True match('aa', 'a') = False match('a', '?') = True match('aa', '*') = True match('abc', '?*') = True ```【Answer】
二分法查找,也称折半查找,是一种在有序数组中查找特定元素的搜索算法。查找过程可以分为以下步骤: >1. 首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。 >1. 如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。 >1. 如果某一步数组为空,则表示找不到目标元素。 ```javascript // 递归实现的js代码 function binary_search2(arr, key, low, high) { if(low > high) { return -1; } var mid = parseInt((high + low) / 2); if(arr[mid] == key) { return mid; } else if(arr[mid] > key) { high =mid -1; return binary_search2(arr, key, low, high); } else if(arr[mid] < key) { low = mid +1; return binary_search2(arr, key, low, high); } } var arr = [86,1,3,2,4,5,6,7,8,9,10,11,23,44]; var arrSorted = arr.sort(function (a,b) { return a-b; }) var result = binary_search2(arrSorted, 5, 0, 10); console.log(result); // 4 ```--- ### 297. 原子计数问题 【Question】 给出一个分子式,比如: HMg2(H2O(N3Ag)2)3N2 计算这个分子式中每个原子出现了多少次,输出一个 map,比如上面的分子式中: map[string]int {"H": 7, "Mg": 2,"Ag": 6, ...} 分子式的规则: 1. 都以大写字母开头,后面跟 0 个或者 1 个小写字母,比如 Mg, H 2. 单个原子后面跟 0 个或者 1 个数字表示它出现的次数,比如 Mg2 表示 Mg 出现 2 次,数字范围 [2-1000] 3. 分子式中可能有括号,括号后面可能跟 0 个或者 1 个数字表示整个括号内的原子出现的次数,比如 (N3Ag)2 表示 N出现 6 次,Ag 出现 2 次 4. 括号可以嵌套 输入是合法的【Answer】
本题可以用DP算法来解答。 用dp[i, j]表示串s, p这两个串分别到i和j位置它们是否匹配。那么我们得到递推关系: 如果p[j] != '* ', dp[i, j] = dp[i-1, j-1] and (s[i] == p[j] || p[j] == '?') 如果p[j] == '* ', 那么dp[i][j] = dp[i - 1][j] || dp[i][j - 1] ``` python def match(s, p): m, n = len(s), len(p) dp = [[False] * (n + 1) for _ in range(m + 1)] dp[0][0] = True for j in range(1, n + 1): dp[0][j] = dp[0][j - 1] and p[j - 1] == '*' for i in range(1, m + 1): for j in range(1, n + 1): if p[j - 1] in [s[i - 1], '?']: dp[i][j] = dp[i-1][j-1] elif p[j - 1] == '*': dp[i][j] = dp[i - 1][j] or dp[i][j - 1] return dp[m][n] ``` 另外,本题也可以用贪心算法--- ### 298. 安卓解锁密码数 【Question】 安卓系统采用9个点连线构成图案密码。 要求连接以下9个点中**至少4个点**构成一条路径,从而得到1个密码。 1 2 3 4 5 6 7 8 9 任意点间均可连线,都有如下附加限制: 1. 在一个密码路径里每个点只能用1次; 2. 如果2号点没有被连,则1不能直接连3号点,即:1->3路径是非法的。类似情况还有1、7;2、8;1、9等等; 3. 如果2点已经连过了,则1可以连到3,3也可以连到1,即:2->1->3路径是合法的。 4. 1和6是可以直接连线的,因为二者之间没有点。 本问题求所有的密码数,也就是路径数,包含4个点、5个点。。。9个点的所有路径数。【Answer】
1. 最简单的方法是递归,碰到 ( 就开始递归 2. 担心爆栈就把递归改成非递归 3. 可以用正则表达式来做,这里不展开了,如果候选人懂的话让他解释--- ### 299. 矩阵中的最长递增路径 【Question】 给定m * n矩阵matrix,可以从任意位置开始,向上、向下、向左、向右移动,但要求下一个位置上的元素要大于当前元素。 找出最长的递增路径长度。【Answer】
本题目如果数据结构算法比较熟悉,会很快想到DFS。 难点是路径不能直达问题如何解决? 用visit记录某个点是否已经在路径中 ``` python visit = [[0, 0 ,0], [0, 0, 0], [0, 0, 0]] ``` 判断两个点能否连同,等价于判断两个点是否存在中间点问题,如果不存在可以直接连,如果存在要判断中间点是否已经访问过了。 中间点坐标为:|i1 - i2| / 2, |j1 - j2| / 2 另外,本题考虑到对称性会大大降低运算量 1、3、7、9点对称 2、4、6、8点对称--- ### 300. 升序数组求平方数不同的个数 【Question】 给定一个升序数组1,元素有重复,对每个元素算一下平方后得到新的数组2,问数组2中不相同的元素共有多少个?给出算法和空间复杂度,要求尽量优化。 举例: 数组1 [-13,-10,-9,-6,-5,-1,3,4,6,7,10,11,15,21,42] 平方后得到 数组2 [169,100,81,36,25,1,9,16,36,49,100,121,225,441,1764] 其中不相同的元素个数为13个。【Answer】
本题很容易想到dfs,但问题是每个点开始递归搜索,重复计算很多,结合DP提升效率。 用dp[i][j]表示从(i,j)开始的最长递增路径长度,当递归调用时,如果dp[i][j]不为0,直接返回dp[i][j]。 ``` python def long_increase_path_len(matrix): if not matrix: return 0 res = 1 m, n = len(martix), len(matrix[0]) dp = [[0] * n for _ in range(m)] for i in range(m): for j in range(n): res = max(res, dfs(matrix, dp, i, j, m, n)) return res def dfs(matrix, dp, i, j, m, n): if dp[i][j]: return dp[i][j] tmp_max = 1 dirs = [[0, -1], [-1, 0], [0, 1], [1, 0]]: for ni, nj in dirs: ii, jj = i + ni, j + nj if ii < 0 or ii >= m or jj < 0 or jj >= n or matrix[ii][jj] <= matrix[i][j]: continue tmp_max = max(tmp_max, 1 + dfs(matrix, dp, ii, jj, m, n) dp[i][j] = tmp_max return dp[i][j] ```--- ### 301. URL反转 【Question】 给定形如 `www.toutiao.com` 的 URL,将其转换成 `com.toutiao.www` 的形式,要求必须原地操作【Answer】
常规解法,按题目思路,先平方算好,再将结果插入hashset,最后输出hashset大小。 优化1,平方没必要算,其实就是绝对值。 优化2,注意到数组2其实是以0分隔的一个降序和一个升序数组。反序遍历降序数组,正序遍历升序数组,即可合并成一个升序数组,合并时做一下排重,最后输出合并后数组的元素个数。--- ### 302. 判断单向链表是否有环 【Question】【Answer】
1. 原地全部翻转一遍; 2. 遍历遇到".",继续翻转该部分字符串; 该题目重点考察编码,需要保证代码简洁,要不不允许使用字符串库函数判断一个链表中是否有环
例如:A->B->C->D->B->C->D
D指向B形成环
要求:在空间复杂度O(1)的情况下,时间复杂度最小
--- ### 303. 给定两个链表,求它们交叉节点 【Question】 1. 已知两个链表, 从某个节点开始就交叉了 2. 已知这两个链表的头节点, 求出交叉的节点【Answer】
创建两个指针slow,fast,同时指向这个链表的头节点。
然后让两个指针开始循环移动
slow每次向下移动一个节点,fast每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环
--- ### 304. 判断一个IP是否是国内 【Question】 如何快速判断一个ip地址是否属于国内?已知db中有几十万个国内ip地址段【Answer】
1. 依次遍历两个链表分别得到链表的长度M和N 2. 然后让长的那个一个从头开始走|M-N|的步数; 3. 两个指针同时走, 直到碰头--- ### 305. 用户在线峰值 【Question】 已知一天内用户登录登出的日志(数据量较大),求这一天用户在线的最大峰值和持续时间段 - 日志包含字段(userid, login_time, logout_time) - 登录登出时间精确到秒【Answer】
1. 将ip地址通过位移运算转成int 2. 对ip地址进行排序(可以考察任意一种排序算法) 3. 二分查找--- ### 306. 股票买卖问题 【Question】 给定一个数组代表股票每天的价格,请问只能买卖一次的情况下,最大化利润是多少?日期不重叠的情况下,可以买卖多次呢? 输入: {100, 80, 120, 130, 70, 60, 100, 125} 只能买一次:65(60 买进,125 卖出) 可以买卖多次: 115(80买进,130卖出;60 买进,126卖出) 提示:不用输出买卖的序列,只需要得到最大利润【Answer】
可以将这一天看成0-24*3600的离散的时间点,构造一个dict 每个用户在login_time对应value+1,logout_time对应value-1 得到用户在线数量的变化趋势,然后再遍历此dict求和 难点: - 想不到先求变化趋势 - 峰值时间段可能存在多个 ``` def get_max(logs): log_count = {} for log in logs: login_time = log['login_time'] logout_time = log['logout_time'] log_count[login_time] = log_count.setdefault(login_time, 0) + 1 log_count[logout_time] = log_count.setdefault(logout_time, 0) - 1 max, current_users, start, end, is_max, timeline = (0, 0, 0, 0, False, []) keys = log_count.keys() keys.sort(lambda a, b: a - b) for time_node in keys: current_users = current_users + log_count[time_node] if current_users > max: max = current_users start = time_node is_max = True elif current_users < max: if is_max: end = time_node is_max = False else: if is_max: end = time_node else: timeline.append((start, end)) start = time_node is_max = True timeline.append((start, end)) return max, timeline ```--- ### 307. 输出给定数字下一个比它大的数字 【Question】 比如数字:1234, 输出 1243 比如 1243,则输出 1324【Answer】
1. 对于只能买一次的情况: ``` public static int maximumProfit(int[] stockPrices) { int profit = 0; int minimumPrice = Integer.MAX_VALUE; /* * 对于给定的一天,最大利润等于 - * max(昨天为止的最大利润, 当天的价格 - 之前的最小价格) */ for(int i = 0; i < stockPrices.length; i++) { profit = Math.max(profit, stockPrices[i] - minimumPrice); minimumPrice = Math.min(stockPrices[i], minimumPrice); } return profit; } ``` 2. 对于可以买卖多次的情况,累积递增序列的差就可以了: ``` public static int maximumProfit2(int[] stockPrices) { int totalProfit = 0; for(int i=1; i 0){ totalProfit += currentProfit; } } return totalProfit; } ```--- ### 308. Path Sum 【Question】 给定一个二叉树和一个数字n,判断二叉树中是否有一个路径上的数字之和等于给定的数字n For example: Given the below binary tree and sum = 22, 5 / \ 4 8 / / \ 11 13 4 / \ \ 7 2 1 return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.【Answer】
用 1243 为例: 1. 从右向左扫描,找到第一个不是升序的数字,比如 2 2. 在 2 的右边,找到比它大的最小的数,是 3 3. 交换 2和 3,得到 1342 4. 把现在 3 右边的所有数字从大到小排序,得到 1324 (如果是排序则是 O(nlogn), 其实逆序就行了)--- ### 309. 单链表每隔k个元素做一次反转 【Question】 给定一个链表,每隔k个元素做一次反转 Example: Inputs: 1->2->3->4->5->6->7->8->NULL and k = 3 Output: 3->2->1->6->5->4->8->7->NULL. Inputs: 1->2->3->4->5->6->7->8->NULL and k = 5 Output: 5->4->3->2->1->8->7->6->NULL.【Answer】
public class Solution { public boolean hasPathSum(TreeNode root, int sum) { if(root == null) return false; int left = sum - root.val; if(root.left == null && root.right == null && left == 0) { return true; } return hasPathSum(root.left, left) || hasPathSum(root.right, left); } } 可以进一步问,输出所有和等于给定数字n的path--- ### 310. 用两个栈实现一个队列 【Question】 用两个堆栈模拟队列的功能,实现push,pop,count三个方法【Answer】
Node* rollback(Node *&head, int k) { Node *pre = NULL; Node *next = NULL; Node *curr = head; int count = 0; while (curr != NULL&&countnext; curr->next = pre; pre = curr; curr = next; count++; } if (curr != NULL) { head->next = rollback(next, k); } return pre; }--- ### 311. 赛马求最快N匹 【Question】【Answer】
简单的做法:栈s1和s2,始终维护s1作为存储空间,以s2作为临时缓冲区,push直接进s1,pop时s1导入s2,栈顶出栈,导回s1 优化做法:入队时,将元素压入s1,出队时,判断s2是否为空,如不为空,则直接弹出顶元素;如为空,则将s1的元素逐个“倒入”s2,把最后一个元素弹出并出队条件:
1.64匹马
2.8个赛道
3.每次比赛只能知道比赛结果名次,不能知道具体时间
求:
用最少的比赛次数,找出最快的4匹
--- ### 312. LRU cache 【Question】 实现一个LRU过期算法的KV cache, 所有KV过期间隔相同, 满足如下性质: 1. 最多存储n对KV; 2. 如果大于n个, 则随意剔除一个已经过期的KV; 3. 如果没有过期的KV, 则按照LRU的规则剔除一个KV; 4. 查询时如果已经过期, 则返回空;【Answer】
1.每次比赛至少能淘汰4匹(树形淘汰算法),因此淘汰60匹,至少需要15次比赛,回答15,是最差答案
2.如果能回答出12次的(经过加速之后的简单排序),为次优解:
1)先每8个一组,一共8组比8次
2)从第9次开始
*1.先取每组第一名,每次比赛,找出最快1匹,取出
*2.将最快这匹对应的组里次快的加入比赛,一共4次,找出最快4匹
3.如果能答出特定情况下10次,最差11次,为最优解(剪枝算法):
1)先每8个一组,一共8组比8次
2) 第9次,先取每组第一名,进行比赛,找出前四名
3) 第10次,将前4名对应的组中的第2名加入比赛,一共8匹,比赛一次,如果对应的前四名没发生变化,说明前4名就是最快4名
4)第11次
*1.假设有一个组里的第2名,进入前四名:
①有一组的第1名被挤出前4名,该组所有的候选马无法进入前4名
②另外两组第2名之后无法进入前4名
因此,4(第2名在第10次进入前4名的组对应的前4名)+2(还在前4名的另外两个第1名)=6匹马进行比赛,决出前4名,即为最终答案
*2假设有两个组的第2名,进入前四名:
①则另外两组都无法进入前4名,而还在前4名的两组的前4名4+4=8,跑一次,最终能得到最快4名
--- ### 313. 求数组的最大区间和 【Question】 # 输出一个 int 型数组的最大连续子数组(所有元素加和最大)各个元素之和 # 保证数组中至少有一个正数 例: 输入:{1,2,5,-7,8,-10} 输出:9 (子数组为: {1,2,5,-7,8})【Answer】
用一个map来维护K->V索引; 用一个双向链表list来维护K, 保证越靠前的K时间越早; 用一个map来维护K->list.node, 该map用于删除时使用 剔除时, 从list取出最老的K, 从map中剔除即可; 最老的那个, 肯定最先过期; 如果都没有过期, 也只能剔除最老的那个; 因此在解决剔除与过期时, 完全同上; 为了解决过期返回空, 有比较简单的办法是在每次查询前先过期一次; 解法大致同上; 为了实现LRU, 可在每次get后, 将该K从cache中删除并重新插入一遍;--- ### 314. 最长无重复子串 【Question】【Answer】
复杂度 `O(n)` 的算法 ``` int _tmain(int A[], _TCHAR* argv[]) { int array_length = sizeof(A) / sizeof(A[0]); int sum = 0; int thisSum = 0; for (int i = 0; i < array_length; i++) { thisSum += A[i]; if (thisSum > sum) { sum = thisSum; } else if (thisSum < 0) { thisSum = 0; } } printf("%d",sum); return 0; } ```无重复子串指:子串中每个字符都不相同 例如:s = 'aaabcdddd' 最长的无重复子串为'abcd'长度为4
--- ### 315. 服务循环依赖检测 【Question】【Answer】
用两个指针left、right指向子串的起止位置 通过set记录是否有重复元素,只要没有重复都可以移动right,更新最长子串长度 如果有重复,移动left,并从set里移除对应的字符
def max_sub_len(s): existed = set() res = 0 left, right = 0, 0 while right < len(s): if s[right] not in existed: existed.add(s[right]) res = max(res, len(existed)) right += 1 else: t.remove(s[left]) left += 1 return res
在微服务的架构下,公司内部会有非常多的独立服务。
服务之间可以相互调用,往往大型应用调用链条很长,如果出现循环依赖将出现非常恶劣的影响。
对于一个具体应用,已知各个服务的调用关系(即依赖关系),请判断是否存在循环调用。
输入:
一组服务依赖关系list,('A', 'B') 表示 A 会调用 B 服务
service_relations = [('A', 'B'), ('A', 'C'), ('B', 'D'), ('D', 'A')]
输出:
由于存在 A - B - D - A 故存在循环依赖,返回True;反之如果不存在,返回False
Follow up:
1. 如果有多个环,请都检测出来
2. 返回每个环中的服务名
--- ### 316. 比较版本号 【Question】【Answer】
可以采用拓扑排序 或者 DFS思路解决
比较两个版本号 version1 和 version2。
如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1, 除此之外返回 0。
你可以假设版本字符串非空,并且只包含数字和 . 字符。
. 字符不代表小数点,而是用于分隔数字序列。
例如,2.5 不是“两个半”,也不是“差一半到三”,而是第二版中的第五个小版本。
你可以假设版本号的每一级的默认修订版号为 0。例如,版本号 3.4 的第一级(大版本)和第二级(小版本)修订号分别为 3 和 4。其第三级和第四级修订号均为 0。
示例 1:
输入: version1 = "0.1", version2 = "1.1"
输出: -1
示例 2:
输入: version1 = "1.0.1", version2 = "1"
输出: 1
示例 3:
输入: version1 = "7.5.2.4", version2 = "7.5.3"
输出: -1
示例 4:
输入:version1 = "1.01", version2 = "1.001"
输出:0
解释:忽略前导零,“01” 和 “001” 表示相同的数字 “1”。
示例 5:
输入:version1 = "1.0", version2 = "1.0.0"
输出:0
解释:version1 没有第三级修订号,这意味着它的第三级修订号默认为 “0”。
--- ### 317. 单链表(奇数位升序,偶数位降序)的排序 【Question】 单链表,奇数位升序,偶数位降序,现在要求整体排成全局升序 输入:1->200->10->120->30->8->88->4 输出:1->4->8->10->30->88->120->200【Answer】
方法1:分割+解析,两次遍历,线性空间
方法2:双指针,一次遍历,常数空间
--- ### 318. 蛇形打印二叉树 【Question】 输入一棵二叉树,比如: ``` 0 1 2 3 4 5 6 7 8 9 ``` 将它蛇形输出,结果如下: 0,1,2,6,5,4,3,7,8,9【Answer】
思路:链表可以随便拆、组合 先把奇数和偶数拆开,形成两个链表,一个升序和一个降序 1->10->30->88 200->120->8->4 然后将降序的反转,再合并成一个列表--- ### 319. 冒泡排序和快速排序的复杂度区别是什么,如何实现? 【Question】 ```javascript /** * Quick Sort **/ function quickSort(arr) { // 补全代码 } console.log(quickSort([1, 3, 10, 6, 2, 8, 7])) // output: [1, 2, 3, 6, 7, 8, 10] /** * BubbleSort **/ function bubbleSort(arr) { // 补全代码 } console.log(bubbleSort([1, 3, 10, 6, 2, 8, 7])) // output: [1, 2, 3, 6, 7, 8, 10] ```【Answer】
1. 依赖栈记录每层节点值:层次便利,按偶数层(根是 0 层)从右到左,奇数层从左到右输出,时间空间复杂度都是 O(n),n 是节点数 2. 不依赖栈,递归:d 是层数,for i from 0 to d, 如果 i 是偶数,后序遍历二叉树的 0到i层,输出第i层的节点;如果i是奇数,先序遍历 0到 i 层,也只输出第i层节点。空间复杂度是 O(d) 即递归深度,时间复杂度是 o(d^2) 因为 0层节点会被访问 d 次,1 层节点 d-1 次,以此递推。--- ### 320. 岛屿数量 【Question】【Answer】
>1. 快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 >1. 冒泡排序算法的运作如下:(从后往前)比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 ```javascript /** * Quick Sort O(NLogN) **/ function quickSort(arr) { const res = []; if (arr.length <= 1) { return arr; } const leftArr = []; const rightArr = []; const q = arr[0]; for (let i = 1, l = arr.length; i < l; i++) { if (arr[i] > q) { rightArr.push(arr[i]); } else { leftArr.push(arr[i]); } } return res.concat(quickSort(leftArr), [q], quickSort(rightArr)); } /** * BubbleSort O(N*N) **/ function bubbleSort(arr) { for (let i = 0, l = arr.length; i < l - 1; i++) { for (let j = i + 1; j < l; j++) { if (arr[i] > arr[j]) { let tem = arr[i]; arr[i] = arr[j]; arr[j] = tem; } } } return arr; } ```给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。
--- ### 331. 二路归并 【Question】【Answer】
我们可以将二维网格看成一个无向图,竖直或水平相邻的 1 之间有边相连。
为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0
最终岛屿的数量就是我们进行深度优先搜索的次数
class Solution { private: void dfs(vector<vector
>& grid, int r, int c) { int nr = grid.size(); int nc = grid[0].size(); grid[r][c] = '0'; if (r - 1 >= 0 && grid[r-1][c] == '1') dfs(grid, r - 1, c); if (r + 1 < nr && grid[r+1][c] == '1') dfs(grid, r + 1, c); if (c - 1 >= 0 && grid[r][c-1] == '1') dfs(grid, r, c - 1); if (c + 1 < nc && grid[r][c+1] == '1') dfs(grid, r, c + 1); } public: int numIslands(vector<vector<char>>& grid) { int nr = grid.size(); if (!nr) return 0; int nc = grid[0].size(); int num_islands = 0; for (int r = 0; r < nr; ++r) { for (int c = 0; c < nc; ++c) { if (grid[r][c] == '1') { ++num_islands; dfs(grid, r, c); } } } return num_islands; } }; </code></pre> </pre> </details> --- ### 321. 把中文数字转成int数字 【Question】
在中文页面解析、中文数据处理中,常常遇到用中文表示的数字,例如:五千三百万零五百零一。
我们一般需要把它转化成int型整数,进行实际存储和使用。 请完成一亿(不含)以内的中文数字到int整数的转换
--- ### 322. Hash表设计 【Question】 常规的hash表设计及变通。问题由浅入深递进 1. 基本的原理,负载因子,扩容的原理 2. 假设内存受限4G,hash表已经占用3G内存,怎么使用剩下的那一个G的内存空间 3. 怎么在文件中设计类似于hash表的结构,能够在文件中快速查找一个key/value对【Answer】
// 递归解法 def cn2digital(number): name2val = {u'一': 1, u'二': 2, u'三': 3, u'四': 4, u'五': 5, u'六': 6, u'七': 7, u'八': 8, u'九': 9} unit2count = {u'十': 10, u'百': 100, u'千': 1000, u'万': 10000} for unit in [u'万', u'千', u'百', u'十']: if unit in number: n1, n2 = number.split(unit) return cn2digital(n1) * unit2count.get(unit) + cn2digital(n2) if not number: return 0 for c in number: if c == u'零': continue return name2val.get(c) // 非递归解法 def cn2digital(number): name2val = {u'一': '1', u'二': '2', u'三': '3', u'四': '4', u'五': '5', u'六': '6', u'七': '7', u'八': '8', u'九': '9'} unit2count = {u'十': 1, u'百': 2, u'千': 3, u'万': 4} res = [] base_count = 0 for num in number[::-1]: if num in name2val: res.append(name2val.get(num)) continue zero_count = 0 if num in unit2count: zero_count = max(0, unit2count.get(num) + base_count - len(res)) if num == u'万': base_count += 4 for _ in range(zero_count): res.append('0') return 0 if not res else int(''.join(res[::-1])) assert cn2digital(u'一万零一') == 10001 assert cn2digital(u'三千五百万') == 35000000 assert cn2digital(u'三千五百一十万') == 35100000 assert cn2digital(u'三千五百零一万') == 35010000 assert cn2digital(u'三千五百零一万零五百') == 35010500 assert cn2digital(u'三千五百零一万五千五百五十五') == 35015555 assert cn2digital(u'一百万') == 1000000 assert cn2digital(u'二百三十四万三千四百九十三') == 2343493
--- ### 323. 1-n数字字典序第k大 【Question】 给你一个数字n(n < 1e9), 再给你一个数字k(k < n), 要求你找到1, 2, 3, ... n按照字典序排序后, 第k大的数字; 如, n = 15, k = 7; 那1 ~ 15按照字典序排序为: 1, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5, 6, 7, 8, 9; 则答案为15;【Answer】
1. 扩容时注意关注 rehash 过程 2. 类似于多个Hash组成一个List 3. 类似于leveldb的思路--- ### 324. 在数组中找出和值为给定值的两个数 【Question】 输入一维数组array和n,找出和值为0的任意两个元素。例如: ``` python array = [2, 3, 1, 10, 4, 30] n = 31 ``` 则结果应该输出[1, 30] 顺序不重要 如果有多个满足条件的,返回任意一对即可【Answer】
利用字典树的思想; 我们假设有这么一棵树, 每个节点都要10个儿子, 10条到儿子的边分别对应数据0~9; 那么我们在这棵树上, 对边按照0~9的顺序进行DFS, 当走到第k个节点时, 该节点对应的数字既为我们的第k大字典序数字;--- ### 325. 老虎吃羊问题 【Question】 在岛上有100只老虎和1只羊,老虎可以吃草,但他们更愿意吃羊。 假设: A:每次只有一只老虎可以吃样,而且一旦他吃了羊,他自己就变成羊。 B:所有的老虎都是聪明而且完全理性的,他们的第一要务是生存。 问最后这只羊会不会被吃?如果是n只老虎和一只羊呢?【Answer】
解法1: 本题容易想到用哈希表,迭代一次边建表边查找n - array[i]是否在hash_table中即可。 该方法空间开销比较大 解法2:先对数组做排序,然后首尾两个指针求和,如果小于n则左指针右移,如果大于n则右指针左移。 该方法时间复杂度O(nlogn) 推荐考察解法2,附带做了排序算法的考察--- ### 326. 爬虫url去重-多线程并发 【Question】 用爬虫抓取网页时, 一个较为重要的问题, 就是对爬去的网页去重; 请你详细的设计一种数据结构, 用来检验某个URL之前是否已经被爬取过; 并给出每次检验的复杂度, 以及整体的空间复杂度; 一般爬虫在实现时, 都会利用多线程并发的爬取; 现在需要你升级一下之前的实现, 以保证并发安全;【Answer】
思路:先simplify。 - 1只老虎,肯定吃。 - 2只老虎肯定不吃,否则就被另一只吃了。 - 3只老虎,如果一只老虎吃掉了羊,问题就转换为2只老虎和1只羊的情况,显然另外两种老虎不敢轻举妄动。所以羊会被吃。 - 4只老虎,如果某一只老虎吃了羊,问题转化为3只老虎和1只羊的问题,它肯定会被接下来的某一只吃掉,然后其他两只只能等着。所以4只老虎,大家都不敢吃羊。 这样归纳,我们就可以发现如果老虎数目是奇数,那么羊肯定被吃,如果是偶数,那么羊肯定不会被吃。--- ### 327. 蓄水问题, 1维 【Question】 给定一个一维数组用于描述一个海拔,相邻的海拔高度不同,则下雨后低洼海拔的洼地会有积水,假设雨水足够,能够填满所有低洼地段,计算下雨后所有低洼地段总蓄水量。 例如给定数组为: 5, 2, 1, 4, 3 则:所有低洼地段蓄水为量为 5【Answer】
通常来说, 不需要改变数据结构本身, 只需要在其外围包裹一些简单的操作, 就能大大提高其并发度; 比如可以根据URL的后几位, 进行hash分桶; 注意这里选取URL的后几位, 而不是前几位, 是为了让hash更加均匀, 因为同个域名下的前缀大多是相同的; 然后每个桶内维护一个上述实现的去重的数据结构;--- ### 328. 轮流抛硬币问题 【Question】 # A和B玩抛硬币游戏,AB轮流抛一枚硬币,谁先抛到正面谁就获胜并结束游戏,硬币两面均匀。A先抛,请问A获胜的概率是多少?【Answer】
定义左极高点: 该点左边最高的那个点; 定义右极高点: 该点右边最高的那个点; 于是每个点的蓄水高度为: min(左极高点高度, 右极高点高度) - 该点高度,累加每个点的高度即可;所有点的左右极点可以分别通过一次向右和向左的扫描得到; 算法复杂度为 O(n)--- ### 329. 查找第一个缺失的正整数 【Question】 查找第一个缺失的正整数。 时间复杂度O(n) ,空间复杂度 O(1) Example 1: Input: [1,2,0] Output: 3 Example 2: Input: [3,4,-1,1] Output: 2 Example 3: Input: [7,8,9,11,12] Output: 1【Answer】
将A和B的获胜情况罗列,可以看到规律。A第一次抛获胜概率是1/2, A不获胜情况下B第一次获胜概率1/2*1/2=1/4。 所以A获胜概率是:1/2+1/8+1/32+...=2/3。B获胜的概率是:1/4+1/16+...=1/3--- ### 330. 微信跳一跳 【Question】【Answer】
1 排序之后查找 2 把出现的数值放到与下标一致的位置,再判断什么位置最先出现不连续的数值,就是答案了。 3 和2差不多,把出现过的数值的下标位置做好标识,如果没有出现过的数组的下标就没有标识,那么这个就是答案从起点开始接下来有 100 个方块,相邻方块间的距离都为 1,每个方块上有增加体力的食用蘑菇或减少体力的毒蘑菇,蘑菇带来的体力改变是已知的。一个人初始体力为 m,每次可以往前跳任意个方块,体力耗尽就会死掉。
- 每跳一次消耗的体力与跳的距离成正比,比例为 1。问这个人能否跳到终点,如果能,求可能剩余的最大体力。
- 每跳一次消耗的体力是跳的距离的平方。问这个人能否跳到终点,如果能,求可能剩余的最大体力。
- 每跳一次消耗的体力是跳的距离的平方,每跳一个方块加 1 分。问这个人能否跳到终点,如果能,求可能得到的最高分数。
【Answer】
- 第 1 问,贪心算法,时间复杂度 O(n)
def flip1(m, array): """ 微信跳一跳第一问 :param m: 初始体力 :param array: 蘑菇带来的体力改变 :return: 可能剩余的最大体力 """ for n in array: m -= 1 # 消耗的体力与跳的距离成正比 if m <= 0: # 体力不足死掉 return -1 if n > 0: # 只跳加体力的格子 m += n if array[-1] < 0: # 终点的蘑菇必须吃 m += array[-1] return m if m > 0 else -1
- 第 2 问,动态规划,时间复杂度 O(n^2)
def flip2(m, array): """ 微信跳一跳第二问 :param m: :param array: :return: 可能剩余的最大体力 """ # powers 表示在每个格子可能剩余的最大体力 powers = [m] + [0] * len(array) for i in range(1, len(array) + 1): for j in range(i): if powers[j] > (i - j) ** 2: powers[i] = max(powers[i], powers[j] - (i - j) ** 2 + array[i - 1]) return powers[-1] if powers[-1] > 0 else -1
- 第 3 问,动态规划,时间复杂度 O(n^3)
def flip3(m, array): """ 微信跳一跳第三问 :param m: :param array: :return: 可能跳的最多格子数 """ # scores 表示在每个格子得到不同分数时可能剩余的最大体力 scores = [{0: m}] + [{} for _ in range(len(array))] for i in range(1, len(array) + 1): for j in range(i): for score, power in scores[j].items(): left = power - (i - j) ** 2 if left > 0 and left + array[i - 1] > 0: scores[i][score + 1] = max(scores[i].get(score + 1, 0), left + array[i - 1]) return max(scores[-1].keys()) if scores[-1].keys() else 0
实现一个merge函数,功能是将两个有序数组,将它们合并成一个有序数组,如:
let arr1 = [1, 2] let arr2 = [-1, 2, 8, 9] merge(arr1,arr2) // 返回 [-1, 1, 2, 2, 8, 9]
--- ### 332. 拆分字符串 【Question】 输入一个列表的单词,及一个长字符串,判断字符串可否由列表中的单词组成。比如: 输入: 单词列表 dict:I, love, byte, bytedance 字符串 s:Ilovebytedance 输出: True【Answer】
function merge(arr1, arr2) { // 可以判断一下arr1、arr2是否是数组 if(!(Array.isArray(arr1) && Array.isArray(arr2))) throw '...' let i = 0, j=0, t=0 let temp = []; while (i < arr1.length && j < arr2.length) { temp[t++] = arr1[i] <= arr2[j] ? arr1[i++] : arr2[j++]; } while (i < arr1.length) { // 数组1有剩余 temp[t++] = arr1[i++]; } while (j < arr2.length) { // 数组2有剩余 temp[t++] = arr2[j++]; } return temp }
--- ### 333. 给定单链表,求离终点距离为 k 的节点 【Question】 给定单链表,求离终点距离为 k 的节点,要求只扫一次且空间复杂度为O(1)【Answer】
1. 用一个数组 validWords[] 记录字符串当前位置之前的字符串是否可以用 dict 组成,validWords[i]=True 可以,否则不可以。默认不可以 2. for 循环 i 从 0 到 s.length: a. 如果 s[0-i] 在 dict 中,设置 validWords[i]=True b. 如果validWords[i]=True,for 循环 j 从 i+1 到 s.length-1,判断 s[i+1 到 j] 是否在 dict 中,如果是,设置 validWords[j]=True 3. 如果 validWords[s.length-1] = True, return True--- ### 334. 数组0值筛选 【Question】 给定一个非负数组,要求你对它进行操作, 使得所有的0 放在数组左边, 大于 0 的值位于数组右边, 要求空间为O(1), 时间为O(n);【Answer】
两个指针, 第一个先向后走k步; 然后两个一起走; 当第一个指针到达链表尾部时另一个指针指向的就是距离终点距离为 k 的节点。--- ### 335. 区间合并 【Question】 给定一堆左右闭合的区间,要求对重叠的区间进行合并,返回合并后的区间段。 例如:[9, 10], [1,4], [3,6], [8, 12] 那么合并后的区间段为:[1, 6], [8, 12]【Answer】
从右往左扫描,同时维护另一个指针指向出现在最右侧的 0,每次扫描到非 0 数字则和它交换--- ### 336. 爬楼梯问题 【Question】【Answer】
由于输入的区间段,不一定按照起点排好序,所以先按照起点坐下排序利于后续合并。 ``` python def merge(intervals): res = [] intervals = sorted(intervals, key=lambda x:x.start) pre = None for interval in intervals: if not pre: pre = interval continue if interval.start <= pre.end: if interval.end >= pre.end: pre.end = interval.end else: res.append(pre) pre = interval if pre: res.append(pre) return res ```
- 爬楼梯问题:爬楼梯时,每一步会有两个选择:爬一个台阶和爬两个台阶,问:楼梯总台阶数为n,则一共有多少种爬法,写一个函数f,使得:总的爬法= f(n)。举例:n=3时,则共有:(1,1,1)、(1,2) 、(2,1)三种爬法,则f(3)=3。
--- ### 337. 找零钱问题 【Question】 有1,2,5,10等不同零钱,问给N元,有多少种不同的组合方式?【Answer】
斐波拉契:f(n)=f(n-1)+f(n-2)
代码需要判断边界
--- ### 338. 把二叉树压成单链表 【Question】 对于输入的二叉树,舍弃left指针,用right指针按照先根顺序串成单链表。例如: ``` 1 2 5 3 4 ``` 转为单链表为1 -> 2 -> 3 -> 4 -> 5 要求in-place【Answer】
假设有m种零钱,具体面值存在arr中,要找的钱为n。 使用m种零钱去找零n元,可以拆分为: 完全不用第m种零钱 和 至少用一次第m种零钱 ``` python def zhaolin(arr, n, m): if n == 0: return 1 if n < 0: return 0 if m <= 0: return 0 return zhaolin(arr, n, m - 1) \ # 不用第m-1号零钱 + zhaolin(arr, n - arr[m - 1], m) # 至少使用1次m-1号零钱 ```--- ### 339. 最短子数组之和 【Question】 给定1个正整数数组array和1个正整数n,从array中寻找和值**大于等于n**的最短子数组。 如果存在,则返回最短子数组长度;如果不存在返回0。 例如:array = [1, 3, 4, 3, 9, 1], n = 12, 那么子数组[3, 9]满足条件且长度最短为2。【Answer】
本题利用dfs递归比较好解,关键点是要把左子树的最后一个节点和右子树头接上。 关键代码: ``` python def flatten(root): if not root: return if root.left: flatten(root.left) if root.right: flatten(root.right) tmp = root.right root.right = root.left root.left = None while root.right: root = root->right root.right = tmp ```--- ### 340. 二叉树最大宽度 【Question】【Answer】
本题用两个指针的思路解决,时间复杂度O(n) 指针left和right记录子数组的左右边界位置, 让right向右移,直到子数组和>=n或到达数组末尾,更新最短距离, 将left像右移一位,然后在和值中减去移去的值, 重复上面的步骤,直到right到达末尾,且left也无法再右移 ``` python def min_sub_len(arrary, n): res = sys.maxsize left, cur_sum = 0, 0 for i in range(0, len(array)): cur_sum += array[i] while left <= i && cur_sum >= n: res = min(res, i - left + 1) cur_sum -= array[left] left += 1 return 0 if res == sys.maxsize else res ```给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度(每一层的节点可能为空)。
--- ### 341. 二叉树中序遍历打印节点信息 【Question】 实现一个二叉树中序遍历函数,打印所有节点信息。 typedef string DataType; typedef struct BinTreeNode { DataType data; struct BinTreeNode* leftChild; struct BinTreeNode* rightChild; } BinTreeNode; typedef BinTreeNode* BinaryTree; void MidOrderPrint(BinaryTree tree) { //打印二叉树节点信息 }【Answer】
每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。
思路:主要想法是给每个节点一个 position 值,如果我们走向左子树,那么 position -> position * 2,如果我们走向右子树,那么 position -> positon * 2 + 1。当我们在看同一层深度的位置值 L 和 R 的时候,宽度就是 R - L + 1。
方法 1:深度优先搜索
方法 2:宽度优先搜索
--- ### 342. 从日志文件中抽取指定分钟的行 【Question】 某个很大(假设有几十T)的日志文件,每行的前两列为日期和时间(时间相等或递增),之后为日志内容,日志内容长度不一。例如: 2015-01-01 00:00:01 this is the first line 2015-01-01 00:00:03 this is another line ... ... 2017-12-31 12:34:45 this is the last line 需要将这个日志文件的某一分钟(例如2017-10-01 10:02这一分钟)的日志保存到另一个文件中。【Answer】
二叉树的遍历按照根节点位置的不同,分为前序遍历、中序遍历、后序遍历。 前序遍历:根节点->左子树->右子树 中序遍历:左子树->根节点->右子树 后序遍历:左子树->右子树->根节点 例如二叉树结构如下 ........... a ........../.....\ ........b........c ....../....\ ....d........e .....\ ...../ ......f....g 前序遍历:abdfegc 中序遍历:dfbgeac 后序遍历:fdgebca 解法一:递归方式 void MidOrderPrint(BinaryTree tree) { if(NULL == tree) { return; } MidOrderPrint(tree->leftChild); printf("%s ", tree->data.c_str()); MidOrderPrint(tree->rightChild); } | | 解法二:非递归方式 //假设 Stack 为已经实现的堆栈类型,支持push和pop方法 class Stack { void push(BinTreeNode *); BinTreeNode *pop(); BinTreeNode *top(); }; void MidOrderPrint(BinaryTree tree){ if(NULL == tree) { printf("this tree is empty!\n"); return; } Stack s; BinTreeNode *curNode = tree; while(curNode != NULL || s.top() != NULL) { while(curNode != NULL) { s.push(curNode); curNode = curNode->leftChild; } curNode = s.pop(); printf("%s ",curNode->data); curNode = curNode ->rightChild; } } _--- ### 343. 概率问题:赌徒获胜胜率计算 【Question】 有两个技巧相当的赌徒 A 和 B(即两人赌博胜率各为0.5),现在设定这样的获胜规则: 1. A只要赢了2局或以上就获胜 2. B要赢3局或以上才能获胜。 问双方胜率各为多少?【Answer】
使用二分法寻找位置,seek到文件的该位置读取下一行的内容来判断需要寻找的行。 本题考察二分查找和对文件操作的了解。需要考虑seek到一行中间的情况。--- ### 344. 旋转链表 【Question】 给定单链表,要求返回向右移k位后的新链表,例如: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> NULL k = 3,则返回:4 -> 5 -> 6 -> 1 -> 2 ->3 -> null【Answer】
如果直接列举所有情况也是可以得出答案的,但有简单方法可以剪枝。 我们用a表示A取胜,用b表示B取胜。 只要意识到,**无论结果如何,最多4局就可分出胜负**,这样就好计算了。 可以列举所有可能的情形如下: aaaa aaab abba bbab \ baaa baba abab babb \ abaa bbaa aabb abbb \ aaba baab bbba bbbb 也可以计算B获胜的情况 1(bbbb)+ 4(abbb babb bbab bbba) 所以A获胜概率是\dfrac{11}{16},B获胜概率是\dfrac{5}{16}--- ### 345. 链表求和 【Question】 给定一个链表`L1`、`L2`,每个元素是为10以内的正整数,链表表示一个数字,表头为高位。 求两个链表之和,以链表形式返回 如: ``` L1 5 -> 6 -> 2 -> 3 -> 7 L2 1 -> 7 -> 0 -> 9 -> 2 和为: 56237+17092=73329 ``` 拓展1: 表头改为低位 拓展2: 两表内数字不重复,优化 拓展3:【Answer】
用一快一慢两个指针fast,slow,快指针提前走k步 然后快慢一起走,直到fast.next == NULL 这是slow->next即为新head,将fast.next指向head,并从slow处断开 本题要注意参数处理: 空链表 k大于链表长度--- ### 346. 多叉树最大高度 【Question】【Answer】
```java /** * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { /** * @param l1: the first list * @param l2: the second list * @return: the sum list of l1 and l2 */ public ListNode addLists(ListNode l1, ListNode l2) { if(l1 == null && l2 == null) return null; ListNode head = new ListNode(0); ListNode tail = head; int carry = 0; while(l1 != null && l2 != null){ int value = carry + l1.val + l2.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l1 = l1.next; l2 = l2.next; } while(l1 != null){ int value = carry + l1.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l1 = l1.next; } while(l2 != null){ int value = carry + l2.val; carry = value / 10; value = value % 10; ListNode node = new ListNode(value); tail.next = node; tail = tail.next; l2 = l2.next; } if(carry > 0){ ListNode node = new ListNode(carry); tail.next = node; tail = tail.next; } return head.next; } } ``` 拓展1 翻转 拓展2 set/bitmap要求:
- 自定义多叉树节点node结构(只需要定义节点结构即可,无需构建树)
- 求多叉树的最大高度 getDepth(node)
--- ### 347. 单链表对折 【Question】 对输入的链表做对折操作 例如:有n个节点的单链表:1 -> 2 -> 3 -> ... -> n-2 -> n-1 -> n -> NULL 处理后的链表为1 -> n > 2 -> n-1 -> 3 -> n-2 ... 要求在原链表基础上操作。【Answer】
节点信息
// 定义树节点 Node { ... // 属性 children:[], // 子节点 }
getDepth实现
function getDepth(node){ if (!node) return 0 if (node.children && node.children.length){ let depth=0; for(let e of node.children){ depth=Math.max(getDepth(e),depth) } return depth+1 }else{ return 1 } }
--- ### 348. 螺旋打印二维数组 【Question】 输入m * n的二维矩阵,要求从(0,0)开始螺旋向内完成打印输出。 具体打印方法: 1. ➡️先从左向右打印第一行; 2. ⤵️再从上向下打印最后一列; 3. ⬅️然后从右向左打印最后一行; 4. ⤴️最后从下向上打印第一列。 如此往复,完成所有元素打印。 例如: ``` python input = [ [ 1, 2, 3, 4, 5], [14, 15, 16, 17, 6], [13, 20, 19, 18, 7], [12, 11, 10, 9, 8], ] output = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ``` 附件要求:不允许再开辟O(mn)的存储,直接打印输出。【Answer】
解题思路 1. 先找到中点,将原链表1分为2,即为l_1, l_2; 2. 然后翻转l_2; 3. 最后对l_1和l_2归并。 本题主要考查候选人的编程功底以及好的编程习惯。 比较好的答案应该是4个函数: * def fold(head): # 对折入口函数 * def find_middle(head): # 找中点函数,用一快一慢两指针 * def reverse(head): # 链表翻转 * def merger(l1, l2): # 链表合并--- ### 349. 字符串相似度-编辑距离 【Question】 百科定义:编辑距离,又称Levenshtein距离(也叫做Edit Distance),是指两个字串之间,由一个转成另一个所需的最少编辑操作次数,如果它们的距离越大,说明它们越是不同。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。 例如将kitten一字转成sitting: sitten (k→s) sittin (e→i) sitting (→g)【Answer】
``` python def print_martix(matrix): ''' : type martix: list[list] ''' if not matrix: return start_row, start_col, end_row, end_col = 0, 0, len(matrix) - 1, len(matrix[0]) - 1 while start_row <= end_row and start_col <= end_col: for k in xrange(start_col, end_col + 1): print matrix[start_row][k] start_row += 1 for k in xrange(start_row, end_row + 1): print matrix[k][end_col] end_col -= 1 if start_row > end_row or start_col > end_col: break for k in xrange(end_col, start_col - 1, -1): print matrix[end_row][k] end_row -= 1 for k in xrange(end_row, start_row - 1, -1): print matrix[k][start_col] start_col += 1 ``` 测试用例 ``` python input0 = [[]] input1 = [[1]] input2 = [[1, 2]] input3 = [[1], [2]] ```--- ### 350. 找旋转数组的中位数 【Question】 有一个有序数组如:1,2,3,4,5,6,7 随机选一个点比如5反转变成:5,6,7,1,2,3,4 对于上面翻转后的数组,求它的中位数。【Answer】
本题用DP思路来解决,递推关系: 若str1[i] == str2[j],temp=0,否则temp=1 d[i][j] = min([i-1][j] + 1, d[i][j-1] + 1, d[i - 1, j - 1] + temp)--- ### 351. 求二叉树的最长路径 【Question】 给定一棵二叉树,求其中的最长路径,所谓路径是指:连通两个节点的最小边数。【Answer】
1. 最简单的,排序,复杂度最高; 2. 遍历整个数组,找到最小的数字比如 1,然后 (index+n/2)%n 3. 二分,找到最小的数字就能找到中位数。淘汰哪一半?--- ### 352. 精简文件路径 【Question】 对输入的unix风格的文件路径做精简。 例如:/a/b/.././ 精简为 /a【Answer】
使用后序遍历O(n)。遍历过程中计算以当前节点为根的最长路径,返回当前节点的高度。--- ### 353. 绝对众数 【Question】【Answer】
为了处理..,容易想到stack解决 ``` python def simplify_path(ppath): segs = path.split('/') stack = [] for seg in segs: if not seg or seg == '.': continue elif seg == '..': if len(stack): stack.pop() else: stack.append(seg) return '/' + '/'.join(stack) ``` 特殊case:/../; ////foo/https://leetcode-cn.com/problems/majority-element/
定义:给定N个数,称出现次数最多的数为众数,若某数出现的次数大于N/2称为绝对众数。如
A={1, 2, 1, 3, 2}中,1和2都是众数,但都不是绝对众数
如A={1,2,1,3,1}中,1是绝对众数。
--- ### 354. 环节点的走法数 【Question】 一个环上有10个点,编号为0-9, 从0点出发,每步可以顺时针到下一个点,也可以逆时针到上一个点, 求:经过n步又回到0点有多少种不同的走法? 举例: 如果n = 1,则从0出发只能到1或者9,不可能回到0,共0种走法 如果n = 2,则从0出发有4条路径:0->1->2, 0->1->0, 0->9->8, 0->9->0,其中有两条回到了0点,故一共有2中走法【Answer】
解题思路:任意删除2个不相同的数,不改变绝对众数
class Solution { public int majorityElement(int[] nums) { int m = nums[0]; // 绝对众数 int count = 0; // 计数 for(int i = 0; i< nums.length; i++) { if(count == 0) { m = nums[i]; count++; }else if(m == nums[i]) { count++; } else { count--; } } return m; } }
--- ### 355. 单词搜索 【Question】【Answer】
DP问题,令F(k, i)表示从0点出发经过k步到达i点的走法数,题目所求为F(n, 0) F(k, i) = F(k - 1, (i + 1) % 10) + F(k - 1, ((i - 1) + 10) % 10) 初始状态:f[1, 0] = 0, f[1, 1] = 1, f[1, 2] = 0, ... f[1, 9] = 1给定1个二维字符数组cmap和单词1个word,搜索word是否在map中。
搜索的定义是从cmap的任意位置开始,可以上下左右移动,依次和word每个字符匹配,如果word能匹配完,则存在,否则不存在。
注:cmap中的每个位置只能被访问1次
a c d z x t r o f i w o
例如上面的cmap,则'zoo'能搜索到,'wto'不能搜索到
--- ### 356. 寻找数组任一峰值 【Question】 峰值定义:比前后元素都大;数组可能存在多个峰值,返回任一个就行 例如:1 2 3 2 1 4 3 可以返回3或者4 可以默认前提: 1 任意元素不等于相邻元素 2 首、尾元素只要比右、左一个元素大即可认为是峰值【Answer】
比较容易想到用DFS解决,如果候选没思路,可以提示属于哪类问题,如果想到图就好办了。
def search(cmap, word): if not cmap or not word: return False visited = [[False] * len(cmap[0]) for _ in range(len(cmap))] for i in range(len(cmap)): for j in range(len(cmap[0])): if dfs(cmap, word, 0, i, j, visited): return True def dfs(cmap, word, pos, i, j, visited): if pos == len(word): return True if i < 0 or i >= len(cmap) or j < 0 or j > len(cmap[0]) or visited[i][j] or cmap[i][j] != word[pos]: return False find = False visited[i][j] = True for (ii, jj) in [(-1, 0), (1, 0), (0, -1), (0, 1)]: if dfs(cmap, word, pos + 1, i + ii, j + jj, visited): find = True break visited[i][j] = False # 易错点 return find
--- ### 357. 对称树判断 【Question】 判断一棵二叉树,是否是(左右)对称树: 对称例子: ``` For example, this binary tree is symmetric: 1 / \ 2 2 / \ / \ 3 4 4 3 ``` 不对称例子: ``` 1 / \ 2 2 \ \ 3 3 ```【Answer】
O(n)肯定能解决问题,但本题结合二分能优化时间复杂度 如果a[mid] < a[mid + 1]说明峰值后半段,否则在前半段 核心代码 ``` python def find_peak(a): left, right = 0, len(a) - 1 while left < right: mid = left + (right - left) / 2 if a[mid] < a[mid + 1]: left = mid + 1 else; right = mid return right--- ### 358. 给定链表,将其中的某一部分翻转 【Question】 给定链表,将其中的某一部分翻转, 要求空间复杂度为O(1);【Answer】
```java /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ public class Solution { public boolean isSymmetric(TreeNode root) { if (root == null) return true; return solve (root.left, root.right); } public boolean solve(TreeNode t1, TreeNode t2) { if (t1 == null && t2 == null) return true; if (t1 == null && t2 != null || t1 != null && t2 == null || t1.val != t2.val) return false; return solve(t1.left, t2.right) && solve(t1.right, t2.left); } } ```--- ### 359. 带TTL的N-kv cache 【Question】 "实现一个带过期的KV cache, 所有KV过期间隔相同, 满足如下性质: 1. 最多存储n对KV; 2. 如果大于n个, 则随意剔除一个已经过期的KV; 3. 如果没有过期的KV, 则将最老的那一对KV给剔除; 4. 查询时如果已经过期, 则返回空; 5. 所有操作复杂度都为O(1)"【Answer】
指针a与d指向要翻转部分的第一个节点,指针b指向a的上一个节点; 指针c指向a,并将a指向下一个节点,再将c插入到b的后面,重复执行该操作直到a走出要翻转的区间; 最后将a接在d后面,完成翻转。--- ### 360. 二叉树的镜像 【Question】 实现一个函数,完成输入一个二叉树,输出该二叉树的镜像。 二叉树结点的定义如下: ``` struct BinaryTreeNode { int data; BinaryTreeNode *Left; BinaryTreeNode *Right; }; ```【Answer】
用一个map来维护K->V索引; 用一个双向链表list来维护K, 保证越靠前的K时间越早; 用一个map来维护K->list.node, 该map用于删除时使用 剔除时, 从list取出最老的K, 从map中剔除即可; 最老的那个, 肯定最先过期; 如果都没有过期, 也只能剔除最老的那个; 因此在解决剔除与过期时, 完全同上; 为了解决过期返回空, 有比较简单的办法是在每次查询前先过期一次;--- ### 361. 输出二叉树左视角能看到的节点 【Question】 给定一颗二叉树: ``` 1 2 3 4 5 6 7 8 ``` 从左边看,输出能看到的 1,2,4,8 这四个节点,顺序无所谓。【Answer】
思路:先序遍历树的每个结点,若遍历到的结点有子结点,则交换它的两个子结点。 有两种实现方法: 1.递归实现: ``` void MirroRecursively(BinaryTreeNode *pNode) { if(NULL == pNode) return; if(NULL == pNode->Left && NULL == pNode->Right) return; BinaryTreeNode *pTemp = pNode->Left; pNode->Left = pNode->Right; pNode->Right = pTemp; if(pNode->Left) MirroRecursively(pNode->Left); if(pNode->Right) MirroRecursively(pNode->Right); } ``` 2.非递归实现,即使用循环实现: ``` void MirrorNonRecurively(BinaryTreeNode *pNode) { if(NULL == pNode) return; stack stackTreeNode; stackTreeNode.push(pNode); while(stackTreeNode.size()) { BinaryTreeNode *pNode = stackTreeNode.top(); stackTreeNode.pop(); if(NULL != pNode->Left || NULL != pNode->Right) { BinaryTreeNode *pTemp = pNode->Left; pNode->Left = pNode->Right; pNode->Right = pTemp; } if(NULL != pNode->Left) stackTreeNode.push(pNode->Left); if(NULL != pNode->Right) stackTreeNode.push(pNode->Right); } } ```--- ### 362. 日志提取 【Question】 你有一份非常大的日志文件; 日志的格式为: timestamp: content; 如 2017-01-01 20:00:00: hello hello hello; 需要你提取日志中指定时间内的内容; 如提取2017-01-01 20:00:00 ~ 2017-01-01 22:30:00的日志数据【Answer】
递归或者非递归都可以--- ### 363. 最多有两个不同字符的最长子串 【Question】 给定字符串s,返回最多包含两个不同字符的最长子串长度。 例如:s = 'abbcceefffffg' 最长子串为'eefffff'长度为5【Answer】
由于时间是递增的, 可以先二分到指定时间, 然后再进行读取;--- ### 364. 生成n阶螺旋数组 【Question】 本题是http://marvel.byted.org/#/question/detail/?id=816&nocontri=true的反向问题。 ``` python input = 1 output = [[1]] input = 2 output = [[1, 2], [4, 3]] input = 3 output = [[ 1, 2, 3], [ 8, 9, 4], [ 7, 6, 5]] ```【Answer】
本题采用两个指针left、right指针指向子串的起始和结束位置。 right不断前行,对left和right内的字符存入dict并计数,当dict的keys()超过2个时,向右移动left。 如此往复,不断更新最长子串长度。 ``` python def max_sub_len(s): max_len, left = 0, 0 count = collections.Counter() for right in xrange(len(s)): count[s[right]] += 1 while len(count) > 2: count[s[left]] -= 1 if count[s[left]] == 0: count.pop(s[left]) left += 1 max_len = max(max_len, right - left + 1) return max_len--- ### 365. 给定长度为n的整形数组,给定小一点的正数k,找到数组中出现次数大于 n/k 的数字 【Question】 举几个例子: 1. 假设 n=100,k=2,那么需要找到数组中出现次数大于 50 的数。 1. 假设 n=100,k=10,那么需要找到数组中出现次数大于 10 的数。【Answer】
``` python def gen_matrix(n): if n <= 0: return None matrix = [[0] * n for _ in xrange(n)] val = 1 start_row, start_col, end_row, end_col = 0, 0, n - 1, n - 1 while val <= n * n: for k in xrange(start_col, end_col + 1): matrix[start_row][k] = val val += 1 start_row += 1 for k in xrange(start_row, end_row + 1): matrix[k][end_col] = val val += 1 end_col -= 1 for k in xrange(end_col, start_col - 1, -1): matrix[end_row][k] = val val += 1 end_row -= 1 for k in xrange(end_row, start_row - 1, -1): matrix[k][start_col] = val val += 1 start_col += 1 return matrix ```--- ### 366. 平均延迟最大的调用链 【Question】【Answer】
1. 方案1:使用hashmap,遍历一次数组,将数组的数字当做key放入hashmap中,并将出现的次数作为value。之后再遍历hashmap将次数大于 n/k 的数打印出来即可 2. 方案2:假设要求空间复杂度为O(K),时间复杂度尽量低该怎么做? - 可以给一点提示:换一个思路,每次都从数组中删除K个互不相同的数,那么删除 n/k 次后,还在数组中的数的出现次数,应该至少都大于 n/k - 最终答案:申请一个K空间的hashmap,按照方案1的思路遍历数组并插入hashmap,每当hashmap有K个值时,就将hashmap里的value减1,如果为0,就从hashmap中删除该key。当数组遍历完,hashmap中剩余的key/value对,就基本是我们要找的数(还需要再次遍历hashmap检查一下次数)在微服务的架构下,公司内部会有非常多的独立服务。
服务之间可以相互调用,往往大型应用调用链条很多也很长,我们需要找出耗时最大的链条进行优化。(假设服务同时调用其依赖的下游服务)
例如:
A服务依赖B服务,平均调用延迟100ms,记为(A, B, 100)
其他依赖和延迟如下:
(A, C, 200)
(A, F, 100)
(B, D, 100)
(D, E, 50)
(C, G, 300)
那么服务A有三条调用链:A-B-D-E,A-C-G,A-F,平均延迟250,500,100
延迟最大的调用链是A-C-G,延迟为500ms
输入:
[(A, B, 100), (A, C, 200), (A, F, 100), (B, D, 100), (D, E, 50), (C, G, 300)]
输出:
500
Follow up:
1. 能否输出延迟最大的调用链,如果存在多条,都输出
--- ### 367. 最大能连续观看剧集数 【Question】 小明常常用BT下载连载电视剧、综艺观看。 因为种子、网络等诸多因素,可能不是所有的剧集都能现在下来,且现在的顺序也不一定按照从第一集到第n集排列。 请问:已知小明已下载的某部电视剧的剧集列表,求小明最多能连续观看多少集? 例如:episodes = [10, 1, 3, 4, 7, 6, 20, 5, 13, 23, 14] 那么小明做多能连续看[3, 4, 5, 6, 7]共5集 希望时间复杂度O(N)【Answer】
可以采用搜索的思路解决,例如DFS
--- ### 368. 根据访问日志统计出头条每日最火的n篇文章 【Question】 每日的访问日志记录有文章id,简化起见,访问的文章id存在输入数组中,给定n,要求返回查看次数最多的文章id 例如:visit_log = [ 10001, 1002, 10001, 20032, 302, 302] 如果n = 2,则根据上面的访问日志,不难看出[10001、302]是最火的2篇文章【Answer】
我们看一般的case寻找连续剧集,其实就看当前剧集i的前一集i-1下载了没,或者后一集i+1下载了没 也就是往前查查、往后查查,为了方便查,我们可以用hash,例如python里的set。 知道这个原理后,我们可以比较容易的写出代码。 ``` python def long_episode_count(episodes): remains = set(episodes) long_count = 0 for i in episodes: if i not in remains: continue remains.remove(i) while pre in remains: remains.remove(pre) pre -= 1 while next in remains: remains.remove(next) next += 1 long_count = max(long_count, next - pre - 1) return long_count ```--- ### 369. 实现字典树 【Question】 字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。 它有3个基本性质:根节点不包含字符,除根节点外每一个节点都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。 请实现字典树Class,并完成单词插入和查找函数。【Answer】
首先对访问日志的文章id做计数,然后根据计数做排序得到top n。 在排序方法选择方面,top n适合用堆来实现。 ``` python def top_article_id(visit_log, n): article_cnt = collections.Counter() for article_id in visit_log: article_cnt[article_id] += 1 heap = article_cnt.keys() def _heap_adjust(heap, parent, heap_size, article_cnt): while parent < heap_size: left, right = parent * 2 + 1, parent * 2 + 2 swap_pos = parent if left < heap_size and article_cnt[heap[left]] < article_cnt[heap[parent]]: swap_pos = left if right < heap_size and article_cnt[heap[right]] < article_cnt[heap[swap_pos]]: swap_pos = right if swap_pos != parent: heap[parent], heap[swap_pos] = heap[swap_pos], heap[parent] parent = swap_pos else: break for i in range(int(math.ceil(n / 2) - 1), -1, -1): heap_adjust(heap, i, n, article_cnt) for i in range(n, len(heap)): if article_cnt[heap[0]] < article_cnt[heap[i]]: heap[0] = heap[i] heap_adjust(heap, 0, n, article_cnt) return heap[:n]--- ### 370. 36进制正整数加法 【Question】 36进制由0-9,a-z,共36个字符表示,最小为'0' '0'~'9'对应十进制的0~9,'a'~'z'对应十进制的10~35 例如:'1b' 换算成10进制等于 1 * 36^1 + 11 * 36^0 = 36 + 11 = 47 要求按照加法规则计算出任意两个36进制正整数的和 如:按照加法规则,计算'1b' + '2x' = '48' 要求:**不允许把36进制数字整体转为10进制数字,计算出10进制累加结果再转回为36进制** 本题可任意改变进制,如加入大写字母变为62进制。【Answer】
参考实现代码: ``` python class TrieNode(object): def __init__(self): self.is_leaf = False self.children = [None] * 26 class Trie(object): def __init__(self): self.root = TrieNode() def insert(self, word): if not word: return cur_node = self.root for char in word: index = ord(char) - ord('a') if not cur_node.children[index]: cur_node.children[index] = TrieNode() cur_node = cur_node.children[index] cur_node.is_leaf = True def search(self, word): if not word: return False cur_node = self.root for char in word: index = ord(char) - ord('a') if not cur_node.children[index]: return False cur_node = cur_node.children[index] return cur_node.is_leaf ```--- ### 371. 数列(顺序为: 一堆负数 + 一堆 0 + 一堆正数),求最后一个出现的负数和第一个出现的正数 【Question】 数列(顺序为: 一堆负数 + 一堆 0 + 一堆正数),求最后一个出现的负数和第一个出现的正数的位置,从 1 开始计数。例如: -2,-1,0,0,0,0,4,5 最后一个负数出现的位置为:2,第一个正数出现的位置为7。【Answer】
``` python def add(num1, num2): ''' >>> add('', '') '' >>> add('', '1') '1' >>> add('ab', '') 'ab' >>> add('1b', '2x') '49' >>> add('0', '2x') '2x' >>> add('zz', '1') '100' ''' def _get_value(num): if ord('0') <= ord(num) <= ord('9'): return ord(num) - ord('0') return ord(num) - ord('a') + 10 def _to_chr(num): if 0 <= num <= 9: return str(num) return chr(ord('a') + num - 10) def _add(n1, n2, carry): result = _get_value(n1) + _get_value(n2) + carry carry = 1 if result >= 36 else 0 result %= 36 return _to_chr(result), carry len1, len2 = len(num1), len(num2) if len1 > len2: # 取巧:把两个数字长度对齐 num2 = '0' * (len1 - len2) + num2 elif len2 > len1: num1 = '0' * (len2 - len1) + num1 res = [] carry = 0 for i in xrange(max(len1, len2) - 1, -1, -1): tmp, carry = _add(num1[i], num2[i], carry) res.append(tmp) if carry: # 易错点:很容易遗漏 res.append('1') return ''.join(res[::-1]) # 易错点:需要翻转 ```--- ### 372. 青蛙跳石子 【Question】 在长度为m的地面格子上,每个格子里面有一些石子;有一只青蛙, 从格子开始处起跳,每次可以跳3到5个格子,求青蛙跳出格子最少需要踩几个石子;【Answer】
1. 二分查找 0 的位置; 2. 注意边界位置 0 的处理,在二分条件上需要做一些处理,如果只是找到 0 的位置然后遍历找到第一个和最后一个 0 的话, 复杂度为恶化; 3. 整体复杂度为 O(lg(n));--- ### 373. 给定一个字符串如下,请统计字符串中出现最多的字母和次数 【Question】 ```javascript function findMaxDuplicateChar(str) { let maxChar = '', maxValue = 1; // 补全代码..... return { maxChar, maxValue }; } const str = 'this is a fe test at toutiao on September'; findMaxDuplicateChar(str) // output: { maxChar:"t", maxValue:7 } ```【Answer】
简单DP dp[i]表示调到第i的位置最少需要踩几个石子; 于是dp[i]可由dp[i-3], dp[i-4], dp[i-5]得来;--- ### 374. 数组排序 【Question】【Answer】
```javascript function findMaxDuplicateChar(str) { if (str.length === 1) { return str; } const charObj = {}; let maxChar = '', maxValue = 1; for(let i = 0; i < str.length; i++) { if (str[i].trim() !== '') { if (!charObj[str.charAt(i)]) { charObj[str.charAt(i)] = 1; } else { charObj[str.charAt(i)] += 1; } } } for (const k in charObj) { if (charObj[k] >= maxValue) { maxChar = k; maxValue = charObj[k]; } } return { maxChar, maxValue }; } const str = 'this is a fe test at toutiao on September'; findMaxDuplicateChar(str) // output: { maxChar:"t", maxValue:7 } ```输入数组nums,要求输出升序排序后的结果。已知数组元素为非负整数,且当数组长度为n时,所有元素的值都小于n;
例:
[3, 2, 1, 3, 5, 0] -> [0, 1, 2, 3, 3, 5]
[0] -> [0]
--- ### 375. 二叉树转换成双向链表 【Question】 转换结果满足下面条件: 1. 不要新建一个链表空间,要原地做;直接使用 left 和 right 指针 2. 转换后的双向链表节点顺序要与中序遍历的结果一致 3. 二叉树最左边的节点作为双向链表的头节点 例如: 二叉树: 1 2 3 4 5 6 7 双向链表: 4 <-> 2 <-> 5 <-> 1 <-> 6 <-> 3 <-> 7【Answer】
O(n2)的排序算法:冒泡排序,插入排序,选择排序 等;
O(nlog(n))的排序算法:归并排序,快排 等;
O(N)的排序算法:空间换时间,利用计数实现,需要O(n)空间复杂度;
--- ### 376. 翻转单词 【Question】 给定一个字符串,逐个翻转字符串中的每个单词。【Answer】
二叉树遍历;递归;链表操作 原理上看,对根节点、根的左子树、根的右子树分别作处理: 1. 比如根节点1, 对于左子树,找到它的最右节点 5, 把 5 和 1 连接起来; 2. 对于 1 的右子树,找到它的最左节点 6,把 6 和 1 连接起来。 实现上,假设已经把左子树变做双向链表了,让指针一直向右走,就能找到最右节点和根连接; 右子树同理。 返回结果的时候,找到双向链表的最左节点就可以了。 ``` private Node convertToDoublyLinkedList(Node root) { if (root.getLeft() != null) { Node left = convertToDoublyLinkedList(root.getLeft()); while (left.getRight() != null) { left = left.getRight(); } left.setRight(root); root.setLeft(left); } if (root.getRight() != null) { Node right = convertToDoublyLinkedList(root.getRight()); while (right.getLeft() != null) { right = right.getLeft(); } right.setLeft(root); root.setRight(right); } return root; } ```--- ### 377. 两个数的和相加 【Question】【Answer】
根据情况,选择栈和队列即可 栈必须,队列可选给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
--- ### 378. 二叉树所有根到叶子路径组成的数字之和 【Question】 二叉树每个节点的value范围是1-9 例如: ``` 1 2 3 4 5 ``` 从根到叶子共3条:1->2->4, 1->2->5, 1->3 构成的数字为124,125,13,求和124 + 125 + 13 = 262即为所求【Answer】
--- ### 379. 三门问题/蒙蒂大厅难题 【Question】 你面前有三扇门,每个门后都有一个奖品,其中一个是一辆车,另外两个是山羊。 你首先挑选一扇门,姑且称之为A,其他两个门分别是B和C。 在打开你选择的门前,我先从B和C中选择一个没有车的门打开。 给你一个选择,你是坚持最开始的选择还是换到剩下未打开的门上? 如果我打开门的策略从随机选择,变成总是优先选择门B,只有在迫不得已的时候选择C。 问结果有变化么?【Answer】
``` python def tree_path_sum(root, val): if not root: return 0 val = val * 10 + root.val if not root.left and not root.right: return val return tree_path_sum(root.left, val) + tree_path_sum(root.right, val) ```--- ### 380. 海盗分金问题 【Question】 5个海盗要分100块金币,分配的协议是:按顺序一个一个来,轮到当前的海盗,他提出一个分配方案,如果包括他在内至少有50%的人同意,那么方案通过,否则这个海盗就会被喂鲨鱼,下一个海盗继续提出分配方案。 假设海盗都是纯理性而且冷血的,他们的第一原则是生存,第二原则就是拿到尽可能多的金子,第三原则是如果给的金币一样,他们倾向于选择有更少的海盗的分配方案。【Answer】
使用贝叶斯公式求解,p(a|b)*p(b) = p(a)*p(b|a) 三种假设,即假设车在门A,门B,门C后面。 D表示打开了门B但是车不在门后。 假设A:先验概率=1/3,似然度=1/2,后验概率=(1/3)*(1/2)/(1/2)=1/3 假设B:先验概率=1/3,似然度=0, 后验概率=0 假设C:先验概率=1/3,似然度=1,后验概率=2/3 变化之后概率相同。--- ### 381. 牛过河 【Question】【Answer】
思路: 5个人太多了,首先把问题简化。从1个人到5个人逐个分析。 * 1个人,显然自己全拿 * 2个人。1,2。2号海盗显然可以全部拿走,因为他自己的一票保证了50%。 * 3个人。1,2,3。首先,如果3号的方案没通过,那么1号将什么都得不到,而2号必定要除3号而后快。因此3号必须征得1号的支持,但又不能完全不给1号任何金币(第三原则)。因此分配方案是1 2 3 = 1 0 99。这样3号的方案能够得到1和3的支持。 * 4个人。1,2,3,4。首先,如果4号的方案没通过,那么2号将什么都得不到。因此4号只需要分配1 2 3 4 = 0 1 0 99 * 5个人。1,2,3,4,5。5号清楚的知道一旦他的方案不通过,1和3将什么都得不到,因而他只需要分配1 2 3 4 5 = 1 0 1 0 98,这样即足以保证。有一堆牛要过河,河的长度是l,河中间有n个石头,牛只能踩着石头过河,问去掉m个石头后(去掉这m个石头的方式是随机的)的每种情况牛能走的石头间距最小值中,最大的那一个是多少
--- ### 382. 求二叉树是否存在和值为N的路径 【Question】 从二叉树的根到叶子节点称为一条路径 路径上每个节点的value之和为路径和值 本题要求所有的路径中是否存在一条和值为N的。 follow-up:可以要求返回所有符合条件的路径【Answer】
二分最大值最小化问题
--- ### 383. special trim 【Question】 对输入的字符串,去除其中的字符'b'以及连续出现的'a'和'c' 例如: 'aacbd' -> 'ad' 'aabcd' -> 'ad' 'aaabbccc' -> '' 不允许使用类似string.replace函数。要求时间、空间复杂度尽量优化【Answer】
要求自己给出二叉树数类定义 本题很容易用递归解决,基本思路如下: ``` def path_exists(root, n): if not root: return False if not root.left and not root.right and root.value == n: return True return path_exists(root.left, n - root.value) or path_exists(root.right, n - root.value) ```--- ### 384. 约瑟夫问题 【Question】 假设有n个人,标号为1-n。 从第一个人开始计数,到第k个人则出列,随后从第k+1个人重新计数,到第k再出列。 直至剩下最后一个人。问最后剩下的人的编号?【Answer】
本题不好处理的是aaabccc,即:c和a相遇就要都去掉。 比较好的思路是用栈的思路解决。 ``` python def special_trim(s): if not s: return s res = [] for c in s: if c == 'b': continue if c == 'c' and res and res[-1] == 'a': res.pop() continue res.append(c) return ''.join(res) ```--- ### 385. 二叉搜索树中的第K小的元素 【Question】 给定二叉搜索树,求所有元素中第k小的。k<=节点总数【Answer】
考察链表使用。最简单直接的方法是使用循环链表。 具体思路是: 1. 构建循环链表,初始化数据; 2. 每到第k-1个结点,便p->next = p->next->next。 3. 循环结束条件为p = p->next,即只有一个结点,该结点所对应的值即为最后剩下的人。--- ### 386. 序列化和反序列化二叉树 【Question】 将一棵二叉树序列化为字符串,并能字符串反序列为一棵树。【Answer】
结合BST的性质,用递归很好解决,按照中序遍历即可 核心代码: ``` python def find_k_small(root, k): def _find(root): if not root: return -1 val = _find(root) if not k: return val k -= 1 if not k: return root.val return _find(root) _find(root) ```--- ### 387. 带过期和最大对象数限制的LRU-CACHE 【Question】 设计一个对象cache, 他支持下列两个基本操作: set(id, object), 根据id设置对象; get(id): 根据id得到一个对象; 同时它有下面几个性质: 1: x秒自动过期, 如果cache内的对象, x秒内没有被get或者set过, 则会自动过期; 2: 对象数限制, 该cache可以设置一个n, 表示cache最多能存储的对象数; 3: LRU置换, 当进行set操作时, 如果此时cache内对象数已经到达了n个, 则cache自动将最久未被使用过的那个对象剔除, 腾出空间放置新对象; 请你设计这样一个cache;【Answer】
本题解法应该有不少,大体思路是按照某种遍历的顺序记录下每个节点,叶子节点的空指针可以用特殊字符表示 例如用先根遍历解决: ``` python def serialize(root): def pre_order(node): if node: vals.append(str(node.val)) pre_order(node.left) pre_order(node.right) else: vals.append('#') vals = [] pre_order(root) return ' '.join(vals) def deserialize(data): def pre_order(): val = next(vals) if val == '#': return None node = TreeNode(int(val)) node.left = pre_order() node.right = pre_order() return node vals = iter(data.split()) return pre_order()--- ### 388. n sum 【Question】【Answer】
通过组合一些基本的数据结构, 来实现一些更高级的性质; 内部维护一个链表, list, 其元素为一个三元组(ID, timestamp, obj), 分别为对象ID, 上次被访问时间, 和对象内容; 在维护该list时, 需要保持一个性质, 越靠后的元素越新, 既timestamp越大; 内部再维护一个map, 该map表示一个ID到list节点的索引, 格式为map(ID, node); 对于get(id)操作: 1: 先在map中查找ID对应的list node; 2: 将node从list中取出, 即list.Remove(node); 3: 检查node.timestamp, 如果过期, 则返回null, 表示无数据, 并将ID从map中删除; 4: 如果未过期, 设置node.timestamp = now(), 并将node添加到list尾部, 即list.Append(node); 5: 返回node.obj; 对于set(id, obj)操作: 1: 同get(id)的1~3步操作, 删除对应的ID; 2: 如果此时空间满了, 既对象数为n, 则将list中表头的那个元素删除; 3: 更新list和map: node = new(ID, now(), obj), list.Append(node), map[ID] = node;输入一维数组array和n,找出和值为sum的n个元素即可,不用找出所有组合。
array = [2, 3, 1, 10, 4, 30] n = 2, sum = 31 result = find(array, n, sum) // result = [1, 30]
--- ### 389. 多叉树广度优先遍历查找 【Question】【Answer】
基础解法供参考
function find(arr, n, sum, shouldSort = true) { let sorted = arr; if (shouldSort) { sorted = arr.sort(); } const length = sorted.length; if (n === 2) { let front = 0; let back = length - 1; while(front < back) { const value = sorted[front] + sorted[back]; if (value === sum) { return [sorted[front], sorted[back]]; } else if (value > sum) { back -= 1; } else { front += 1; } } return null; } for(let i = 0; i < length; i += 1) { const val = sorted[i]; const result = find(sorted.slice([i + 1]), n - 1, sum - val, false); if (!result) { return null; } else { return [val, ...result]; } } }
要求:
- 自定义多叉树节点node结构(只需要定义节点结构即可,无需构建树)
- 按照广度优先查找符合要求的节点(没有符合要求的节点返回null),比如查找电话号码为 phone的用户信息,调用如下:
let node = wideTraversal(node,(e)=>e.phone===phone)
【Answer】
节点定义:
// 定义树节点 Node { ... // 属性 children:[], // 子节点 }
实现:
wideTraversal(node: Node, predict): Node | null { if (!node) return null let queue: Array
= []; queue.push(node) while (queue.length) { let cur = queue.shift() if (!cur) continue if (predict(cur)) return cur if (cur.children&& cur.children.length) { queue.push(...cur.children) } } return null }</code></pre> </pre> </details> --- ### 390. 给定无向图和它一个顶点,求无向图中到该顶点距离为K的所有点 【Question】 给定无向图和它一个顶点,求无向图中到该顶点距离为K的所有点,各个相邻顶点间距离为1
--- ### 391. 输出所有根节点到叶子节点的路径 【Question】 比如: 1 2 3 4 5 6 7 8 输出: 1,2,4 1,2,5 1,3,6 1,3,7,8【Answer】
方案:对图进行K步的BFS, 注意处理被重复走过的点;--- ### 392. 找出旋转有序数组中的最小值 【Question】 假设原数组为1,2,3,4,5那4,5,1,2,3就是旋转有序的数组 注:数组无重复元素【Answer】
二叉树遍历--- ### 393. 搜索二维矩阵 【Question】 给定二维m * n矩阵matrix,满足一定特性: 1. 每行从左到右递增 2. 每列从上到下递增 给定目标元素num,判断num是否在矩阵中存在 例如: ``` python matrix = [ [1, 3, 5, 10], [2, 4, 6, 11], [7, 9, 12, 20], ] ``` num = 4存在;num = 13不存在【Answer】
暴力可以O(n),但没用到旋转有序的特征。 本题可以用二分的思想降低时间复杂度 定义左右两个指针left、right指向头尾元素 如果a[left] < a[right]则没有移位,直接输出a[left]即可,反之二分搜索。 如果a[left] > a[mid] 则要搜索右半段,因为a[left]也大于a[right];反之搜索左半段。 核心代码 ``` python def find_min(arr): left, right = 0, len(arr) - 1 if arr[left] < arr[right]: return arr[left] while left != right - 1: mid = left + (right - left) / 2 if arr[left] < arr[mid]: left = mid else: right = mid return min(arr[left], arr[right])--- ### 394. 扑克牌的堆栈、队列操作 【Question】 我手中有一堆扑克牌, 但是观众不知道它的顺序。 * 第一步, 我从牌顶拿出一张牌, 放到桌子上。 * 第二步, 我从牌顶再拿一张牌, 放在手上牌的底部。 * 第三步, 重复第一步的操作, 直到我手中所有的牌都放到了桌子上。 最后, 观众可以看到桌子上牌的顺序是:13\12\11\10\9\8\7\6\5\4\3\2\1 请问, 我刚开始拿在手里的牌的顺序是什么?【Answer】
结合数组定义,观察例子,有两个特殊位置很特殊:左下角和右上角。 左下角的7往上所有的数变小,往右所有的数变大。 那么我们就可以将目标数字num和左下角比较,比目标小就往右搜,比目标大就往上搜。 如此往复可以判断num是否在matrix中。 ``` python def search(matrix, num): if not matrix: return False rows, cols = len(matrix), len(matrix[0]) if num < matrix[0][0] or num > matrix[rows - 1][cols - 1]: return False i, j = rows - 1, 0 # 左下角 while i >= 0 and j < cols: if matrix[i][j] < num: j += 1 elif matrix[i][j] > num: i -= 1 else: return True return False ```--- ### 395. 用js实现一个binarySearch二分查找 【Question】 定一个一个binarySearch的函数,传参能支持四个参数,分别是: >1. arr: 一个数组, >1. key: 一个需要查找的目标值, >1. low: 左边界 >1. high: 右边界 >1. 如果能知道则访问素组的位置,否则返回-1 ```javascript function binarySearch(){ // 补全代码 } var arr = [86,1,3,2,4,5,6,7,8,9,10,11,23,44]; var result = binary_search2(arr, 5, 0, 6); console.log(result); // 4 ```【Answer】
解法一: 这道题候选人容易出现折半的思路, 其实考虑的复杂了。 本质是将一个队列和栈做了两个操作 1. 出队、入栈 2. 出队、入队(队尾) 因为是看得到结果, 看不到初始顺序, 那么这个操作就是一个逆操作。 1. 出栈、入队 2. 出队(队尾)、入队(队首) 答案: 输入: 1,2,3,4,5,6,7,8,9,10,11,12,13, 输出: 1,12,2,8,3,11,4,9,5,13,6,10,7, 代码如下 ``` C++ 再改改 int doTheWork(std::deque * pQ, std::stack * pS) { if(NULL == pQ or NULL == pS) return -1; while(pS->size() > 0) { int val = pS->top(); pS->pop(); if (pQ->size() > 0) { int tmp = pQ->back(); pQ->pop_back(); pQ->push_front(tmp); pQ->push_front(val); } else { pQ->push_front(val); } } return 0; } ``` 解法二: 对手上牌按照a,b,c...进行编码,直接按顺序操作,输出结果和桌上实际结果对应,即为原手上牌的顺序。--- ### 396. 通配符匹配 【Question】 给定字符串s和模式串p,实现函数match(s, p),判断模式串p是否能完全匹配s 模式串中有两个特殊字符'?'和'*' '?'匹配任意1个字符;'* '匹配任意r个字符,包括空 例如: ``` python match('a', 'a') = True match('aa', 'a') = False match('a', '?') = True match('aa', '*') = True match('abc', '?*') = True ```【Answer】
二分法查找,也称折半查找,是一种在有序数组中查找特定元素的搜索算法。查找过程可以分为以下步骤: >1. 首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。 >1. 如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。 >1. 如果某一步数组为空,则表示找不到目标元素。 ```javascript // 递归实现的js代码 function binary_search2(arr, key, low, high) { if(low > high) { return -1; } var mid = parseInt((high + low) / 2); if(arr[mid] == key) { return mid; } else if(arr[mid] > key) { high =mid -1; return binary_search2(arr, key, low, high); } else if(arr[mid] < key) { low = mid +1; return binary_search2(arr, key, low, high); } } var arr = [86,1,3,2,4,5,6,7,8,9,10,11,23,44]; var arrSorted = arr.sort(function (a,b) { return a-b; }) var result = binary_search2(arrSorted, 5, 0, 10); console.log(result); // 4 ```--- ### 397. 原子计数问题 【Question】 给出一个分子式,比如: HMg2(H2O(N3Ag)2)3N2 计算这个分子式中每个原子出现了多少次,输出一个 map,比如上面的分子式中: map[string]int {"H": 7, "Mg": 2,"Ag": 6, ...} 分子式的规则: 1. 都以大写字母开头,后面跟 0 个或者 1 个小写字母,比如 Mg, H 2. 单个原子后面跟 0 个或者 1 个数字表示它出现的次数,比如 Mg2 表示 Mg 出现 2 次,数字范围 [2-1000] 3. 分子式中可能有括号,括号后面可能跟 0 个或者 1 个数字表示整个括号内的原子出现的次数,比如 (N3Ag)2 表示 N出现 6 次,Ag 出现 2 次 4. 括号可以嵌套 输入是合法的【Answer】
本题可以用DP算法来解答。 用dp[i, j]表示串s, p这两个串分别到i和j位置它们是否匹配。那么我们得到递推关系: 如果p[j] != '* ', dp[i, j] = dp[i-1, j-1] and (s[i] == p[j] || p[j] == '?') 如果p[j] == '* ', 那么dp[i][j] = dp[i - 1][j] || dp[i][j - 1] ``` python def match(s, p): m, n = len(s), len(p) dp = [[False] * (n + 1) for _ in range(m + 1)] dp[0][0] = True for j in range(1, n + 1): dp[0][j] = dp[0][j - 1] and p[j - 1] == '*' for i in range(1, m + 1): for j in range(1, n + 1): if p[j - 1] in [s[i - 1], '?']: dp[i][j] = dp[i-1][j-1] elif p[j - 1] == '*': dp[i][j] = dp[i - 1][j] or dp[i][j - 1] return dp[m][n] ``` 另外,本题也可以用贪心算法--- ### 398. 安卓解锁密码数 【Question】 安卓系统采用9个点连线构成图案密码。 要求连接以下9个点中**至少4个点**构成一条路径,从而得到1个密码。 1 2 3 4 5 6 7 8 9 任意点间均可连线,都有如下附加限制: 1. 在一个密码路径里每个点只能用1次; 2. 如果2号点没有被连,则1不能直接连3号点,即:1->3路径是非法的。类似情况还有1、7;2、8;1、9等等; 3. 如果2点已经连过了,则1可以连到3,3也可以连到1,即:2->1->3路径是合法的。 4. 1和6是可以直接连线的,因为二者之间没有点。 本问题求所有的密码数,也就是路径数,包含4个点、5个点。。。9个点的所有路径数。【Answer】
1. 最简单的方法是递归,碰到 ( 就开始递归 2. 担心爆栈就把递归改成非递归 3. 可以用正则表达式来做,这里不展开了,如果候选人懂的话让他解释--- ### 399. 矩阵中的最长递增路径 【Question】 给定m * n矩阵matrix,可以从任意位置开始,向上、向下、向左、向右移动,但要求下一个位置上的元素要大于当前元素。 找出最长的递增路径长度。【Answer】
本题目如果数据结构算法比较熟悉,会很快想到DFS。 难点是路径不能直达问题如何解决? 用visit记录某个点是否已经在路径中 ``` python visit = [[0, 0 ,0], [0, 0, 0], [0, 0, 0]] ``` 判断两个点能否连同,等价于判断两个点是否存在中间点问题,如果不存在可以直接连,如果存在要判断中间点是否已经访问过了。 中间点坐标为:|i1 - i2| / 2, |j1 - j2| / 2 另外,本题考虑到对称性会大大降低运算量 1、3、7、9点对称 2、4、6、8点对称--- ### 400. 升序数组求平方数不同的个数 【Question】 给定一个升序数组1,元素有重复,对每个元素算一下平方后得到新的数组2,问数组2中不相同的元素共有多少个?给出算法和空间复杂度,要求尽量优化。 举例: 数组1 [-13,-10,-9,-6,-5,-1,3,4,6,7,10,11,15,21,42] 平方后得到 数组2 [169,100,81,36,25,1,9,16,36,49,100,121,225,441,1764] 其中不相同的元素个数为13个。【Answer】
本题很容易想到dfs,但问题是每个点开始递归搜索,重复计算很多,结合DP提升效率。 用dp[i][j]表示从(i,j)开始的最长递增路径长度,当递归调用时,如果dp[i][j]不为0,直接返回dp[i][j]。 ``` python def long_increase_path_len(matrix): if not matrix: return 0 res = 1 m, n = len(martix), len(matrix[0]) dp = [[0] * n for _ in range(m)] for i in range(m): for j in range(n): res = max(res, dfs(matrix, dp, i, j, m, n)) return res def dfs(matrix, dp, i, j, m, n): if dp[i][j]: return dp[i][j] tmp_max = 1 dirs = [[0, -1], [-1, 0], [0, 1], [1, 0]]: for ni, nj in dirs: ii, jj = i + ni, j + nj if ii < 0 or ii >= m or jj < 0 or jj >= n or matrix[ii][jj] <= matrix[i][j]: continue tmp_max = max(tmp_max, 1 + dfs(matrix, dp, ii, jj, m, n) dp[i][j] = tmp_max return dp[i][j] ```---【Answer】
常规解法,按题目思路,先平方算好,再将结果插入hashset,最后输出hashset大小。 优化1,平方没必要算,其实就是绝对值。 优化2,注意到数组2其实是以0分隔的一个降序和一个升序数组。反序遍历降序数组,正序遍历升序数组,即可合并成一个升序数组,合并时做一下排重,最后输出合并后数组的元素个数。