力扣算法
常用方法:
Set
set的属性
- size():返回集合所包含的元素数量
set的方法
- add(value):向集合中添加一个新的项
- delete(value):删除集合中某个元素
- has(value):判断集合中是否存在value,返回true或false
- keys()(values()):返回包含所有键的数组
// 基本用法
let set = new Set();
set.add('a');
set.add('b');
set.add('c');
console.log(set.keys()); // SetIterator {"a", "b", "c"}
console.log(set.values()); // SetIterator {"a", "b", "c"}
// 拓展用法
x const s = new Set();
[2,3,5,4,5,2,2].forEach(x => s.add(x))
for(let i in s){
console.log(i) // 2 3 5 4
}
// 数组的去重
let arr = [1, 2, 1, 4, 5, 3];
let diffArr = [...new Set(arr)]
console.log(diffArr) // [1, 2, 4, 5, 3]
Map
let m = new Map()
m.set('Jay','Jay Chou')
m.set(true,'真的')
console.log(m.has('Jay')) // true
console.log(m.has('Jay Chou')) // false
console.log(m.size) // 2
console.log(m.keys()) // MapIterator{"Jay",true}
console.log(m.values()) // MapIterator{"Jay Chou","真的"}
console.log(m.get('Jay')) // Jay Chou
// 遍历
for(let key of m.keys()){
console.log(key) // Jay true
}
常用算法:
二分查找
要求
线性表中的记录必须按关键码有序,并且必须采用顺序存储
原理
-
设置查找区间:low = 0 ,high = arr.length - 1
-
若查找区间不存在,则查找失败,否则转步骤三
-
取中间位:mid = (high + low) / 2,比较target 与 arr[mid]的大小
3.1 若target < arr[mid],则high = mid + 1,查找在左半区进行,转步骤2
3.2 若target > arr[mid],则low = mid + 1,查找在右半区进行,转步骤2
3.3 若target = arr[mid],则查找成功,返回mid
性能分析
最好时间复杂度:O(1),target = arr[mid]
最坏时间复杂度:O(logn),查找不到目标元素
循环实现
function BinarySearch(let arr,let target){
let low = 0
let high = arr.length
while(low < high){
let mid = ~~((high + low) / 2)
if(target < arr[mid]){
high = mid - 1
}else if(target > arr[mid]){
low = mid + 1
}else{
return mid
}
}
return -1
}
递归实现
let longestPrefix = function (str) {
if (str.length === 0) {
return ""
}
// 找出长度最小的那个字符串
let minLength = str[0].length;
for (let i = 0; i < str.length; i++) {
minLength = Math.min(minLength, str[i].length)
}
// 定义了二分查找的两个端点
let low = 0, high = minLength;
while (low < high) {
let mid = ~~((high - low + 1) / 2) + low
// 使用二分查找 查找公共前缀的位置
if (isCommonPrefix(str, mid)) {
low = mid
} else {
high = mid - 1
}
}
return str[0].substring(0, low)
}
function isCommonPrefix(strs, length) {
let str0 = strs[0].substring(0, length)
let count = strs.length
for (let i = 1; i < count; i++) {
let str = strs[i]
for (let j = 0; j < length; j++) {
if (str0.charAt(j) !== str.charAt(j)) {
return false
}
}
}
return true;
}
简单:
1.两数之和
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
解法一:暴力枚举
- 枚举在数组中所有不同的两个下标的组合
- 逐个检查它们所对应的数的和是否等于
target
var twoSum = function(nums, target) {
let arr = []
let length = nums.length
for(let i = 0 ; i < length ; i++){
for(let j = i + 1; j < length ; j++){
if(nums[i] + nums[j] === target){
arr.push(i)
arr.push(j)
}
}
}
return arr
}
解法二:查找表法
- 在遍历的同时,记录一些信息,以省去一层循环,这是“以空间换时间”的想法
- 需要记录已经遍历过的数值和它对应的下标,可以借助查找表实现
- 查找表有两个常用的实现:
- 哈希表
- 平衡二叉搜索树
哈希表:
- 算出当前数和
target
的差 - 检查哈希表中是否存在该差,若存在,返回下标
- 若不存在,当前数字为key,索引为value存入哈希表
var twoSum = function(nums, target) {
// 创建一个map
let map = new Map()
let length = nums.length
for(let i = 0 ; i < length ; i++){
// 如果map中存在该target-nums[i],返回两个数的下标
if(map.has(target - nums[i])){
return [map.get(target - nums[i]),i]
}else{
// 否则存入nums[i]和对应的下标
map.set(nums[i],i)
}
}
return []
}
9.回文数
给你一个整数 x
,如果 x
是一个回文整数,返回 true
;否则,返回 false
。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
- 例如,
121
是回文,而123
不是。
示例 1:
输入:x = 121
输出:true
示例 2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。
解法一:将整数转为字符串
- 由于给定的参数x为数值类型,要先将x转换为字符串类型
- 将字符串的前半部分和后半部分分别提取出来进行比较
- 若相同,返回true,否则返回false
let isPalindrome = function(x) {
let str = x + ''
// 前半部分向下取整
let prefixStr = str.slice(0,Math.floor(x/2))
// 后半部分向上取整
let suffixStr = str.slice(Math.ceil(x/2),str.length)
// 将后半部分利用reverse方法反转
let reverseStr = suffixStr.split('').reverse().join('')
return reverseStr === prefixStr
}
解法二:每次求余得到反转的数
- 负数、10的倍数都不可能是回文数
- 利用整数反转来获取反转后的数,若和参数完全一致,则是回文数
let isPalindrome = function(x) {
if (x < 0 || (x % 10 === 0) && x !== 0) {
return false
}
let revertNum = 0
let index = x
while(index != 0){
revertNum = revertNum * 10 + index % 10
index = ~~(index / 10)
}
return revertNum === x
}
算法优化
在整数反转到一半时,即可判断后半部分和前半部分是否相等
- 若x的长度为偶数,直接比较
- 若x的长度为奇数,中间的数字并不影响最终的结果,只需再次
/10
即可
let isPalindrome = function(x) {
if (x < 0 || (x % 10 === 0 && x !== 0)) {
return false
}
let revertNum = 0
while(x > revertNum){
revertNum = revertNum * 10 + x % 10
x = ~~(x / 10)
}
return revertNum === x || ~~(revertNum / 10) === x
}
13.罗马数字转整数
罗马数字包含以下七种字符: I
, V
, X
, L
,C
,D
和 M
。
例如, 罗马数字 2
写做 II
,即为两个并列的 1 。12
写做 XII
,即为 X
+ II
。 27
写做 XXVII
, 即为 XX
+ V
+ II
。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2
写做 II
,即为两个并列的 1 。12
写做 XII
,即为 X
+ II
。 27
写做 XXVII
, 即为 XX
+ V
+ II
。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII
,而是 IV
。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX
。这个特殊的规则只适用于以下六种情况:
I
可以放在V
(5) 和X
(10) 的左边,来表示 4 和 9。X
可以放在L
(50) 和C
(100) 的左边,来表示 40 和 90。C
可以放在D
(500) 和M
(1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。
示例 1:
输入: s = "III"
输出: 3
示例 2:
输入: s = "IV"
输出: 4
示例 3:
输入: s = "IX"
输出: 9
示例 4:
输入: s = "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: s = "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
思路
通常情况下,罗马数字小的数放在大的数的后面
如果输入的字符串是上述格式,则对每个字符串进行累加即可
如果小的数放在大的数的前面,则说明该数类似IV、IX
,只需将V - I
即后面的数减去前面的数即可
let romanToInt = function (s) {
let map = new Map()
map.set('I', 1)
map.set('V', 5)
map.set('X', 10)
map.set('L', 50)
map.set('C', 100)
map.set('D', 500)
map.set('M', 1000)
let sum = 0
let value = 0
let len = s.length
for(let i = 0 ; i < len ; i++){
// 得到每一个字符串对应的值
value = map.get(s[i])
// 若小的数在前面
if(value < map.get(s[i+1])){
sum = sum + map.get(s[i+1]) - value
// i后面的数已经计算过,也为了防止数组溢出
i++
}else{
sum += value
}
}
}
14.最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
示例 1:
输入:strs = ["flower","flow","flight"]
输出:"fl"
示例 2:
输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。
解法一:横向扫描
- 当字符串数组长度为0时,则前缀为空,直接返回
- 令最长公共前缀ans为第一个字符串,进行初始化
- 遍历后面的字符串,依次与ans进行比较,当字符不相等时,直接跳出循环
- 如果查找过程中出现了ans为空,则公共前缀不存在直接返回
/**
* @param {string[]} strs
* @return {string}
*/
let longestCommonPrefix = function (str) {
// 当数组为空时,直接返回
if(str.length === 0){
return ""
}
let ans = str[0]
// 遍历str数组
for(let i = 0 ; i < str.length ; i++){
let j = 0;
// 遍历数组元素的每一个字符,当字符串不相等,跳出循环
for(; j < str.length && j < ans.length; j++){
if(ans.chart[j] !== str[i][j]){
break;
}
}
ans = ans.subString(0,j)
// 当ans为空时,直接返回
if(ans === ""){
return ""
}
}
return ans
}
解法二:二分查找
- 最长公共前缀的长度不会超过字符串数组中的最短字符串的长度
- 用minLength表示字符串数组中最短字符串的长度
- 可以在[0,minLength]的范围内通过二分查找得到最长公共前缀的长度
- 每次查找取查找范围的中间值mid,判断每个字符串的长度为mid的前缀是否相同
- 如果相同,则最长公共前缀的长度一定大于或等于mid
- 如果不相同,则最长公共前缀的长度一定小于mid
- 通过上述方式将查找范围缩小一般,直到得到最长公共前缀的长度
let longestCommonPrefix = function (str) {
if(str.length === 0){
return ""
}
int minLength =
}