数组中出现次数超过一半的数字
数组中出现次数超过一半的数字 - 数组中出现次数超过一半的数字 - 力扣(LeetCode)
方法一:哈希表统计法
思路:遍历数组,用map统计各个数字出现的次数
代码实现:
var majorityElement = function(nums) {
let map = new Map()
for(let i =0; i< nums.length;i++) {
if(map.has(nums[i])) {
map.set(nums[i], map.get(nums[i]) + 1)
}else {
map.set(nums[i], 1)
}
}
for(let [key, value] of map.entries() ) {
if(value > nums.length / 2) {
return key
}
}
};
- 时间复杂度:O(n)其中 n是字符串 的长度
- 空间复杂度: O(n),哈希表最多包含n−[n/2]个键值对,所以占用的空间为O(n)。
方法二:排序
思路:题目说数组中有一个数字出现的次数超过数组长度一半,若对数组进行排序后, 那么数组的中间位置必然就是那个数字。
var majorityElement = function(nums) {
nums.sort((a,b) => a-b)
return nums[nums.length >> 1]
};
- 时间复杂度:O(nlogn),数组排序的时间复杂度为O(nlogn)
- 空间复杂度: O(logn),如果使用语言自带的排序算法,需要使用O(logn)栈空间,如果自己编写堆排序,则只需要使用O(1)的额外空间。
方法三:摩尔投票法
摩尔投票法:核心理念为 票数正负抵消。此方法时间和空间复杂度分别为O(n)和 O(1),为最佳解法。
下面代码虽然通过了,但有些问题,若数组为[2,2,2,1,3,5,4,2]会出错返回4,目前还没想到解决方法!有小伙伴知道的可以在评论区留言啊!感谢!!!
var majorityElement = function(nums) {
let count = 0 //票数
let many
for(let i = 0; i < nums.length; i++) {
if(count ==0) {
many = nums[i]
}
count += (nums[i] == many ? 1 : -1)
}
return many
};
数组中只出现一次的两个数字
剑指 Offer 56 - I. 数组中数字出现的次数 - 力扣(LeetCode)
题目:一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
方法一:分组异或
思路与算法:假设数组 nums 中只出现一次的元素分别为 x1 和 x2。如果把 nums中的所有元素全部异或起来,得到结果 x,那么一定有 x = x1 ^ x2
且 x显然不会等于 0,因为如果 x=0,那么说明 x1 = x2,这样 x1 和 x2就不是只出现一次的数字了。因此可以使用位运算 x & -x 或者 x & (~x + 1) 取出 x 的二进制表示中最低位的那个1,设其为第 l 位,那么 x1 和 x2中的某一个数的二进制表示的第 l 位为0,另一个数的二进制表示的第 l 位为1。在这种情况下,x1^x2的二进制表示的第 l位才能为1.
这样一来,就可以把 nums中的所有元素分为两类,其中一类包含所有二进制表示的第 l 位为0 的数,另一类包含所有二进制表示的第 l 位为1的数。可以发现:
- 对于任意一组在数组nums中出现两次的元素,该元素的两次出现会被包含在同一类中;
- 对于任意一个在数组nums中只出现一次的元素,即 x1和 x2,它们会被包含在不同类中。
因此,如果将每一类元素全部异或起来,那么其中一类会得到 x1,另一类会得到x2。这样就找出了这两个只出现一次的元素。
var singleNumber = function(nums) {
let xor = 0
for(let num of nums) {
xor ^= num
}
let bin = xor & (-xor)
let type1 = 0, type2 = 0
for(let num of nums) {
if(num & bin) {
type1 ^= num
} else {
type2 ^= num
}
}
return [type1, type2]
};
- 时间复杂度:O(n)
- 空间复杂度:O(1)
方法二:哈希表,但空间复杂度超出要求
思路:可以使用一个哈希映射统计数组中每一个元素出现的次数。
在统计完成后,对哈希映射进行遍历,将所有只出现了一次的数放入答案中。
var singleNumber = function(nums) {
let map = new Map()
for(let num of nums) {
map.set(num, (map.get(num) || 0) + 1)
}
let res = []
for(let [num, count] of map) {
if(count == 1) {
res.push(num)
}
}
return res
};
- 时间复杂度:O(n)
- 空间复杂度:O(n),即为哈希映射需要使用的空间
某个元素仅出现一次,其余元素都恰出现 N次
剑指 Offer 56 - II. 数组中数字出现的次数 II - 力扣(LeetCode)
思路与算法:
为了方便叙述,称 只出现一次的元素 为 答案
由于数组中的元素都在 int(即32位整数)范围内,因此可以依次计算答案的每一个二进制位是 0 还是 1。
具体地,考虑答案的第 i 个二进制位(i 从0开始编号),它可能为 0 或 1。对于数组中非答案的元素,每个元素出现了 n 次,对应着第 i 个二进制位的 n 个1 或 n个0,无论哪种情况,它们的和都是 n 的倍数(即和为 0 或 n)。因此:
答案的第 i 个二进制位就是数组中所有元素的第 i 个二进制位之和除以 n 的余数。
这样一来,对于数组中的每一个元素x,使用位运算 ( x >> i) & 1得到 x 的第 i个二进制位,并将它们相加再对 n 取余,得到的结果一定为 0 或 1,即为答案的第 i个二进制位。
>>为右移,各二进制位右移若干,对于无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)
<<左移,各二进制位全左移若干,高位丢弃,低位补0
&为与运算,两个位都为 1 时,结果才为 1
|为或运算,两个位都为 0 时,才为 0
细节:
值得注意的是,如果使用的语言对 [有符号整数类型] 和 [无符号整数类型] 没有区分,那么可能会得到错误的答案。因为 [有符号整数类型] (即int类型)的第31个二进制位(即最高位)是补码意义下的符号位,对应着 -2^(31),而 [无符号整数类型] 由于没有符号,第31个二进制位对应着 2^(31)。因此在某些语言(如python)需要对最高位进行特殊判断。
var singleNumber = function(nums) {
let res = 0
for(let i = 0; i < 32; i++) {
let total = 0
for(let num of nums) {
total += (num >> i) & 1
}
if(total % 3 !== 0) {
res |= (1 << i)
}
}
return res
};
- 时间复杂度:O(nlogC),且其中n为数组的长度,C是元素的数据范围,在本题中log C = log 2^32 = 32,也就是需要遍历第0 ~31个二进制位
- 空间复杂度:O(1)
方法二:哈希表
思路与算法:用哈希表映射统计数组中每个元素的出现次数。对于哈希映射中的每个键值对,键表示一个元素,值表示其出现的次数。
在统计完成后,遍历哈希映射即可找出只出现一次的元素。
var singleNumber = function(nums) {
let map = new Map()
nums.forEach(item => {
map.set(item, (map.get(item) || 0) + 1)
})
let res = 0
for(let [num, count] of map.entries()) {
if( count == 1) {
res = num
break
}
}
return res
};
- 时间复杂度:O(n)
- 空间复杂度:O(n),哈希映射中包含最多 [total / n] + 1个元素,即需要的空间为O(n)
某个元素仅出现一次,其余元素都恰出现2次的另外解法
方法:位运算
function singleNumber(nums) {
let res = 0
for(let i of nums) {
res ^= i
}
return res
}
异或运算(^),在两个二进制位不同时返回1,相同时返回0,联想消消乐。
- 时间复杂度:O(n)
- 空间复杂度:O(1)
连续子数组的最大和
连续子数组的最大和 - 连续子数组的最大和 - 力扣(LeetCode)
思路:记录一个当前连续子数组最大值 max 默认值为数组第一项
1.从数组第二个数开始,若 sum<0 则当前的sum不再对后面的累加有贡献,sum = 当前数
2.若 sum>0 则sum = sum + 当前数
3.比较 sum 和 max ,max = 两者最大值
var maxSubArray = function(nums) {
let sum = nums[0]
let max = nums[0]
for(let i = 1; i < nums.length; i++) {
if(sum < 0) {
sum = nums[i]
} else {
sum += nums[i]
}
if(sum > max) {
max = sum
}
}
return max
};
- 时间复杂度:O(n)
- 空间复杂度:O(1)
方法二:动态规划
空间复杂度降低:由于dp[i] 只与dp[i−1] 和nums[i] 有关系,因此可以将原数组nums 用作dp 列表,即直接在nums 上修改即可。由于省去dp 列表使用的额外空间,因此空间复杂度从O(N) 降至O(1) 。
var maxSubArray = function(nums) {
for(let i = 1; i <nums.length; i++) {
nums[i] += Math.max(0, nums[i-1])
}
return Math.max.apply(null, nums)
};
- 时间复杂度:O(N),线性遍历数组nums 即可获得结果,使用O(N) 时间。
- 空间复杂度:O(1),使用常数大小的额外空间。
扑克牌顺子
剑指 Offer 61. 扑克牌中的顺子 - 力扣(LeetCode)
题目:从若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
思路: 面试题61. 扑克牌中的顺子(集合 Set / 排序,清晰图解) - 扑克牌中的顺子 - 力扣(LeetCode)
根据题意,此 5 张牌是顺子的 充分条件 如下:
- 除大小王外,所有牌 无重复 ;
- 设此 5 张牌中最大的牌为 max ,最小的牌为** min (大小王除外)**,则需满足:max−min<5
因而,可将问题转化为:此 5 张牌是否满足以上两个条件?
方法一:set + 遍历
- 遍历五张牌,遇到大小王(即0)直接跳过
- 判别重复: 利用 Set 实现遍历判重
- 获取最大 / 最小的牌
var isStraight = function(nums) {
let set = new Set()
let max = 0, min = 14
for(let num of nums) {
//跳过大小王
if(num == 0) continue
//有重复直接返回false
if(set.has(num)) {
return false
}
set.add(num)
max =Math.max(num, max)
min = Math.min(num, min)
}
return max - min < 5
};
- 时间复杂度:O(n) = O(5) = O(1)
- 空间复杂度:O(n) = O(5) = O(1),用于判重的辅助Set使用的额外空间
方法二:排序 + 遍历
- 先对数组执行排序。
- 判别重复: 排序数组中的相同元素位置相邻,因此可通过遍历数组,nums[i]=nums[i+1] 是否成立来判重。
- 获取最大 / 最小的牌: 排序后,数组末位元素nums[4] 为最大牌;元素nums[count] 为最小牌,其中count 为大小王的数量。
var isStraight = function(nums) {
nums.sort((a,b) => a- b)
let count = 0
for(let i =0; i < nums.length - 1; i++) {
if(nums[i] === 0) {
count++
}else if(nums[i] == nums[i+1]) {
return false
}
}
return nums[4] - nums[count] < 5
};
时间复杂度:O(NlogN)=O(5log5)=O(1),其中nums 长度,本题中N=5 ;数组排序使用O(NlogN) 时间。
空间复杂度:O(1),变量count 使用O(1) 大小的额外空间。