javascript算法小抄之笔记
BFS算法
leetcode 109 开密码锁 tips:可以使用双向BFS优化
let bfs(start,target){
//用队列,并定义好visited,将第一个节点加入队列
let queue=[]
let visited=new Set()
queue.push(start)
visited.add(start)
let count=0;
//开始雷达式出队
while(queue.length){
size=queue.length
//for循环按顺序
for(let i=0;i<size;i++){
temp=queue.shift()
if(temp==target){
return count;
}
//如果没有没访问过就进队并标记
if(!visited.has(temp)){
queue.push(temp)
visited.add(temp)
}
}
count++;
}
}
回溯法
leetcode 51 n皇后
let ans=[];
let visited=new Array(50).fill(0)
let diago=new Array(50).fill(0)
let anti_diago=new Array(50).fill(0)
let cur=1;
let memo=[]
var solveNQueens = function(n) {
ans=[]
// 记得是0开始
bfs(0,n)
return ans;
};
function bfs(cur,n){
if(cur==n){
//js中小坑 需要用JSON去完成深拷贝
let copy =JSON.parse(JSON.stringify(memo))
ans.push(copy)
return ;
}
for(let i=0;i<n;i++){
//副对角线
let u=cur+i+n;
//主对角线
let v=cur-i+n;
if(visited[i]==0&&diago[v]==0&&anti_diago[u]==0){
visited[i]=diago[v]=anti_diago[u]=1;
let tempString=''
for(let k=0;k<n;k++){
tempString=tempString+'.'
}
// js中小坑 字符串中更换需要用到string->Array->string 使用Array和join转换
let temp=Array.from(tempString)
temp[i]='Q'
temp=temp.join('')
memo.push(temp)
bfs(cur+1,n)
memo.pop()
visited[i]=diago[v]=anti_diago[u]=0;
}
}
}
动态规划算法
leetcode 322 开密码锁
leetcode 322 零钱兑换
var coinChange = function(coins, amount) {
dp=new Array(amount+1).fill(amount+1)
//初始化!!! 很重要
dp[0]=0;
for(let i=1;i<=amount;i++){
for(let j=0;j<coins.length;j++){
//终止条件
if(i<coins[j])continue
// 转移方程!!
dp[i]=Math.min(dp[i],dp[i-coins[j]])
}
}
if(dp[amount]==amount+1){
return -1
}
else{
return dp[amount];
}
}
leetcode 300 最长递增子序列
var lengthOfLIS = function(nums) {
//初始化dp数组为1,因为至少为本身为递增子序列
dp=new Array(nums.length).fill(1)
for(let i=0;i<nums.length;i++){
for(let j=0;j<i;j++){
if(nums[j]<nums[i]){
//dp数组是找到之前比nums[i]小的数
dp[i]=Math.max(dp[i],dp[j]+1)
}
}
}
let ans=0
//再找dp数组中最大的
for(let i=0;i<nums.length;i++){
ans=Math.max(ans,dp[i])
}
return ans
};
leetcode 354 俄罗斯套娃信封问题
var maxEnvelopes = function(envelopes) {
let size=envelopes.length
//先排序,先把一列按照递增排,如果相等则递减排
envelopes.sort((e1,e2)=>{
if(e1[0]!=e2[0]){
return e1[0]-e2[0]
}else{
return e2[1]-e1[1]
}
})
let ans=0
let dp=new Array(size).fill(1)
for(let i=0;i<size;i++){
for(let j=0;j<i;j++){
//和一维最长上升子序列相同
if(envelopes[i][1]>envelopes[j][1]){
dp[i]=Math.max(dp[i],dp[j]+1)
}
}
}
for(let i=0;i<size;i++){
ans=Math.max(dp[i],ans)
}
return ans
};
双指针算法
leetcode 19 删除链表的倒数第 N 个结点
剑指 Offer II 006. 排序数组中两个数字之和
leetcode 142 环形链表
leetcode 344 反转字符串
var removeNthFromEnd = function(head, n) {
let slow,fast
slow=fast=head
//创建个新的头,保证只有一个节点删除掉这种情况
newHead=new ListNode(0,head)
//保存下新的头
pre=newHead
for(let i=0;i<n;i++){
//快指针先走n步
fast=fast.next
}
while(fast!=null){
//快慢指针和新头指针都往后走
fast=fast.next
slow=slow.next
pre=pre.next
}
pre.next=slow.next
return newHead.next
};
leetcode 160 相交链表
var getIntersectionNode = function(headA, headB) {
if(headA==null||headB==null){
return false
}
let left,right
left=headA
right=headB
while(left!=right){
if(left==null){
left=headB
}else{
left=left.next
}
if(right==null){
right=headA
}else{
right=right.next
}
}
return left
};
二分查找算法
leetcode 704 二分查找
var search = function(nums, target) {
let left=0
//左闭右闭
let right=nums.length-1
//终止条件为left=right+1
while(left<=right){
//重点!! 因为js特性所以要用ParseInt去把小数变成整数,否则会进入死循环
let mid = left+parseInt((right-left)/2);
if(nums[mid]==target){
return mid
}else if(nums[mid]<target){
left=mid+1
}else if(nums[mid]>target){
right=mid-1
}
}
return -1
};
补充
左侧边界的二分查找
var left_search=function(nums, target){
let left=0
let right=nums.length-1
while(left<=right){
let mid =left+parseInt((right-left)/2)
if(nums[mid]<target){
left=mid+1
}else if(nums[mid]>target){
right=mid-1
}else if(nums[mid]==target){
right=mid-1
}
}
if(left>=nums.length||nums[left]!=target){
return -1
}
return left
}
右侧边界的二分查找
var right_search=function(nums,target){
let left=0
let right=nums.length-1
while(left<=right){
let mid =left+parInt((right-left)/2)
if(nums[mid]<target){
left=mid+1
}else if(nums[mid]>target){
right=mid-1
}else if(nums[mid]==target){
left=mid+1
}
}
if(right<0||nums[right]!=target){
return -1
}
return right
}
滑动窗口算法
leetcode 76 最小覆盖子串
leetcode 567 字符串的排列
leetcode 438 找到字符串中所有字母异位词
leetcode 3 无重复字符的最长子串
var minWindow = function(s, t) {
//window为滑动窗口,needs为目标字符串
let window={}
let needs={}
//js中小特性
for(let i of t){
needs[i]=(needs[i]||0)+1
}
let left=0
let right=0
//valid表示有几个满足needs的条件
let valid=0
let len=Infinity
let start
//移动右边界
while(right<s.length){
const c=s[right]
right++
//如果是目标字符串就把滑动窗口内加1
if(needs[c]){
window[c]=(window[c]||0)+1
if(needs[c]==window[c]){
valid++
}
}
//移动左边界
while(valid==Object.keys(needs).length){
//判断是否更小要更新
if(right-left<len){
len=right-left
start=left
}
const d=s[left]
left++
//如果左边界缩小需要调整valid
if(needs[d]){
if(needs[d]==window[d]){
valid--
}
window[d]--
}
}
}
//返回答案
return len==Infinity?"":s.substr(start,len)
};
动态规划----二维数组算法
总结步骤:
-
想清楚dp代表什么!!!(要方便后面找传递函数)
-
找传递函数!!(可以先从已知来开始找)
-
填数组看动态规划的方向
leetcode 1143 最长公共子序列
dp[i][j]代表text1的0-i中和text2的0-j中最长公共子序列
var longestCommonSubsequence = function(text1, text2) {
//二维数组定义小坑(1.map只能遍历有空间的数组 2.箭头函数中{}表示函数体,如果没有return 默认返回undefined)
let dp=new Array(text1.length+1).fill(0).map(()=>new Array(text2.length+1).fill(0))
//第一行和第一列设为0,表示空字符串
for(let i=1;i<=text1.length;i++){
for(let j=1;j<=text2.length;j++){
//如果前一个字符是相同的话那么就+1
if(text1[i-1]==text2[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
}
//否则比较
else{
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])
}
}
}
return dp[text1.length][text2.length]
};
leetcode 72 [编辑距离](leetcode-cn.com/problems/lo…)
dp[i][j]代表word1的0-i中和word2的0-j中编辑距离
var minDistance = function(word1, word2) {
let dp=new Array(word1.length+1).fill(0).map(()=>new Array(word2.length+1).fill(0))
//将空串的base case初始化
for(let i=1;i<=word1.length;i++){
dp[i][0]=i
}
for(let j=1;j<=word2.length;j++){
dp[0][j]=j
}
for(let i=1;i<=word1.length;i++){
for(let j=1;j<=word2.length;j++){
if(word1[i-1]==word2[j-1]){
dp[i][j]=dp[i-1][j-1]//无需操作
}
else{
dp[i][j]=Math.min(
dp[i-1][j-1]+1,//替换
dp[i-1][j]+1,//删除
dp[i][j-1]+1//插入
)
}
}
}
return dp[word1.length][word2.length]
};
leetcode 516 最长回文子序列
dp[i][j]代表i,j之间最长回文子序列
var longestPalindromeSubseq = function(s) {
//所有j<i的地方是不符合我们定义的dp,所以初始化为0
let dp=new Array(s.length).fill(0).map(()=>new Array(s.length).fill(0))
for(let i=0;i<s.length;i++){
for(let j=0;j<s.length;j++){
//只有一个字符是回文
if(i==j){
dp[i][j]=1
}
}
}
//这里动态规划方向是重点!!!
//是从右下角往上推
for(let i=s.length-2;i>=0;i--){
for(let j=i+1;j<s.length;j++){
if(s[i]==s[j]){
dp[i][j]=dp[i+1][j-1]+2
}else{
dp[i][j]=Math.max(dp[i+1][j],dp[i][j-1])
}
}
}
return dp[0][s.length-1]
};
这里可以用一维的数组来优化空间复杂度,优化为O(N),具体可见算法小抄P146
leetcode 5 最长回文子串
var longestPalindrome = function(s) {
let n=s.length
let dp=new Array(n).fill(false).map(()=>new Array(n).fill(false))
let maxx=0;
let ans=''
for(let i=n-1;i>=0;i--){
for(let j=i;j<n;j++){
if(s[i]==s[j]){
if(j-i<=1||dp[i+1][j-1]){
if(j-i+1>maxx){
ans=s.slice(i,j+1)
maxx=j-i+1
}
dp[i][j]=true
}
}
}
}
return ans
};
tips:
var test = 'hello world';
alert(test.slice(4,7)); //o w
alert(test.substring(4,7)); //o w
alert(test.substr(4,7)); //o world
leetcode 312 *Hard 戳气球
//dp数组表示(i,j)之间的气球能得分最大值
var maxCoins = function(nums) {
//左右两边气球分值初始化为1
let points=new Array(nums+2).fill(1)
points[0]=points[nums.length+1]=1
for(let i=1;i<=nums.length;i++){
points[i]=nums[i-1]
}
let dp=new Array(nums.length+2).fill(0).map(()=>new Array(nums.length+2).fill(0))
//这里相当于二维数组从右下角开始推到左上角
for(let i=nums.length;i>=0;i--){
for(let j=i+1;j<nums.length+2;j++){
//假设k为最后一个戳破的气球
for(let k=i+1;k<j;k++){
dp[i][j]=Math.max(dp[i][k]+dp[k][j]+points[i]*points[k]*points[j],dp[i][j])
}
}
}
return dp[0][nums.length+1]
};
动态规划---01背包算法
典中典 01背包
var knapsack(W,N,wt,val){
let dp= new Array(N+1).fill(0).map(()=>new Array(W+1).fill(0))
for(let i=1;i<=N;i++){
for(let j=1;j<=W;j++){
//可以放入的情况下
if(j>=wt[i-1]){
//放或者不放取max
dp[i][j]=Math.max(dp[i-1][j-wt[i-1]]+val[i-1],dp[i-1][j])
}else{
//放不进去
dp[i][j]=dp[i-1][j]
}
}
}
}
leetcode 416 分割等和子集
var canPartition = function(nums) {
let sum=0
nums.forEach((x)=>{
sum+=x,
})
//如果sum为奇数,则不可能分成两堆
if(sum&1) return false
let dp =new Array(nums.length+1).fill(false).map(()=>new Array(parseInt(sum/2)+1).fill(false))
//如果每堆要求0则直接满足可以凑满,设为true
for(let i=0;i<=nums.length;i++){
dp[i][0]=true
}
for(let i=1;i<=nums.length;i++){
for(let j=1;j<=parseInt(sum/2);j++){
//如果可以加上
if(nums[i-1]<=j){
dp[i][j]=dp[i-1][j-nums[i-1]] || dp[i-1][j]
}else{
dp[i][j]=dp[i-1][j]
}
}
}
return dp[nums.length][parseInt(sum/2)]
};
leetcode 494 目标和
//本题可以直接用dfs回溯来爆破或者用以下转换成背包问题
var findTargetSumWays = function(nums, target) {
function bag(nums,sum){
let dp=new Array(nums.length+1).fill(0).map(()=>new Array(sum+1).fill(0))
for(let i=0;i<=nums.length;i++){
dp[i][0]=1
}
for(let i=1;i<nums.length+1;i++){
for(let j=0;j<=sum;j++){
if(j>=nums[i-1]){
dp[i][j]=dp[i-1][j-nums[i-1]]+dp[i-1][j]
}else{
dp[i][j]=dp[i-1][j]
}
}
}
return dp[nums.length][sum]
}
let sum=0
nums.forEach((x)=>{
sum+=x
})
//下面两种情况都无法分成两个A集合满足元素和=(target+sum)
if((sum+target)%2==1||(target+sum)<0) return 0
//开始背包算法
return bag(nums,parseInt((target+sum)/2))
};
leetcode 518 零钱兑换 II
//实质同上的背包算法相同 可以看上面的题目
var change = function(amount, coins) {
let dp=new Array(coins.length+1).fill(0).map(()=>new Array(amount+1).fill(0))
for(let i=0;i<coins.length+1;i++){
dp[i][0]=1
}
for(let i=1;i<=coins.length;i++){
for(let j=0;j<=amount;j++){
if(coins[i-1]<=j){
dp[i][j]=dp[i][j-coins[i-1]]+dp[i-1][j]
}else{
dp[i][j]=dp[i-1][j]
}
}
}
return dp[coins.length][amount]
};
动态规划---打家劫舍算法
leetcode 198 打家劫舍
var rob = function(nums) {
let n=nums.length
let dp_i
let dp_i1=0
let dp_i2=0
//因为动态规划依赖于dp[i+1],dp[i+2]的情况,所以可以把空间复杂度优化成O(N)
for(let i=n-1;i>=0;i--){
//dp[i]=Math.max(dp[i+1],dp[i+2]+nums[i])
dp_i=Math.max(dp_i1,dp_i2+nums[i])
dp_i2=dp_i1
dp_i1=dp_i
}
return dp_i
};
leetcode 213 打家劫舍 II
var rob = function(nums) {
function robRes(nums,start,end){
let n=nums.length
let dp=new Array(n+2).fill(0)
for(let i=end;i>=start;i--){
dp[i]=Math.max(dp[i+1],dp[i+2]+nums[i])
}
return dp[start]
}
if(nums.length==1){
return nums[0]
}
//1.打劫第一家放弃最后一家 或者 打劫最后一家放弃第一家 tips:因为两家都不打劫肯定被包括在情况1和2中了所以不考虑
return Math.max(robRes(nums,0,nums.length-2),robRes(nums,1,nums.length-1))
};
leetcode 337 打家劫舍 III
var rob = function(root) {
const memo=new Map()
function robTree(node){
if(node==null) return 0
//查看备忘录是否存储过
if(memo.get(node)){
return memo.get(node)
}
//打劫这一层
let ans1=node.val
+(node.left==null?0:robTree(node.left.left)+robTree(node.left.right))
+(node.right==null?0:robTree(node.right.left)+robTree(node.right.right))
//不打劫这一层
let ans2=(robTree(node.left)+robTree(node.right))
let ans=Math.max(ans1,ans2)
//加入备忘录
memo.set(node,ans)
return ans
}
return robTree(root)
};
LRU算法
leetcode 146 LRU 缓存
//vue中keep alive 使用这个缓存淘汰策略 实质是哈希链表 哈希表+双向链表实现
var LRUCache = function(capacity) {
this.capacity=capacity
//用map来实现
this.map=new Map()
};
LRUCache.prototype.get = function(key) {
//如果有的话 就先删除再重新加入
if(this.map.has(key)){
let temp=this.map.get(key)
this.map.delete(key)
this.map.set(key,temp)
return temp
}
return -1
};
LRUCache.prototype.put = function(key, value) {
//有的话先删除
if(this.map.has(key)){
this.map.delete(key)
}
this.map.set(key,value)
//大于了最大缓存量,删除头
if(this.map.size>this.capacity){
//点睛之笔通过this.map.keys().next().value来获得多余的节点
this.map.delete(this.map.keys().next().value)
}
};
二叉树算法
典型题目:
leetcode 144 二叉树的前序遍历
leetcode 94 二叉树的中序遍历
leetcode 145 二叉树的后序遍历
前三种过于简单,这里就不贴代码了
leetcode 102 二叉树的层序遍历
var levelOrder = function(root) {
let ans=[]
//层次遍历通过队列先进先出特性
let queue=[]
queue.push(root)
//存储每一层的节点
let cur=[]
//在队列不为空的时候
while(queue.length){
let len=queue.length
//将这一层的所有节点取出
while(len--){
let temp=queue.shift()
if(temp){
cur.push(temp.val)
queue.push(temp.left)
queue.push(temp.right)
}
}
//将该层节点推入ans中
if(cur.length){
ans.push(cur)
}
cur=[]
}
return ans
};
leetcode 236 二叉树的最近公共祖先
var lowestCommonAncestor = function(root, p, q) {
//递归终点 1.如果root为null 2.如果root为p、q其中一个则返回root
if(root==null) return null
if(p==root||q==root) return root
//后序遍历递归
let left=lowestCommonAncestor(root.left,p,q)
let right=lowestCommonAncestor(root.right,p,q)
//如果左右都为null 则表示不在当前root节点下
if(left==null&&right==null){
return null
}
//如果左右都存在,则表示当前的root为最近公共父亲
if(left!=null&&right!=null){
return root
}
//如果一方为null,则返回另一方
return left==null?right:left
};
完全二叉树算法
leetcode 100 相同的树
var isSameTree = function(p, q) {
function isSame(p,q){
//都为空返回真
if(p==null&&q==null) return true
//一个空则为假
if(p==null||q==null) return false
if(p.val==q.val){
return isSame(p.left,q.left)&&isSame(p.right,q.right)
}
//两者值不相等就返回假
return false
}
return isSame(p,q)
};
leetcode 98 验证二叉搜索树
var isValidBST = function(root) {
//增加了两个参数min,max为了保证左孩子一定小于父节点
function isValid(node,min,max){
if(node==null) return true
//如果min存在并且不符合左孩子小于父亲 则返回假
if(min!=null && node.val<=min.val) return false
//同上
if(max!=null && node.val>=max.val) return false
//返回两者递归都为真的情况
return isValid(node.left,min,node)&&isValid(node.right,node,max)
}
return isValid(root,null,null)
};
leetcode 701 二叉搜索树中的插入操作
//整体思路先找到对应的节点后才插入
var insertIntoBST = function(root, val) {
function myInsert(node,val){
if(node==null){
//找到了插入点则返回new的新节点
return new TreeNode(val,null,null)
}
if(node.val<val){
//更新
node.right=myInsert(node.right,val)
}
if(node.val>val){
//更新
node.left=myInsert(node.left,val)
}
//返回更新后的新节点
return node
}
root=myInsert(root,val)
return root
};
leetcode 450 删除二叉搜索树中的节点
var deleteNode = function(root, key) {
function delete_node(node,key){
if(node==null) return null
//找到对应要删除的节点
if(node.val==key){
//情况1 左孩子为空 将右孩子接上
if(node.left==null&&node.right){
return node.right
}
//情况2 右孩子为空 将左孩子接上
if(node.right==null&&node.left){
return node.left
}
//情况3 左右孩子都为空 直接删除
if(node.left==null&&node.right==null){
return null
}
//情况4 找右孩子中最小的节点然后递归继续删除
minNode=getMinNode(node.right)
node.val=minNode.val
node.right=delete_node(node.right,node.val)
}
if(node.val<key){
node.right=delete_node(node.right,key)
}
if(node.val>key){
node.left=delete_node(node.left,key)
}
return node
}
function getMinNode(node){
while(node.left){
node=node.left
}
return node
}
root=delete_node(root,key)
return root
};
单调栈和单调队列算法
单调栈典型题目:
算法小抄P267 下一个更大元素1
var nextGeaterElement= function(nums){
let stack=[]
let ans=new Array(nums.length)
for(let i=nums.length-1;i>=0;i--){
while(stack.length>0&&stack[stack.length-1]<=nums[i]){
stack.pop()
}
ans[i]=(stack.length==0)?-1:stack[stack.length-1]
stack.push(nums[i])
}
return ans
}
算法小抄P270 下一个更大元素2
var nextGeaterElement= function(nums){
let stack=[]
let n=nums.length
let ans=new Array(n)
//逻辑上将原来的数组复制成了双倍大小
for(let i=2*n-1;i>=0;i--){
//实际上通过i%n来解决循环数组的问题
while(stack.length>0&&stack[stack.length-1]<=nums[i%n]){
stack.pop()
}
ans[i%n]=(stack.length==0)?-1:stack[stack.length-1]
stack.push(nums[i%n])
}
return ans
}
leetcode 739 每日温度
var dailyTemperatures = function(temperatures) {
let ans=new Array(temperatures.length).fill(0)
//用数组代替栈
let stack=[]
//从右往左遍历
for(let i=temperatures.length-1;i>=0;i--){
//如果栈不为空并且栈顶的气温小于等于当前的气温
while(stack.length>0&&temperatures[stack[stack.length-1]]<=temperatures[i]){
//出队
stack.pop()
}
//当前的ans[i]为栈顶元素也就是最高的元素索引
ans[i]=(stack.length==0)?0:(stack[stack.length-1]-i)
stack.push(i)
}
return ans
};
单调队列典型题目:
leetcode 239 滑动窗口最大值
var maxSlidingWindow = function(nums, k) {
let ans=[]
let deque=[]
for(let i=0;i<nums.length;i++){
//判断当前元素和单调队列第一个元素是否还是k之内
if(i-deque[0]>k-1){
deque.shift()
}
//判断是否新进的元素后是否能保持单调
if(deque.length){
while(nums[i]>nums[deque[deque.length-1]]){
deque.pop()
}
}
deque.push(i)
//如果大于k-1说明要开始找最大值了,而最大值就是队首
if(i>=k-1){
ans.push(nums[deque[0]])
}
}
return ans
};
链表算法
leetcode 206 反转链表
//解法1 正向反转
var reverseList = function(head) {
let pre=null
let cur=head
while(cur!=null){
let next=cur.next
cur.next=pre
pre=cur
cur=next
}
return pre
};
//解法2 递归反转
var reverseList = function(head) {
if(head==null || head.next==null){
return head
}
const newHead=reverseList(head.next)
head.next.next=head
head.next=null
return newHead
};
leetcode 234 回文链表
//方法1 通过递归栈来比较
var isPalindrome = function(head) {
let left=head
function reverseList(right){
if(right==null) return true
let res=reverseList(right.next)
res=res&&(right.val==left.val)
left=left.next
return res
}
return reverseList(head)
};
//方法2 通过反转一半链表
var isPalindrome = function(head) {
let slow,fast
slow=fast=head
//找到链表中点
while(fast!=null&&fast.next!=null){
slow=slow.next
fast=fast.next.next
}
//如果fast指针没有指向null,则说明链表长度为奇数,将slow往前走一步
if(fast!=null){
slow=slow.next
}
//反转链表
function reverseList(head){
if(head==null||head.next==null) return head
let newHead=reverseList(head.next)
head.next.next=head
head.next=null
return newHead
}
let newHead=reverseList(slow)
//通过双指针比较反转后链表是否和
while(newHead!=null){
if(newHead.val!=head.val) return false
newHead=newHead.next
head=head.next
}
return true
};
leetcode 92 反转链表 II
//主要思路:找到要反转的链表前后然后切断 接着反转 再连接
var reverseBetween = function(head, left, right) {
//虚头节点
let dummyNode=new ListNode(-1,head)
let preNode=dummyNode
for(let i=0;i<left-1;i++){
preNode=preNode.next
}
let rightNode=preNode
for(let i=0;i<right-left+1;i++){
rightNode=rightNode.next
}
let leftNode=preNode.next
let nextNode=rightNode.next
//切断
preNode.next=null
rightNode.next=null
//反转
let newNode =reverseList(leftNode)
//连接
preNode.next=newNode
leftNode.next=nextNode
//切记返回虚节点的下一个节点
return dummyNode.next
};
leetcode 25 K 个一组翻转链表
var reverseKGroup = function(head, k) {
//如果开头为null 则返回
if(head==null) return null
let a,b
//[a,b)左闭右开区间内反转
a=b=head
//b走k步 如果遇到b为null则返回
for(let i=0;i<k;i++){
if(b==null) return head
b=b.next
}
//链表反转后记录新节点
let newNode=reverseList(a,b)
//尾节点接递归
a.next=reverseKGroup(b,k)
return newNode
};
//正常反转链表 但是链表终点变成了targetNode
function reverseList(node,targetNode){
let pre=null
let cur=node
while(cur!=targetNode){
let next=cur.next
cur.next=pre
pre=cur
cur=next
}
return pre
}
回溯算法之子集问题:
leecode 78 子集
let ans=[]
var subsets = function(nums) {
ans=[]
let track=[]
backTrack(nums,0,track)
return ans
};
function backTrack(nums,start,track){
//小坑 需要用解构赋值,否则因为浅拷贝会遇到问题
ans.push([...track])
//通过start来控制不要重复
for(let i=start;i<nums.length;i++){
track.push(nums[i])
backTrack(nums,i+1,track)
track.pop()
}
}
leetcode 46 全排列
let ans=[]
var permute = function(nums) {
ans=[]
let track=[]
//开始回溯算法
backTrack(nums,track)
return ans
};
function backTrack(nums,track){
//终止条件为满足够长度
if(track.length==nums.length){
ans.push([...track])
return
}
//递归
for(let i=0;i<nums.length;i++){
if(!track.includes(nums[i])){
track.push(nums[i])
backTrack(nums,track)
track.pop()
}
}
}
leetcode 77 组合
let ans=[]
var combine = function(n, k) {
ans=[]
let track=[]
backTrack(n,k,1,track)
return ans
};
function backTrack(n,k,start,track){
//满足条件,结束递归
if(track.length==k){
ans.push([...track])
return
}
//从start开始递归,为了保证不重复
for(let i=start;i<=n;i++){
track.push(i)
backTrack(n,k,i+1,track)
track.pop()
}
}
回溯算法之常见题目
leetcode 22 括号生成
let ans=[]
var generateParenthesis = function(n) {
//初始化!!!
let track=[]
ans=[]
backTrack(track,n,n)
return ans
};
function backTrack(track,left,right){
//如果多用了括号返回
if(left<0||right<0) return
//如果先用右括号多于左括号 返回
if(right<left) return
//刚刚好则加入答案
if(left==0&&right==0){
ans.push(track.join(''))
return
}
//加左括号,回溯 加右括号,回溯
track.push('(')
backTrack(track,left-1,right)
track.pop()
track.push(')')
backTrack(track,left,right-1)
track.pop()
}
leetcode 37 解答数独
var solveSudoku = function(board) {
backTrack(board,0,0)
return board
};
//因为只有一个解,所以return true来打断递归
function backTrack(board,row,col){
//找到了
if(row==9&&col==8) return true
//需要去下一列填数字
if(row>8) return backTrack(board,0,col+1)
//如果已有数字则去填下一行
if(board[row][col]!='.') return backTrack(board,row+1,col)
for(let i=1;i<=9;i++){
//可以填这个数字的话
if(isValid(board,row,col,i)){
board[row][col]=(i+'')
//找到了就返回true 停止递归
if(backTrack(board,row+1,col)){
return true
}
board[row][col]='.'
}
}
return false
}
//判断是否行、列、3x3内有填写过
function isValid(board,r,c,n){
let rowStart=Math.floor(r/3)*3
let colStart=Math.floor(c/3)*3
for(let i=0;i<9;i++){
if(board[r][i]==n) return false
if(board[i][c]==n) return false
}
for(let i=rowStart;i<rowStart+3;i++){
for(let j=colStart;j<colStart+3;j++){
if(board[i][j]==n){
return false
}
}
}
return true
}
leetcode 773 滑动谜题
//手动存下每个位置0可以移动的坐标
let neighbor=[[1,3],[0,4,2],[1,5],[0,4],[3,1,5],[2,4]]
var slidingPuzzle = function(board) {
let queue=[]
//用字符串来比较
let target='123450'
let init=[]
let ans=0
let visited=new Set()
for(let i=0;i<2;i++){
for(let j=0;j<3;j++){
init.push(board[i][j])
}
}
queue.push(init.join(''))
while(queue.length!=0){
ans++
let size=queue.length
for(let i=0;i<size;i++){
//取出每个状态
let status=queue.shift()
if(status==target) return ans-1
for(const nextStatus of getNext(status)){
//如果没有访问过则标记然后加入队列
if(!visited.has(nextStatus)){
visited.add(nextStatus)
queue.push(nextStatus)
}
}
}
}
return -1
};
function getNext(status){
let ret=[]
let array=Array.from(status)
const pos=status.indexOf('0')
for(let i=0;i<neighbor[pos].length;i++){
y=neighbor[pos][i]
let temp=array[y]
array[y]=array[pos]
array[pos]=temp
//交换位置然后加入数组,表示可以去的下一步状态
ret.push(array.join(''))
temp=array[y]
array[y]=array[pos]
array[pos]=temp
}
return ret
}
典题题目两数之和
leetcode 15 [三数之和](leetcode-cn.com/problems/tw…)
var threeSum = function(nums) {
let ans=[]
//小坑 需要自定义判断大小的函数,否则负数会不是从小到大
nums.sort((a,b)=>{
return a-b
})
for(let i=0;i<nums.length;i++){
let rest=(0-nums[i])
let ret=twoSum(nums,i+1,nums.length-1,rest)
//如果有两数之和就加入答案
if(ret.length!=0){
for(let item of ret){
ans.push([nums[i]].concat(item))
}
}
//防止第一个数重复
while(i<nums.length-1&&nums[i]==nums[i+1]) i++
}
return ans
};
function twoSum(nums,left,right,target){
let ret=[]
while(left<right){
let leftNum=nums[left]
let rightNum=nums[right]
let sum=leftNum+rightNum
if(sum<target){
left++
}else if(sum>target){
right--
}
else{
ret.push([nums[left],nums[right]])
//防止第二第三个数重复
while(left<right&&leftNum==nums[left]) left++
while(left<right&&rightNum==nums[right]) right--
}
}
return ret
}
高频面试题系列
高效寻找素数 算法小抄P351
function countPrime(n){
//将数组置为都是素数
let arrPrime= new Array(n).fill(true)
//只需要找到sqrt(n)
for(let i=2;i*i<n;i++){
//如果i是素数 那么i*i之后的每个+i数都是合数 置位false
if(Prime(i)){
for(let j=i*i;j<n;j+=i){
arrPrime[j]=false
}
}
}
//统计有多少个素数
let ans=0
for(let i=2;i<n;i++){
if(arrPrime[i]){
ans++
}
}
return ans
}
function Prime(n){
//只需要到sqrt(n)
for(let i=2;i*i<=n;i++){
if(n%i==0) return false
}
return true
}
leetcode 244 基本计算器
var calculate = function(s) {
//操作符
let ops=[1]
//符号
let sign=1
//指针
let i=0
let n=s.length
let ans=0
while(i<n){
if(s[i]==' '){
i++
}else if(s[i]=='+'){
sign=ops[ops.length-1]
i++
}else if(s[i]=='-'){
sign=-ops[ops.length-1]
i++
}else if(s[i]=='('){ //如果是(则将前面的sign推入操作符
ops.push(sign)
i++
}else if(s[i]==')'){ //如果是)则将ops中操作符弹出一个
ops.pop()
i++
}else{
let num=0
//如果是数字,循环取完然后统计
while(i<n&& !(isNaN(Number(s[i]))) && s[i] !== ' '){
num=10*num+Number(s[i])
i++
}
ans+=sign*num
}
}
return ans
};
leetcode 372 超级次方
let base=1337
var superPow = function(a, b) {
if(b.length==0) return 1
let last=b.pop()
//先取最后一个数来求a的last次方
let part1=myPow(a,last)
//再递归求剩下的
let part2=myPow(superPow(a,b),10)
//每次计算%base
return (part1*part2)%base
};
function myPow(a,b){
a%=base
let ans=1
for(let i=0;i<b;i++){
ans*=a
ans%=base
}
return ans
}