貪欲の本質は、全体最適を達成するために各段階で局所最適を選択することです。
記事ディレクトリ
455. クッキーの配布
https://leetcode.cn/problems/assign-cookies/description/
大きなビスケットは年長児も年少児も満足させることができ、年長児を満足させるのが明らかに最も費用対効果が高いです。したがって、ビスケットは大きいものから小さいものへと順序付けされます。ビスケットが子供を満足させることができる場合は、ans++、そうでない場合は、ビスケットが小さな子供を満足させることができるかどうかを試してください。
i<0||j<0 の場合、ループから抜ける条件に注意してください。
var findContentChildren = function (g, s) {
g.sort((a, b) => (a - b))
s.sort((a, b) => (a - b))
let ans = 0;
for (let i = g.length - 1, j = s.length - 1; i >= 0, j >= 0;) {
// g[i] 人 s[j]饼干
if (s[j] >= g[i]) {
ans++;
i--, j--;
} else {
i--;
}
if (i < 0 || j < 0) break;
}
return ans;
};
376. スイングシーケンス
最長のスイング アレイを取得するには、次のように上下のスロープのヘッドとテールを維持する必要があります。
1,2,5,7,5,2,1
これらは1,2,5,7
すべて上り坂ですが、1,7
7 はバックとより良い下り坂を形成できるため、上り坂が他のものより優れています。それが の場合、1,7
バックが下り坂に行きたい場合は、<7 にすることができます。 の場合1,2
、バックが行きたい場合は、7 未満にすることができます。下り坂では、 が必要となり<2
、条件比<7
は小さくなります。逆に。
しかし、選択は1,7
依然として1,2
基本的に上りであり、長さは2です。したがって、問題を、配列に含まれる上り坂と下り坂の変化の数に単純化できます。
上の例では、上に 1 つ、下に 1 つですが、スイング配列をどのように選択しても、常に 3 になります。
フラグを使用して、フロントが上り坂か下り坂かを記録します。フロントが上り坂で現在の配列が下り坂の場合は、ans++ が使用され、その逆も同様です。
平坦な傾斜を決定し、最初の上り/下り坂の後に開始することに注意してください (フラグ値は最初の上り/下り坂を記録します)。
var wiggleMaxLength = function(nums) {
// flag表示是上行1还是下行0,-1表示一直平坡
let ans=1,flag=-1;
// 要找到第一次上行或下行
let index=1;
for(;index<nums.length;index++){
if(nums[index]===nums[index-1]) continue;
if(nums[index]>nums[index-1]) {
flag=1;ans=2;
break;
}else{
flag=0;ans=2;
break;
}
}
for(let i=index+1;i<nums.length;i++){
// 上行
if(nums[i]>nums[i-1]){
if(!flag){
ans++;flag=1;
}
}
// 下行
else if(nums[i]<nums[i-1]){
if(flag){
ans++;flag=0;
}
}
}
return ans;
};
53. 部分配列合計の最大値
配列がすべて負である場合と、配列がすべて負ではない場合の 2 つの状況があります。
配列がすべて負の場合は、最大の要素を持つ配列を選択します。
配列がすべて負でない場合は、常に最大値を累積して保存します。cnt<0 の場合は、cnt=0 を設定します。これは、前の部分を選択しないことと同じです。
var maxSubArray = function (nums) {
let ans = -10001;
// 数组全为负数
let flag = 0;
nums.forEach(item => {
if (item > 0) {
flag = 1; return;
}
})
if (!flag) {
nums.forEach(item => {
ans = Math.max(ans, item);
})
return ans;
}
let cnt = 0;
// 贪心:如果一块连续部分是负数则舍去
nums.forEach(item => {
cnt += item;
if (item >= 0) ans = Math.max(ans, cnt);
else {
if (cnt < 0) cnt = 0;
}
})
return ans;
};
122. 株を売買するのに最適な時間帯 II
価格配列があります:1,2,3,5,8
最低の買いと最高の売りが間違いなく答えです、つまり 8-1=7
8-1=(2-1)+(3-2)+(5-3)+(8-5)であることに注意してください。つまり、プレフィックスの差の正の値の合計です。
配列が:2,0,1,4
でプレフィックスの差が の場合-2,1,3
、0 で買って 4 で売ることを意味します。
var maxProfit = function (prices) {
// 前缀差 差值为正表示利润
let ans = 0;
for (let i = 1; i < prices.length; i++) {
let temp = prices[i] - prices[i - 1]
if (temp > 0) ans += temp;
}
return ans;
};
55. ジャンプゲーム
var canJump = function (nums) {
// cover表示可以覆盖的范围,也就是下标
let cover = 0;
if (nums.length === 1) return true;
for (let i = 0; i <= cover; i++) {
cover = Math.max(cover, i + nums[i]);
if (cover >= nums.length - 1) return true;
}
return false;
};
45. ジャンプゲームⅡ
var jump = function (nums) {
if (nums.length === 1) return 0;
// ans步数,next本次可以到达的最大范围,max下一次可以到达的最大范围
let ans = 0, next = 0, max = 0;
for (let i = 0; i < nums.length; i++) {
max = Math.max(max, i + nums[i]);
// 当前已经达到本次可以达到的最大范围,到下一范围需要一步
if (i === next) {
ans++;
next = max;
if (max >= nums.length - 1) break;
}
}
return ans;
};
1005. K 個の否定後の最大化された配列合計
var largestSumAfterKNegations = function (nums, k) {
// k是偶数可以相当于不变
let f = [], z = []
nums.forEach(item => {
if (item < 0) f.push(item);
else z.push(item);
})
f.sort((a, b) => (a - b));
z.sort((a, b) => (a - b));
if (k >= f.length) {
k -= f.length;
f = f.map(item => {
return -item;
})
z.push(...f);
f = []
} else {
for (let i = 0; i < k; i++) {
let temp = -f[i];
z.push(temp);
}
f = f.slice(k);
k = 0;
}
f.sort((a, b) => (a - b));
z.sort((a, b) => (a - b));
if (k && k % 2) {
// k是奇数
z[0] = -z[0];
}
let ans = 0;
if (f.length) {
ans += f.reduce((pre, val) => {
return pre + val;
})
}
if (z.length) {
ans += z.reduce((pre, val) => {
return pre + val;
})
}
return ans;
};
134. ガソリンスタンド
nは1e5、激しい二重層サイクルはタイムリミットを超えてしまう。
配列 arr=gas-cost を取得します。arr[i] は位置 i から出る残りのオイルを表します。
arr をトラバースし、間隔の合計を計算します。間隔の合計が <0 と表示される場合は、この間隔の最初から開始できない (ガスが切れています) ことを意味します。この間隔の次の場所 j から開始できます。したがって、区間の合計は j から計算されます。この j 値を維持する: が考えられる答えです。
arr の合計を計算します。arr の合計が 0 より大きい場合は、時計回りに歩き始めて走り終えることができる場所があることを意味します。質問からすると、この答えはユニークです。したがって、それは上で維持された j 値です。
arr<0 の場合、答えがないことを意味します。
var canCompleteCircuit = function (gas, cost) {
// 遍历 找gas-cost的区间和<0 答案就是此区间的下一个
let curSum = 0, allSum = 0, ans = 0;
for (let i = 0; i < gas.length; i++) {
let temp = gas[i] - cost[i];
curSum += temp
allSum += temp
// 此区间和为负,不能从这个区间和的头开始
if (curSum < 0) {
curSum = 0;
ans = i + 1;//从此区间的下一个下标开始尝试
}
}
if (allSum < 0) return -1;
return ans;
};
135. お菓子配り(難しい)
アイデア:これは良いアイデアです
フローの保存: 左から右に 1 回、右から左に 1 回トラバースします。
var candy = function (ratings) {
let arr = new Array(ratings.length).fill(1)
// 右边大于左边,即从左到右:右边大的糖果+1
for (let i = 1; i < ratings.length; i++) {
if (ratings[i] > ratings[i - 1]) {
arr[i] = arr[i - 1] + 1;
}
}
// 左边大于右边,即从右到左(右边最小)
for (let i = ratings.length - 1; i >= 0; i--) {
if (ratings[i - 1] > ratings[i]) {
arr[i - 1] = Math.max(arr[i - 1], arr[i] + 1);
}
}
let ans = arr.reduce((pre, val) => {
return pre + val;
})
return ans;
};
860. レモネードチェンジ
forEach
内の 1 つはreturn
、関数ではなくループから飛び出していますreturn
。
var lemonadeChange = function (bills) {
let cnt5 = 0, cnt10 = 0;
let flag = true;
bills.forEach(item => {
console.log(item, cnt5, cnt10)
if (item === 5) {
cnt5++;
} else if (item === 10) {
cnt10++;
if (cnt5 > 0) cnt5--;
else {
flag = false; return;//这里的return是跳出bills的循环 而不是函数的return
}
} else if (item === 20) {
if (cnt5 > 0 && cnt10 > 0) {
cnt5--; cnt10--;
} else if (cnt5 >= 3) {
cnt5 -= 3;
} else {
flag = false; return;
}
}
})
return flag;
};
406.高さに基づいてキューを再構築する
キュー内のコンテンツが「i 番目の人の身長は hi であり、その人の前に身長が hi 以上のちょうど ki 人の人がいる」という条件を満たしたい場合は、次のように構築するだけで済みます。
- 前の人は全員 i 番目の人より背が高い
- 前の人の数は k[i] です
したがって、最初に高さの大きいものから小さいものに並べる必要があり、高さが同じである場合は、k を小さいものから大きいものに並べます。(たとえば、[5,0] は [5,1] の前になければなりません)
[[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
たとえば、並べ替え後の場合:
[ [ 7, 0 ], [ 7, 1 ], [ 6, 1 ], [ 5, 0 ], [ 5, 2 ], [ 4, 4 ] ]
構築を開始します。
- はい
[ 7, 0 ]
、前に 0 人上にいるので、彼は 0 番目にいます。ここの 0 は people[0][0] です。 - はい
[ 7, 1 ]
、前に 1 つ上の人がいますので、彼が最初です。ここの 0 は people[1][1] - はい
[ 6, 1 ]
、前に 1 つ上の人がいます。つまり、彼が最初の 1 人で、ここの 1 は人です[2][1] - はい
[ 5, 0 ]
、前にそれより上の人が 0 人いるので、彼は 0 人目で、ここの 0 は人です[3][1]
等々。
毎回のキューは次のとおりです。
i为 0 , [ [ 7, 0 ] ]
i为 1 , [ [ 7, 0 ], [ 7, 1 ] ]
i为 2 , [ [ 7, 0 ], [ 6, 1 ], [ 7, 1 ] ]
i为 3 , [ [ 5, 0 ], [ 7, 0 ], [ 6, 1 ], [ 7, 1 ] ]
i为 4 , [ [ 5, 0 ], [ 7, 0 ], [ 5, 2 ], [ 6, 1 ], [ 7, 1 ] ]
i为 5 , [ [ 5, 0 ], [ 7, 0 ], [ 5, 2 ], [ 6, 1 ], [ 4, 4 ], [ 7, 1 ] ]
コード全体:
var reconstructQueue = function (people) {
// 先看h,h从大到小,若h一样看k,从小到大
people.sort((a, b) => {
if (a[0] != b[0]) return b[0] - a[0];
else return a[1] - b[1];
})
let queue = []
for (let i = 0; i < people.length; i++) {
queue.splice(people[i][1], 0, people[i]);
}
return queue;
};
452. 最小限の矢で風船を爆発させる
var findMinArrowShots = function (points) {
let ans = 1;
// 都从小到大排
points.sort((a, b) => {
if (a[0] != b[0]) return a[0] - b[0];
else return a[1] - b[1];
})
let min = points[0][0], max = points[0][1];
for (let i = 1; i < points.length; i++) {
let a = points[i][0], b = points[i][1];
if (a >= min && b <= max) {
min = a
max = b
} else {
if (a >= min && a <= max) min = a
else if (b >= min && b <= max) max = b
else {
ans++;
min = a, max = b;
}
}
}
return ans;
};
435. 重複する間隔はありません
var eraseOverlapIntervals = function (intervals) {
intervals.sort((a, b) => {
if (a[0] != b[0]) return a[0] - b[0]
else return a[1] - b[1]
})
let ans = 0
let min = intervals[0][0], max = intervals[0][1]
for (let i = 1; i < intervals.length; i++) {
let a = intervals[i][0], b = intervals[i][1]
if (a >= max) {
min = a, max = b
} else {
ans++;
if (a >= min && b <= max) {
min = a, max = b;
}
}
}
return ans;
};
763. 文字間隔を分割する
var partitionLabels = function (s) {
let start = new Map(), end = new Map()
for (let i = 0; i < s.length; i++) {
// 注意:只有一个的情况
end.set(s[i], i);
if (!start.has(s[i])) start.set(s[i], i);
}
let arr = [...start.keys()], nums = []
arr.forEach(item => {
nums.push([start.get(item), end.get(item)])
})
// 从小到大
nums.sort((a, b) => {
if (a[0] != b[0]) return a[0] - b[0]
else return a[1] - b[1]
})
let min = nums[0][0], max = nums[0][1], ans = []
for (let i = 1; i < nums.length; i++) {
let a = nums[i][0], b = nums[i][1]
if (a > max) {
ans.push(max - min + 1)
min = a, max = b
} else max = Math.max(max, b);
}
ans.push(max - min + 1)
return ans;
};
56. マージ間隔
var merge = function (intervals) {
// 从小到大
intervals.sort((a, b) => {
if (a[0] != b[0]) return a[0] - b[0]
else return a[1] - b[1]
})
let min = intervals[0][0], max = intervals[0][1], ans = []
for (let i = 1; i < intervals.length; i++) {
let a = intervals[i][0], b = intervals[i][1];
if (a >= min && b <= max) continue;
if (a > max) {
ans.push([min, max])
min = a, max = b;
} else max = b;
}
ans.push([min, max])
return ans;
};
738. 単調増加する数値
var monotoneIncreasingDigits = function (n) {
let str = n.toString()
str = str.split('').map(item => {
return Number(item)
});
// 找到第一个不是递增的位置
let flag;
for (let i = str.length - 1; i >= 0; i--) {
if (str[i - 1] > str[i]) {
flag = i
str[i - 1]--;
str[i] = 9
}
}
for (let i = flag; i < str.length; i++) str[i] = 9
return Number(str.join(''))
};
968. バイナリツリーを監視する
まず難易度が跳ね上がりました。