掘金团队号上线,助你 Offer 临门! 点击 查看详情
多数元素(题号169)
题目
给定一个大小为 n
的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:[3,2,3]
输出:3
示例 2:
输入:[2,2,1,1,1,2,2]
输出:2
进阶:
尝试设计时间复杂度为 O(n)
、空间复杂度为 O(1)
的算法解决此问题。
链接
解释
这题重点有两个,一个是n / 2
,一个是必然会出现。
有了这两个限定条件解决方案就很多 了,比较不好理解的是抵消法,当然了这种方法也是最优解。
自己的答案
var majorityElement = function(nums) {
var obj = new Map()
var res = 0
var max = nums.length /2
while (nums.length > 0) {
var item = nums[0]
obj.set(item, obj.has(item) ? obj.get(item) + 1 : 1)
if (obj.get(item) > max) {
res = item
nums.length = 0
}
nums.shift()
}
return res
};
这是第一次给出的答案,当时还挺开心的,毕竟是做出来了,后来才发现是粑粑一坨。
这块一个很明显的问题就是在满足条件时没有终止循环,完全没有意义,所以第二次写这题时改进了一波👇:
var majorityElement = function(nums) {
var obj = {}
res = 0
len = nums.length
max = ~~(len / 2)
for (let i = 0; i < len; i++) {
var num = nums[i]
obj[num] = obj[num] ? ++obj[num] : 1
if (obj[num] > max) {
res = num
break
}
}
return res
};
是不是清爽了不少,并且在满足条件时直接终止,这里其实用Map
的话性能会更好些。
更好的方法(排序)
/**
* @param {number[]} nums
* @return {number}
*/
var majorityElement = function(nums) {
nums.sort()
return nums[~~(nums.length / 2)]
};
这没啥可说的,数组排序后,多数元素必然出现在数组的中间,写起来简单,但排序的时间复杂度是O(NlogN),并不是最优解。
需要注意的是sort
方法,该方法如果不传任何参数,默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的。
也就是并非是从小到大的顺序,虽然它有时候看上去挺像的。
不过这里没有什么关系,顺序的不同并不会影响元素的数量,用就完事了。
更好的方法(栈)
var majorityElement = function(nums) {
let stack = []
for(let n of nums){
let m = stack.length
if(stack[m - 1] === n || !m){
stack.push(n)
} else {
stack.pop()
}
}
return stack[0]
};
该方法利用了数量优势,因为多数元素的数量必然是最多的,然后和其他元素相抵消。
就一个个往栈里推,如果最后一个元素和当前元素不同,则去掉栈顶的元素,那么占有绝对数量优势的多数元素则会取得最后的胜利。
更好的方法3(抵消)
var majorityElement = function(nums) {
let x = 0
let m = 0
for(let n of nums){
if(m === 0) x = n
m += x === n ? 1 : -1
}
return x
};
这就是直接抵消了,和栈相比多了一个变量,但是少了对栈的操作,就酱。
注意,栈和抵消方法其实都需要遍历完整个数组,但是笔者的答案有些时候并不需要遍历完整个数组。
买卖股票的最佳时机 II(题号122)
题目
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 3 * 10 ^ 4
0 <= prices[i] <= 10 ^ 4
链接
解释
这题其实很简单,不用想的太复杂,什么后一个数如果比前一个数大,再判断下一个是是不是比之前大数更大等等,这样的逻辑反而更加复杂。
在后者比前者大时,直接取两者的差累计给res
即可,但这里需要注意一下性能问题,笔者就发现了一些微小的差异。
自己的答案(循环)
var maxProfit = function(prices) {
var max = 0;
var size = prices.length;
for (let i = 0; i < size - 1; i++)
if (prices[i] < prices[i + 1])
max += prices[i + 1] - prices[i];
return max;
};
当时直接用的for
循环,很简单,也很有效,这还是一年前的答案。
自己的答案(reduce)
var maxProfit = function(prices) {
return prices.reduce((total, val, i) => prices[i] < prices[i + 1] ? total + prices[i + 1] - prices[i] : total, 0)
};
一个简单的reduce
。注意,这里通过判断下一个数是否比当前数大来判断是否需要进行数据累计,这个判断其实是比较耗性能的,下面的写法确实比这种写法的性能好一些。
更好的答案(Math.max)
var maxProfit = function(prices) {
return prices.reduce((total, val, i) => total += i ? Math.max(0, prices[i] - prices[i - 1]) : total, 0)
};
同样,也是一个reduce
。但注意内部实现细节,这里通过Math.max
来进行取值,所以省去了判断下一个元素是否大于当前元素的判断,确实省掉了一部分判断,这一部分判断大概节省了20毫秒左右的时间,却让执行用时从10%一下跳到了70%以上,最好的甚至到了90%以上,还是细节决定成败啊。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇
有兴趣的也可以看看我的个人主页👇