回溯、贪心、动态规划的思路是通过大量做题培养的,不是说几天就能掌握,而且题目不会告诉你使用哪个算法。坚持做题。。。
文章目录
回溯算法——重点是结束条件(纵向)和循环条件(横向)
回溯就相当于遍历多叉树
三角遍历:下一层从 i + 1 的元素开始
矩阵遍历:下一层从 i 元素开始
77.组合——横向元素唯一、三角遍历
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
if(temp.length === k)
------
for(let i = start; i <= n - (k - temp.length) + 1; i++)
216.组合总和 III——横向元素唯一、三角遍历
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
只使用数字1到9
每个数字 最多使用一次
if(curSum > n) return
if(temp.length === k && curSum === n)
-----
const min = Math.min(n-curSum, 9 - (k - temp.length) + 1)
for(let i=start; i <= min; i++)
40.组合总和 II——横向元素重复(需去重)、三角遍历
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
先排序,记录同一层的上一次元素
if (curSum > target) return;
if(curSum === target)
-----
var lastVal = -1
for(let i=start; i<len; i++) {
let val = candidates[i];
39.组合总和 ——横向元素唯一、矩阵遍历
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
先排序
if(curSum === target)
------
for(let i=index; i<len; i++){
backtrack(i)
47.全排列 II——横向元素重复(需去重)、三角遍历
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
排序的目的:上一层填的数可能与当层填的数相同,不排序就需要用数组来记录
var permuteUnique = function(nums) {
var res=[], temp=[], used=new Array(nums.length).fill(false)
nums.sort((a,b) => a-b)
function backtrack(depth) {
if(depth === nums.length) {
res.push([...temp])
return
}
var lastVal = -11
for(let i=0; i<nums.length; i++){
// 同一层不应该测试相同值
if(used[i] || lastVal===nums[i]) continue
temp.push(nums[i])
used[i] = true
lastVal = nums[i]
backtrack(depth + 1)
temp.pop()
used[i] = false
}
}
backtrack(0)
return res
};
不排序
var permuteUnique = function(nums) {
var res=[], temp=[], used=new Array(nums.length).fill(false)
// nums.sort((a,b) => a-b)
function backtrack(depth) {
if(depth === nums.length) {
res.push([...temp])
return
}
var tested = []
// var lastVal = -11
for(let i=0; i<nums.length; i++){
// 同一层不应该测试相同值
if(used[i] || tested.includes(nums[i])) continue
// if(used[i] || lastVal===nums[i]) continue
temp.push(nums[i])
used[i] = true
tested.push(nums[i])
lastVal = nums[i]
backtrack(depth + 1)
temp.pop()
used[i] = false
}
}
backtrack(0)
return res
};
17.电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
var letterCombinations = function(digits) {
const map = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
var res = [], temp = [], len = digits.length
if(len === 0) return res
function backtrack(index){
if(index === len) {
res.push(temp.join(""))
return
}
let val = digits[index]
for(let char of map[val]){
// console.log(char)
temp.push(char)
backtrack(index + 1)
temp.pop()
}
}
backtrack(0)
return res
};
贪心算法
(重做)376.摆动序列
针对序列[2,5],可以假设为[2,2,5],这样它就有坡度了即preDiff = 0
第一个2为虚构,左差值为0,如果右差值不等于0,则第二个2可以作为左边界,preDiff = curDiff。
/**
* @param {number[]} nums
* @return {number}
*/
var wiggleMaxLength = function(nums) {
if(nums.length == 1) return 1;
// 最右节点为第一个
var leftDiff = 0, rightDiff = 0, res = 1;
for(let i=0; i<nums.length-1; i++) {
rightDiff = nums[i+1] - nums[i]
// 左差值可以取0,是因为只取水平线的最右边节点
if((rightDiff > 0 && leftDiff <= 0) || (rightDiff < 0 && leftDiff >= 0)) {
res++;
// i 记录为峰值时, 才改变左插值, i作为左边界
leftDiff = rightDiff
}
}
return res
};
53. 最大子数组和 (简单)
贪心算法
动态规划
var maxSubArray = function(nums) {
var res = new Array(nums.length)
res[0] = nums[0]
for(let i=1; i<nums.length; i++) {
res[i] = Math.max(res[i-1]+nums[i], nums[i])
}
return Math.max(...res)
};
var maxSubArray = function(nums) {
var sum = Number.MIN_SAFE_INTEGER
var curSum = 0
for(let i=0; i<nums.length; i++) {
curSum += nums[i];
if(curSum > sum) sum = curSum;
if(curSum < 0 ) curSum = 0;
}
return sum
};
122. 买卖股票的最佳时机 II (中等)
模拟
贪心算法
动态规划(情况复杂)
注意现金 + 股票才是总资产,今天可以买入(现金减少);或者卖出(现金增加)。
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function(prices) {
var len = prices.length
var dp1 = new Array(len) //第i天持有股票所得现金
var dp2 = new Array(len) //第i天不持有股票所得最多现金
dp1[0] = -prices[0] //买入
dp2[0] = 0
for(let i=1; i<len; i++) {
// 根据前一天持有或不持有
dp1[i] = Math.max(dp1[i-1], dp2[i-1]-prices[i])
dp2[i] = Math.max(dp1[i-1]+prices[i], dp2[i-1])
}
return dp2[len-1]
};
var maxProfit = function(prices) {
// var diff = []
var res = 0
prices.reduce((pre, cur) => {
// diff.push(cur - pre)
res += Math.max(0, cur-pre)
return cur
})
// console.log(diff)
return res
};
var maxProfit = function(prices) {
var st = 0, en = 0, res = 0;
while(st < prices.length && en < prices.length) {
while(en < prices.length-1) {
if(prices[en+1]>prices[en]) {
en++
}else {
break
}
}
if(st != en) {
res += (prices[en] - prices[st])
st = en + 1
en = st
} else{
en++
st++
}
}
return res
};
55. 跳跃游戏 (中等)
核心:覆盖区域不断增大
var canJump = function(nums) {
var end = 0, i = 0;
for(; i<nums.length && i<=end; i++) {
var temp = i + nums[i]
if(temp>end) end = temp
}
if(end >= nums.length-1) return true
return false
};
45. 跳跃游戏 II (中等)
核心:得到本次跳跃的区间,选出区间中下一次能跳最远的位置。
var jump = function(nums) {
var i = 0, end = 0, count = 0; //last = 1
if(nums.length == 1) return 0
while(i<nums.length) {
count++
end = i + nums[i]
if(end >= nums.length-1) return count
var temp = 0
for(let j=i+1; j<=end; j++) {
if(j + nums[j] > temp){
temp = j + nums[j]
i = j
}
}
// last = end + 1
}
};
官方代码:
核心:双指针,规定每一次跳跃的区间,到达区间末尾时,才更新下一次跳跃的区间
var jump = function(nums) {
let curIndex = 0
let nextIndex = 0
let steps = 0
for(let i = 0; i < nums.length - 1; i++) {
nextIndex = Math.max(nums[i] + i, nextIndex)
if(i === curIndex) {
curIndex = nextIndex
steps++
}
}
return steps
};
1005. K 次取反后最大化的数组和 (简单)(不会)
答案简洁是因为:按绝对值的大小来排序
var largestSumAfterKNegations = function(nums, k) {
nums.sort((a,b) => Math.abs(b) - Math.abs(a))
for(let i = 0; i < nums.length && k > 0; i++) {
if(nums[i] < 0) {
nums[i] *= -1
k--
}
}
if(k > 0 && k % 2) {
nums[nums.length - 1] *= -1
}
k = 0
var res = nums.reduce((a, b) => {
return a + b
})
return res
};