时间复杂度:O(n) 空间复杂度O(n)
var isAnagram = function(s, t) {
if(s.length!==t.length) return false //如果长度不等,那么一定不满足要求
//分别对字符串s t进行遍历,然后分别使用map对象进行保存
let map1 = new Map()
let map2 = new Map()
//字符串是可迭代的,可以使用for of进行遍历
for(let item of s){
map1.set(item,map1.get(item)+1||1)
}
for(let item of t){
map2.set(item,map2.get(item)+1||1)
}
//第三个循环 使用for of从map1中拿key,然后对map2中的进行比较
for(let [key,val] of map1){
//注意,当map1中的所有的属性所对应的值都和map2中的相等的时候,不存在map2中比map1多一个属性的情况
if(!map2.get(key)||map2.get(key)!==val){
return false
}
}
return true
};
var isAnagram = function(s, t) {
//使用一个map对象
let map = new Map()
for(let val of s){
map.set(val,map.get(val)+1||1)
}
//然后遍历t,从map中进行相减
for(let key of t){
//map中没有这个键
if(!map.has(key)) return false
map.set(key,map.get(key)-1)
if(map.get(key)==0) map.delete(key)
}
if(map.size==0) return true
return false
};
① sort() 对字母进行排序成a b c这种排序
② map.values() 返回一个只包含val的迭代对象
var groupAnagrams = function(strs) {
//设字符串个数是n 最长的字符串长度是k 那么sort进行排序klogk 时间复杂度O(nklogk)
//空间复杂度 map O(nk) 进行排序的过程中 O(nlogk) =>O(nk)
//排序法
let map = new Map()
for(let s of strs){
let arr = s.split("")//字符串转换成数组
arr.sort() //对字母进行排序,会排序成a b c 。。。。
let newStr = arr.join("")
map.has(newStr)?map.get(newStr).push(s):map.set(newStr,[s])
}
return Array.from(map.values())//返回map的迭代数组
};
var groupAnagrams = function(strs) {
//计数法
let map = {}
for(let str of strs){
let arr = new Array(26).fill(0) //创建一个有26个位置的数组
for(let i=0;i<str.length;i++){
//charCodeAt()获取字符的unicode 由于a b c。。这些小写字母的unicode都是相邻的所以这样就可以取得 0 1 2 这样的下标
let index = str[i].charCodeAt() - 'a'.charCodeAt()
arr[index]++
}
map[arr]?map[arr].push(str):map[arr] = [str]
}
return Object.values(map)
};
var intersection = function(nums1, nums2) {
//时空复杂度都是O(n)
//使用一个map对象来保存num1中的每一个键,然后使用arr保存两者间的相同的
let map = new Map()
let arr = []
for(let val of nums1){
map.set(val,map.get(val)+1||1)
}
for(let val of nums2){
if(map.has(val)){
arr.push(val)
}
}
//set进行去重,然后再将对象转换为数组
return Array.from(new Set(arr))
};
时空都是O(n)
new Set()毫无疑问是O(n)
Array.from()也是O(n),原因见第三条:
var intersection = function(nums1, nums2) {
let a = new Set(nums1)
let b = new Set(nums2)
return [...a].filter(item =>b.has(item))
};
时空都是O(n)
var intersect = function(nums1, nums2) {
let map = new Map()
let arr = []
for(let item of nums1){
map.set(item,map.get(item)+1||1)
}
for(let item of nums2){
if(map.has(item)){
arr.push(item)
map.set(item,map.get(item)-1)
if(map.get(item)==0) map.delete(item)
}
}
return arr
};
var intersect = function(nums1, nums2) {
//将两个数组排序之后,从头开始比较两个数组元素
if(nums1.length==0||nums2.length==0) return []
//进行排序
nums1.sort((a,b)=>a-b)
nums2.sort((a,b)=>a-b)
let r1 = 0, r2 = 0 //分别指向数组的首部
let arr = []
while(r1<nums1.length&&r2<nums2.length){
if(nums1[r1]==nums2[r2]){
arr.push(nums1[r1])
r1++
r2++
}else if(nums1[r1]<nums2[r2]){
r1++
}else{
r2++
}
}
return arr
};
并不会出现无限不循环的情况
var isHappy = function(n) {
//首先将n转换成字符串,拿到每一位;计算的时候再将其转换成int
if(n==1) return true
let map = new Map() //如果一个数重复出现,那么说明不满足,直接跳出
while(n!==1){
if(map.has(n)){
return false
}
map.set(n,true)
let temp = 0 //临时记录
let str = String(n)
for(let item of str){
temp+=parseInt(item)*parseInt(item)
}
if(temp==1) return true
n = temp
}
};
思路:通过上面的分析,我们知道,如果n不满足条件,那么一定会进入循环,相当于是一个圈,所以这里使用slow fast
参考
const getNext = function(n){
return String(n).split('').map(item =>item*item).reduce((pre,cur)=>pre+cur,0)
}
var isHappy = function(n) {
//使用快慢指针,slow走一个 fast走两步
let slow = n
let fast = getNext(n)
while(fast!==1&&fast!==slow){
slow = getNext(slow)
fast = getNext(getNext(fast))
}
return fast === 1
};
var twoSum = function(nums, target) {
//使用map对象
let map = new Map()
for(let i=0;i<nums.length;i++){
if(map.has(target - nums[i])){
return [i,map.get(target - nums[i])]
}
map.set(nums[i],i)
}
};
var twoSum = function(nums, target) {
//暴力解法
for(let i=0;i<nums.length;i++){
let cur = nums[i]
for(let j=i+1;j<nums.length;j++){
if(nums[j]==target - cur){
return [i,j]
}
}
}
};
var fourSumCount = function(nums1, nums2, nums3, nums4) {
//使用两个map对象,一个放置nums1与nums2的遍历结果,另外一个放置nums3与nums4的遍历结果
let map1 = new Map()
let map2 = new Map()
for(let item of nums1){
for(let item2 of nums2){
let target = item + item2
map1.set(target,map1.get(target)+1||1)
}
}
for(let item of nums3){
for(let item2 of nums4){
let target = item + item2
map2.set(target,map2.get(target)+1||1)
}
}
//然后遍历map1和map2
let sum = 0
for(let [key,val] of map1){
let target = 0 - key
if(map2.has(target)){
sum+=map1.get(key) * map2.get(target)
}
}
return sum
};
var fourSumCount = function(nums1, nums2, nums3, nums4) {
//使用两个map对象,一个放置nums1与nums2的遍历结果,另外一个放置nums3与nums4的遍历结果
let map1 = new Map()
let sum = 0
for(let item of nums1){
for(let item2 of nums2){
let target = item + item2
map1.set(target,map1.get(target)+1||1)
}
}
for(let item of nums3){
for(let item2 of nums4){
let target = 0 - (item + item2)
if(map1.has(target)){
sum+=map1.get(target)
}
}
}
return sum
};
时间O(n^2) 空间O(1)
var threeSum = function(nums) {
//使用双指针
let len = nums.length
let arr = []
if(len<3) return arr
nums.sort((a,b)=>a-b)
for(let i=0;i<len;i++){
if(nums[i]>0) break //那么i后面都是大于0的,就不用遍历了
if(i>0&&nums[i]==nums[i-1]) continue //为啥要调过呢?如果nums[i-1]<0,也正好再三元组中,那么这个三元组和可能是包含nums[i]的,如果不包含的话,那么如果从nums[i]开始,后面两个值都一定是和nums[i-1]的三元组重合了
let left = i + 1
let right = len - 1
while(left<right){
let sum = nums[i] + nums[left] + nums[right]
if(sum==0){
arr.push([nums[i],nums[left],nums[right]])
while(nums[left]==nums[left+1]) left++ //已经有两个值相同,那么另外一个也是相同的
while(nums[right]==nums[right-1]) right--
//左右两侧指针要同时移动
left++
right--
}else if(sum<0){
left++
}else{
right--
}
}
}
return arr
};
var fourSum = function(nums, target) {
//双指针
let len = nums.length
let arr = []
if(len<4) return arr
nums.sort((a,b)=>a-b)
for(let i=0;i<len;i++){
if(nums[i]>target&&nums[i]>=0) break//这里不要有等号,剩下的交给后面的
if(i>0&&nums[i]==nums[i-1]) continue
for(let j=i+1;j<len;j++){ //if nums[i]+nums[j]<0,然后再去加上一个负数,那么还是有机会等于target的
if(nums[i]+nums[j]>target&&nums[i]+nums[j]>=0) break//跳出本层循环
if(j>i+1&&nums[j]==nums[j-1]) continue //直接进入下一层循环
let left = j+1
let right = len - 1
while(left<right){
let temp = nums[i] + nums[j] + nums[left] + nums[right]
if(temp==target){
arr.push([nums[i],nums[j],nums[left],nums[right]])
while(nums[left]==nums[left+1]) left++
while(nums[right]==nums[right - 1]) right--
left++
right--
}else if(temp<target){
left++
}else{
right--
}
}
}
}
return arr
};
时间复杂度是O(nlogn) 不超时,但是不满足题意
var longestConsecutive = function(nums) {
//方法一:排序
if(nums.length==0) return 0
//首先进行排序 排序时间复杂度是O(nlogn)
nums.sort((a,b)=>a-b)
let max = 1
let count = 1
//对nums进行遍历
for(let i=0;i<nums.length;i++){
let cur = i,next = i+1
//如果出现重复的值,那么直接跳过
if(nums[cur]==nums[next]) continue //进入下一层循环 这里要注意:这些数字在原数组中不一定是要相邻的
if(nums[cur] + 1 == nums[next]){
count++
max = Math.max(max,count)
}else{
count = 1
}
}
return max
};
var longestConsecutive = function(nums) {
//方法二:使用set
if(nums.length==0) return 0
let set = new Set(nums) //此时,set中已经有了nums中所有的不重复的的值
let max = 1
for(let i=0;i<nums.length;i++){
//我们只去找可能是某一条序列的最左侧的
if(!set.has(nums[i] - 1)){ //这种情况只能说明左侧没有了
let cur = nums[i]
let count = 1
while(set.has(cur + 1)){
//往左侧找
count++
cur++
}
max = Math.max(max,count)
}
}
return max
};
var longestConsecutive = function(nums) {
//方法三:使用map map中的键保存num 值保存当前已知的序列的长度
if(nums.length==0) return 0
let map = new Map()
let max = 1
for(let i=0;i<nums.length;i++){
let cur = nums[i]
if(!map.has(cur)){ //不要有重复的判断
let leftLen = map.get(cur - 1)||0
let rightLen = map.get(cur + 1) || 0
map.set(cur,leftLen+1+rightLen)
//只需要更新两端即可
map.set(cur - leftLen,leftLen+1+rightLen)
map.set(cur + rightLen,leftLen+1+rightLen)
max = Math.max(max,leftLen+1+rightLen)
}
}
return max
};
背包问题总结,是动态规划的一个特例,记得要看
var wordBreak = function(s, wordDict) {
//dp[i] = true,也即是 字符串 0 到 i - 1 的部分是与wordDict匹配的
let dp = new Array(s.length+1).fill(false)
dp[0] = true
for(let i=0;i<=s.length;i++){//注意这里是<= 当i指向s后面的空结点的时候,最后一轮会将true放到下标为 s.length 的位置
for(let j=0;j<wordDict.length;j++){
if(i>=wordDict[j].length){
//开始进行匹配 当前s的指针是i
//刚开始的时候,i肯定是小于wordDicet[j].length 那么i这个指针就会一直往后走,直到满足wordDict中的第一个的长度 那么就开始进行判断 如果转了一圈没有满足条件的 那么dp中每一项都是false
if(s.slice(i-wordDict[j].length,i)==wordDict[j]&&dp[i-wordDict[j].length]){
dp[i] = true
}
}
}
}
return dp[s.length]
};
var wordBreak = function(s, wordDict) {
//dfs
let len = s.length
//arr用来记录从某个索引的后面是不是判断了
let arr = new Array(len)
let set = new Set(wordDict)
const helper = start =>{
if(start==len) return true
if(arr[start]!==undefined) return arr[start]
for(let i=start;i<=len;i++){
let prefix = s.slice(start,i)
if(set.has(prefix)&&helper(i)){ //注意这里i<=len 这样当i越界的时候才能返回true
//这时候从start往后都满足条件
arr[start] = true
return true
}
}
//for循环结束之后竟然没有返回出去 也即是从start开始没有满足条件的
arr[start] = false
return false
}
return helper(0)
};
这道题其实可以用动态规划算法做,但我们可以先思考一下使用广度优先遍历算法来解决这个问题会出现什么问题。
假设字符串 s 的长度为 n,字典中单词的个数为 m,我们可以将 s 看作一个图,其中每个节点代表字符串中以第 i 个字符为结尾的子串,例如字符串 s = “leetcode”,以第 4 个字符为结尾的子串为 “code”。
广度优先遍历算法在遍历图的过程中,每次的扩展都依赖于上一次扩展的结果,这就带来了复杂度问题。如果我们不加限制地一直进行 BFS,可能会导致节点的数量指数级增加,计算时间也会随之指数级增加,效率非常低下。
在这道题目中,如果简单地使用 BFS 算法,可能会出现重复访问的问题,例如当在访问以第 i 个字符为结尾的子串时,有多个单词可以与之匹配,而这些单词的长度不同,我们需要对每个长度都进行一次扩展,这样会导致节点数量的指数级增加并影响算法效率,即遍历相同的子串多次。
为了解决这个问题,我们可以采用一些剪枝技巧来优化搜索过程,例如在 BFS 算法中引入 visited 数组来记录已经访问过的节点,避免重复访问,或者在遍历子串时判断当前子串是否能够由字典中的单词拼接而成,如果不能则可以跳过,减少扩展操作的次数。我们也可以在 BFS 算法中引入优先队列,按照某种启发式规则来选择哪些节点先进行扩展,从而使算法更快地找到答案。
综上所述,虽然 BFS 算法在一些问题上存在重复访问的问题,但我们可以通过合理的设计算法来避免这个问题,并提高算法的效率。
var wordBreak = function(s, wordDict) {
//BFS
let len = s.length
let set = new Set(wordDict)
let queue = []
queue.push(0)
let visited = new Array(len)
while(queue.length){
let start = queue.shift()
if (visited[start]) continue; // 是访问过的,跳过
visited[start] = true; // 未访问过的,记录一下
for(let i=start+1;i<=len;i++){
let pre = s.slice(start,i)
if(set.has(pre)){
if(i<len){
queue.push(i)
}else{
return true
}
}
}
}
return false
};